summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2023-09-18 05:11:05 +0000
committersanine <sanine.not@pm.me>2023-09-18 05:11:05 +0000
commit3425c3c435a000b14103a0055fd913ccb855b734 (patch)
tree2efbb0599104d3794f5c0604d71ac16bf97368d2
parentbfcc2e759807a692d8e0353e3bbecd109175032b (diff)
implement basic ui
-rw-r--r--draw.lua2
-rw-r--r--style.css51
-rwxr-xr-xyarrow-ui.cgi158
-rwxr-xr-xyarrow.cgi5
4 files changed, 213 insertions, 3 deletions
diff --git a/draw.lua b/draw.lua
index 5d39cc5..11b4b77 100644
--- a/draw.lua
+++ b/draw.lua
@@ -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{
diff --git a/style.css b/style.css
index 90f985a..0296148 100644
--- a/style.css
+++ b/style.css
@@ -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,
})
diff --git a/yarrow.cgi b/yarrow.cgi
index 7543f42..14dba58 100755
--- a/yarrow.cgi
+++ b/yarrow.cgi
@@ -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)