diff options
author | sanine <sanine.not@pm.me> | 2023-09-18 05:11:05 +0000 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2023-09-18 05:11:05 +0000 |
commit | 3425c3c435a000b14103a0055fd913ccb855b734 (patch) | |
tree | 2efbb0599104d3794f5c0604d71ac16bf97368d2 | |
parent | bfcc2e759807a692d8e0353e3bbecd109175032b (diff) |
implement basic ui
-rw-r--r-- | draw.lua | 2 | ||||
-rw-r--r-- | style.css | 51 | ||||
-rwxr-xr-x | yarrow-ui.cgi | 158 | ||||
-rwxr-xr-x | yarrow.cgi | 5 |
4 files changed, 213 insertions, 3 deletions
@@ -296,7 +296,7 @@ function draw(stats, theme) {base(stats, theme)}, {traits(stats, theme)}, {actions(stats, theme)}, - {reactions(stats, theme)}, + --{reactions(stats, theme)}, {legendary(stats, theme)} ) return m.render(m.svg{ @@ -84,3 +84,54 @@ pre code { text-align: center; } + +.inputdiv { + display: flex; +} + + +label { + flex: 1; + min-width: 9em; + display: inline-block; + text-align: right; + vertical-align: top; + margin: 0 0.5em 0 0; +} + +input, textarea, button { + font-family: monospace; + flex: 3; + color: var(--light); + background-color: #020202; + border: 1px solid var(--dark); +} + +input:hover, textarea:hover, button:hover { + border: 1px solid var(--light); +} + +*:focus { + outline: none; + border: 1px solid var(--highlight); +} + +*:focus:hover { + border: 1px solid #ffce4e; +} + +input { + padding: 0.2em; +} + +textarea { + min-width: 30em; +} + +button { + font-size: 1em; + padding: 0.2em; + color: var(--highlight); +} + + diff --git a/yarrow-ui.cgi b/yarrow-ui.cgi index 37c2f87..9a1d134 100755 --- a/yarrow-ui.cgi +++ b/yarrow-ui.cgi @@ -6,7 +6,156 @@ local b64 = require '3rdparty.base64' local json = require '3rdparty.json' -local img = h('img', { style='color: white', src='https://sanine.net/utils/yarrow/yarrow.cgi?statistics=eyJzdHIiOjE3LCJocCI6IjM5ICg2ZDggKyAxMikiLCJ3aXMiOjEzLCJhYyI6IjE3IChuYXR1cmFsIGFybW9yLCBzaGllbGQpIiwiY29uIjoxNSwiaW50IjoxMiwic3BlZWQiOiIzMCBmdC4iLCJhY3Rpb25zIjpbeyJuYW1lIjoiV2FyaGFtbWVyIiwidmFsdWUiOiIqTWVsZWUgV2VhcG9uIEF0dGFjazoqICs1IHRvIGhpdCwgcmVhY2ggNSBmdC4sIG9uZSB0YXJnZXQuICpIaXQ6KiA3ICgxZDggKyAzKSBibHVkZ2VvbmluZyBkYW1hZ2Ugb3IgOCAoMWQxMCArIDMpIGJsdWRnZW9uaW5nIGRhbWFnZSBpZiB1c2VkIHdpdGggdHdvIGhhbmRzIHRvIG1ha2UgYSBtZWxlZSBhdHRhY2ssIHBsdXMgMyAoMWQ2KSBmaXJlIGRhbWFnZS4ifV0sInNpemUiOiJtZWRpdW0iLCJ0eXBlIjoiZWxlbWVudGFsIiwiY2hhIjoxMCwibGVnZW5kYXJ5Ijp7ImRlc2NyaXB0aW9uIjoiVGhlIGRlYXRoIHR5cmFudCBjYW4gdGFrZSAzIGxlZ2VuZGFyeSBhY3Rpb25zLCB1c2luZyB0aGUgRXllIFJheSBvcHRpb24gYmVsb3cuIEl0IGNhbiB0YWtlIG9ubHkgb25lIGxlZ2VuZGFyeSBhY3Rpb24gYXQgYSB0aW1lIGFuZCBvbmx5IGF0IHRoZSBlbmQgb2YgYW5vdGhlciBjcmVhdHVyZSdzIHR1cm4uIFRoZSB0eXJhbnQgcmVnYWlucyBzcGVudCBsZWdlbmRhcnkgYWN0aW9ucyBhdCB0aGUgc3RhcnQgb2YgaXRzIHR1cm4uIiwiYWN0aW9ucyI6W3sibmFtZSI6IkV5ZSBSYXkiLCJ2YWx1ZSI6IlRoZSBkZWF0aCB0eXJhbnQgdXNlcyBvbmUgcmFuZG9tIGV5ZSByYXkuIn1dfSwiaW1tdW5pdGllcyI6ImZpcmUsIHBvaXNvbiIsInRyYWl0cyI6W3sibmFtZSI6IkhlYXRlZCBCb2R5IiwidmFsdWUiOiJBIGNyZWF0dXJlIHRoYXQgdG91Y2hlcyB0aGUgYXplciBvciBoaXRzIGl0IHdpdGggYSBtZWxlZSBhdHRhY2sgd2hpbGUgd2l0aGluIDUgZmVldCBvZiBpdCB0YWtlcyA1ICgxZDEwKSBmaXJlIGRhbWFnZS4ifSx7Im5hbWUiOiJIZWF0ZWQgV2VhcG9ucyIsInZhbHVlIjoiV2hlbiB0aGUgYXplciBoaXRzIHdpdGggYSBtZXRhbCBtZWxlZSB3ZWFwb24sIGl0IGRlYWxzIGFuZCBleHRyYSAzICgxZDYpIGZpcmUgZGFtYWdlIChpbmNsdWRlZCBpbiB0aGUgYXR0YWNrKS4ifSx7Im5hbWUiOiJJbGx1bWluYXRpb24iLCJ2YWx1ZSI6IlRoZSBhemVyIHNoZWRzIGJyaWdodCBsaWdodCBpbiBhIDEwPWZvb3QgcmFkaXVzIGFuZCBkaW0gbGlnaHQgZm9yIGFuIGFkZGl0aW9uYWwgMTAgZmVldC4ifV0sImFsaWdubWVudCI6Imxhd2Z1bCBuZXV0cmFsIiwic2F2aW5nX3Rocm93cyI6IkNvbiArNCIsIm5hbWUiOiJBemVyIiwiY29uZGl0aW9uX2ltbXVuaXRpZXMiOiJwb2lzb25lZCIsImxhbmd1YWdlcyI6IklnbmFuIiwic2Vuc2VzIjoicGFzc2l2ZSBQZXJjZXB0aW9uIDExIiwiZGV4IjoxMiwiY3IiOiIyICg0NTAgWFApIn0=' }) +-- get statistics +local metavars = marigold.get_metavars() +local query = marigold.decode_query(metavars.query_string or '') +local function query_retrieve(key, default) + local key = 'stat_'..key + if query[key] ~= '' then return query[key] + else return default + end +end +local stat = { + name = query_retrieve('name', 'Name'), + alignment = query_retrieve('alignment', 'neutral'), + size = query_retrieve('size', 'medium'), + type = query_retrieve('type', 'creature'), + + ac = query_retrieve('ac', '0'), + hp = query_retrieve('hp', '0'), + speed = query_retrieve('speed', '0 ft.'), + + str = query_retrieve('str', '10'), + dex = query_retrieve('dex', '10'), + con = query_retrieve('con', '10'), + int = query_retrieve('int', '10'), + wis = query_retrieve('wis', '10'), + cha = query_retrieve('cha', '10'), + + saving_throws = (query.stat_saving_throws ~= '' and query.stat_saving_throws) or '', + vulnerabilities = (query.stat_vulnerabilities ~= '' and query.stat_vulnerabilities) or '', + resistances = (query.stat_reistances ~= '' and query.stat_reistances) or '', + immunities = (query.stat_immunities ~= '' and query.stat_immunities) or '', + condition_immunities = (query.stat_condition_immunities ~= '' and query.stat_condition_immunities) or '', + languages = (query.stat_languages ~= '' and query.stat_languages) or '', + cr = (query.stat_cr ~= '' and query.stat_cr) or '', + + traits = (query.stat_traits ~= '' and query.stat_traits) or [[@New Trait +Add a named trait with an at sign. All lines following until the next at sign +will be part of the trait description! + +@Second Trait +This is a different trait. +]], + actions = (query.stat_actions ~= '' and query.stat_actions) or [[ +@New Action +Actions (and reactions and legendary actions) use the same syntax as traits. As well, +in all of them you can add *italics* and **bold** text! +]], + reactions = (query.stat_reactions ~= '' and query.stat_reactions) or '', + legendary_description = (query.stat_legendary_description ~= '' and query.stat_legendary_description) or '', + legendary = (query.stat_legendary ~= '' and query.stat_legendary) or '', +} + + + +-- stats yarrow query conversion +local function parse_traits(str) + local tbl = {} + for name, value in string.gmatch(str, '@([^@]-)\n([^@]+)') do + table.insert(tbl, {name=name, value=value}) + end + return tbl +end +local function convert(stats) + local tbl = {} + for k, v in pairs(stats) do + if v ~= '' then + tbl[k] = v + end + end + tbl.traits = parse_traits(stats.traits) + tbl.actions = parse_traits(stats.actions) + tbl.actions = parse_traits(stats.actions) + if stats.legendary ~= '' then + tbl.legendary = {} + tbl.legendary.description = stats.legendary_description + tbl.legendary.actions = parse_traits(stats.legendary) + end + return json.encode(tbl) +end + + +-- form helpers +local function text_input(label, name, value) + local tbl = {} + tbl['for'] = name + return h('div', { class='inputdiv', + h('label', label, tbl), + h('input', { type='text', id=name, name=name, value=value }), + }) +end +local function textarea(label, name, value, height) + local tbl = {} + tbl['for'] = name + return h('div', { class='inputdiv', + h('label', label, tbl), + h('textarea', value, { id=name, name=name, style=(height and 'height:'..height) or nil }), + }) +end + + +-- stats form +local form_basic = h('form', { style="width:100%; max-width: 30em; margin: auto; padding: 3em 0;", + text_input('Name', 'stat_name', stat.name), + text_input('Alignment', 'stat_alignment', stat.alignment), + text_input('Size', 'stat_size', stat.size), + text_input('Type', 'stat_type', stat.type), + h('br'), + + text_input('Armor Class', 'stat_ac', stat.ac), + text_input('Hit Points', 'stat_hp', stat.hp), + text_input('Speed', 'stat_speed', stat.speed), + h('br'), + + text_input('STR', 'stat_str', stat.str), + text_input('DEX', 'stat_dex', stat.dex), + text_input('CON', 'stat_con', stat.dex), + text_input('INT', 'stat_int', stat.int), + text_input('WIS', 'stat_wis', stat.wis), + text_input('CHA', 'stat_cha', stat.cha), + h('br'), + + text_input('Saving Throws', 'stat_saving_throws', stat.saving_throws), + text_input('Damage Vulnerabilities', 'stat_vulnerabilities', stat.vulnerabilities), + text_input('Damage Resistances', 'stat_resistances', stat.resistances), + text_input('Damage Immunities', 'stat_immunities', stat.immunities), + text_input('Condition Immunities', 'stat_condition_immunities', stat.condition_immunities), + text_input('Senses', 'stat_senses', stat.senses), + text_input('Languages', 'stat_languages', stat.languages), + text_input('Challenge', 'stat_cr', stat.cr), + h('br'), + + textarea('Additional Traits', 'stat_traits', stat.traits, '20em'), + h('br'), + textarea('Actions', 'stat_actions', stat.actions, '20em'), + h('br'), + textarea('Reactions', 'stat_reactions', stat.reactions, '20em'), + h('br'), + textarea('Legendary Action Text', 'stat_legendary_description', stat.legendary_description, '5em'), + textarea('Legendary Actions', 'stat_legendary', stat.legendary, '20em'), + h('br'), + h('div', { style='text-align:center', + h('button', 'Update', { type='submit' }), + }) +}) + + +local img = h('div', { style='text-align: center', + h('img', { style="max-width:500px", width='90%', src='yarrow.cgi?statistics='..b64.encode(convert(stat)) }), + h('br'), + h('a', 'image permalink', { id="permalink", href='yarrow.cgi?statistics='..b64.encode(convert(stat)) }), +}) local head = h('head', { @@ -16,7 +165,14 @@ local head = h('head', { h('link', { rel='stylesheet', href='style.css' }), }) + +local traits = parse_traits(stat.traits) +local traits_elements = {} +for _, t in ipairs(traits) do + table.insert(traits_elements, h('p', string.format('%s -> %s', t.name, t.value))) +end local body = h('body', { + form_basic, img, }) @@ -21,9 +21,12 @@ if not query.statistics then error('no statistics submitted!') end local statistics = json.decode(b64.decode(query.statistics)) +local theme = (query.theme and json.decode(b64.decode(query.theme))) or { + bg='white', text='black', flair='darkred', +} -- create & respond with image -local svg = draw.draw(statistics) +local svg = draw.draw(statistics, theme) print('Content-type: image/svg+xml\n') print(svg) |