From f601a40776843f6196221e74ba502c571a5b1bcd Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Sun, 12 Jul 2026 21:16:58 -0400 Subject: fix(admin): standardize most search dropdowns and radio buttons --- internal/admin/api_stations.go | 37 ++++++++++++++++++++++++++ internal/admin/server.go | 1 + internal/admin/static/admin.css | 29 +++++++++++++++----- internal/admin/static/cardeditor.js | 48 ++++++++++++++++++++++++++++++++-- internal/admin/static/hazardeditor.js | 2 +- internal/admin/static/itemeditor.js | 23 +++++++++++++--- internal/admin/static/mobeditor.js | 38 +++++++++++++++++++++------ internal/admin/static/moduleeditor.js | 8 +++--- internal/admin/static/talktree.js | 8 +++--- internal/admin/static/techeditor.js | 4 +-- internal/admin/templates/drops.html | 2 +- internal/admin/templates/files.html | 2 +- internal/admin/templates/hazards.html | 2 +- internal/admin/templates/items.html | 2 +- internal/admin/templates/mobs.html | 2 +- internal/admin/templates/modules.html | 2 +- internal/admin/templates/objects.html | 2 +- internal/admin/templates/players.html | 2 +- internal/admin/templates/techs.html | 2 +- internal/admin/templates/triggers.html | 2 +- 20 files changed, 179 insertions(+), 39 deletions(-) create mode 100644 internal/admin/api_stations.go (limited to 'internal') diff --git a/internal/admin/api_stations.go b/internal/admin/api_stations.go new file mode 100644 index 0000000..411ed96 --- /dev/null +++ b/internal/admin/api_stations.go @@ -0,0 +1,37 @@ +package admin + +import ( + "net/http" +) + +func (s *AdminServer) handleStations(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + items, err := s.itemStore.LoadAll() + if err != nil { + http.Error(w, "failed to load items", http.StatusInternalServerError) + return + } + + seen := map[string]bool{} + var stations []string + for _, def := range items { + if def.Craft != nil { + for _, c := range def.Craft { + for _, s := range c.Station { + if s != "" { + if !seen[s] { + seen[s] = true + stations = append(stations, s) + } + } + } + } + } + } + + writeJSON(w, stations) +} diff --git a/internal/admin/server.go b/internal/admin/server.go index 11efb5c..cc63cee 100644 --- a/internal/admin/server.go +++ b/internal/admin/server.go @@ -171,6 +171,7 @@ func NewServer(cfg *config.Config, useTLS bool, accountStore *player.AccountStor apiMux.HandleFunc("/api/dashboard", s.handleDashboard) apiMux.HandleFunc("/api/global_flags", s.handleGlobalFlags) apiMux.HandleFunc("/api/tools", s.handleTools) + apiMux.HandleFunc("/api/stations", s.handleStations) apiMux.HandleFunc("/api/search", s.handleSearch) apiMux.HandleFunc("/api/undo/state", s.handleUndoState) apiMux.HandleFunc("/api/undo/undo", s.doUndo) diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css index c500aad..a1a364e 100644 --- a/internal/admin/static/admin.css +++ b/internal/admin/static/admin.css @@ -38,7 +38,8 @@ body{font-family:monospace;background:var(--bg);color:var(--text);display:flex;f .table a{color:var(--text);text-decoration:none} .table a:hover{color:#fff} .search-bar{margin-bottom:12px} -.search-bar input{width:100%;padding:6px 8px;font-size:13px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px} +.search-bar input{width:100%;padding:6px 28px 6px 8px;font-size:13px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center} +input.search-input{padding-right:24px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 6px center} .login{max-width:360px;margin:80px auto;background:var(--panel);padding:30px;border:1px solid var(--border);border-radius:6px} .login h1{font-size:18px;margin-bottom:20px;text-align:center} .z-controls-wrapper{position:absolute;top:12px;left:12px;z-index:10} @@ -278,7 +279,7 @@ g.ud-hover:hover text{font-weight:bold} .panel-tab-content .entry-fields textarea{resize:vertical;min-height:28px} .panel-tab-content .entry-fields .entry-id{font-size:11px;color:#6cf;display:block;padding:3px 0} .panel-tab-content .section-search{position:relative;margin-top:4px} -.panel-tab-content .search-input{width:100%;padding:4px 6px;font-size:11px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;font-family:monospace} +.panel-tab-content .search-input{width:100%;padding:4px 24px 4px 6px;font-size:11px;background-color:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;font-family:monospace} .panel-tab-content .search-results{position:absolute;top:100%;left:0;right:0;max-height:160px;overflow-y:auto;background:var(--panel);border:1px solid var(--border);border-radius:3px;z-index:50;box-shadow:0 4px 12px rgba(0,0,0,.4)} .panel-tab-content .search-results .sr-item{padding:4px 8px;font-size:11px;cursor:pointer;color:var(--text);border-bottom:1px solid rgba(255,255,255,.03)} .panel-tab-content .search-results .sr-item:hover,.panel-tab-content .search-results .sr-item.active{background:var(--accent)} @@ -300,7 +301,7 @@ g.ud-hover:hover text{font-weight:bold} .obj-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:6px 8px} .obj-field{display:flex;flex-direction:column;gap:2px;font-size:11px} .obj-field span{color:#888;font-size:9px;text-transform:uppercase;letter-spacing:.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} -.obj-field input,.obj-field textarea,.obj-field select{padding:4px 6px;font-size:11px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;font-family:monospace;width:100%} +.obj-field input,.obj-field textarea,.obj-field select{padding:4px 6px;font-size:11px;background-color:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;font-family:monospace;width:100%} .obj-field textarea{resize:vertical;min-height:28px} .obj-field.obj-wide{grid-column:1/-1} .obj-field.obj-check{flex-direction:row;align-items:center;gap:4px;padding-bottom:3px} @@ -313,6 +314,15 @@ g.ud-hover:hover text{font-weight:bold} .seg-toggle input:checked+span{color:#3a8fd4} .seg-toggle label:has(input:checked){color:var(--text);background:var(--accent)} .seg-toggle input{position:absolute;opacity:0;pointer-events:none;width:0;height:0} +.seg-pill{position:relative;border-radius:6px;background:var(--input-bg);display:inline-flex;border:1px solid var(--border)} +.seg-pill .seg-opt{flex:1;text-align:center;position:relative;z-index:1;padding:5px 14px;font-size:11px;cursor:pointer;color:#888;background:transparent;border:none;font-family:monospace;transition:color .15s} +.seg-pill .seg-opt:hover{color:var(--text)} +.seg-pill .seg-opt.seg-active{color:var(--text)} +.seg-pill::before{content:'';position:absolute;top:2px;bottom:2px;left:0;width:calc(100% / 3);background:var(--accent);border-radius:4px;transition:transform .2s ease;z-index:0} +.seg-pill.seg-pill-3.seg-idx-0::before{transform:translateX(0)} +.seg-pill.seg-pill-3.seg-idx-1::before{transform:translateX(100%)} +.seg-pill.seg-pill-3.seg-idx-2::before{transform:translateX(200%)} +.seg-pill .seg-opt:focus-visible{outline:2px solid var(--accent);outline-offset:-1px;border-radius:3px;z-index:2} .obj-color-row{display:flex;align-items:center;gap:4px} .obj-color-row .color-swatch{flex-shrink:0} .obj-color-row input{width:38px!important;text-align:center;padding:4px 2px} @@ -335,16 +345,21 @@ g.ud-hover:hover text{font-weight:bold} .add-section-bar button{padding:4px 12px;font-size:11px} .obj-kv-list{margin-top:4px} .obj-kv-row{display:flex;gap:4px;align-items:center;margin-bottom:3px} -.obj-kv-row input{padding:3px 5px;font-size:10px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:2px;font-family:monospace;flex:1} +.obj-kv-row input{padding:3px 5px;font-size:10px;background-color:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:2px;font-family:monospace;flex:1} .obj-kv-row .obj-kv-key{flex:1} +.obj-kv-key-wrap.obj-search{position:relative!important} +.obj-kv-key-wrap.obj-search .search-input.obj-kv-key{width:100%;flex:none;padding-right:24px} +.obj-kv-key-wrap.obj-search .search-results{position:absolute;top:100%;left:0;right:0;max-height:160px;overflow-y:auto;background:var(--panel);border:1px solid var(--border);border-radius:3px;z-index:50;box-shadow:0 4px 12px rgba(0,0,0,.4);margin-top:2px} +.obj-kv-key-wrap.obj-search .search-results .sr-item{padding:4px 8px;font-size:11px;cursor:pointer;color:var(--text);border-bottom:1px solid rgba(255,255,255,.03)} +.obj-kv-key-wrap.obj-search .search-results .sr-item:hover,.obj-kv-key-wrap.obj-search .search-results .sr-item.active{background:var(--accent)} .obj-kv-row .obj-kv-val{width:70px;flex:none} .obj-core-row{display:flex;gap:6px;align-items:flex-end;margin-bottom:8px} -.obj-field-error input,.obj-field-error textarea,.obj-field-error select{border-color:var(--danger)!important;background:rgba(192,57,43,.08)} +.obj-field-error input,.obj-field-error textarea,.obj-field-error select{border-color:var(--danger)!important;background-color:rgba(192,57,43,.08)} .obj-search{position:relative!important} .obj-search .search-results{position:absolute;top:100%;left:0;right:0;max-height:160px;overflow-y:auto;background:var(--panel);border:1px solid var(--border);border-radius:3px;z-index:50;box-shadow:0 4px 12px rgba(0,0,0,.4);margin-top:2px} .obj-search .search-results .sr-item{padding:4px 8px;font-size:11px;cursor:pointer;color:var(--text);border-bottom:1px solid rgba(255,255,255,.03)} .obj-search .search-results .sr-item:hover,.obj-search .search-results .sr-item.active{background:var(--accent)} -.obj-search input.search-input{padding-right:24px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 6px center} +.obj-search input.search-input{padding-right:24px} .obj-search input.search-input::placeholder{color:#666} .obj-narrow.obj-search input{width:80px} .obj-sub-row .obj-search input.search-input{min-width:80px} @@ -475,3 +490,5 @@ g.ud-hover:hover text{font-weight:bold} .obj-card[data-card="trigger_action"]>.obj-card-body>.obj-sub-add{display:none} .obj-card[data-card="trigger_action"] .ie-inter-header .ie-trig-up, .obj-card[data-card="trigger_action"] .ie-inter-header .ie-trig-dn{display:none} +input:focus-visible,select:focus-visible,textarea:focus-visible,button:focus-visible{outline:2px solid rgba(100,160,255,.5);outline-offset:1px} +.sr-no-results{padding:6px 8px;font-size:11px;color:#666;text-align:center;cursor:default;pointer-events:none} diff --git a/internal/admin/static/cardeditor.js b/internal/admin/static/cardeditor.js index 1e8d259..1b2b76b 100644 --- a/internal/admin/static/cardeditor.js +++ b/internal/admin/static/cardeditor.js @@ -90,6 +90,46 @@ function ced_addKVRow(cardID, subKey) { el.appendChild(row); } +function ced_buildDynamicSelect(fid, dpAttr, val, dynKey) { + var items = (window._dynOpts && window._dynOpts[dynKey]) ? window._dynOpts[dynKey].slice().sort() : []; + var s = ''; + return s; +} + +function ced_renderKVSearch(sec, il, data, getter) { + var sectionRoot = data[ced_cardPath(sec)] || data; + var map = getter ? getter(sectionRoot, il) : (sectionRoot[il.key] || {}); + var entity = il.searchEntity || 'items'; + var h = '
'; + h += '' + esc(il.label) + ''; + h += '
'; + Object.keys(map).forEach(function(k, idx) { + h += '
'; + h += ''; + h += ''; + h += '
'; + }); + h += '
'; + h += ''; + h += '
'; + return h; +} + +function ced_addKVSearchRow(cardID, subKey, entity) { + var el = document.querySelector('.obj-kv-list[data-sec="' + cardID + '"][data-key="' + subKey + '"]'); + if (!el) return; + var row = document.createElement('div'); + row.className = 'obj-kv-row'; + row.innerHTML = ''; + el.appendChild(row); + ced_setupSearchFields(row); +} + // ---- Search autocomplete ---- function ced_setupSearchFields(root) { @@ -114,7 +154,7 @@ function ced_setupSearchFields(root) { if (entityType === 'drops' && data.drops) items = data.drops; if (items.length === 0) { results.style.display = 'block'; - results.innerHTML = '
No results
'; + results.innerHTML = '
No results
'; return; } results.style.display = 'block'; @@ -315,6 +355,10 @@ function ced_renderField(sec, f, data, idx, subPath, optStyle, cardPathFn, field return wrap('obj-field', '' + esc(label) + ''); } if (type === 'select') { + if (hint && hint.indexOf('dyn:') === 0) { + var inner = '' + esc(label) + '' + ced_buildDynamicSelect(fid, dpAttr, val, hint.substring(4)); + return wrap('obj-field', inner); + } var opts = hint ? hint.split('|') : []; var inner = '' + esc(label) + ' ' + esc(f[1]) + ''; } if (f[2] === 'select') { - var opts = f[3] ? f[3].split('|') : []; + var hint = f[3] || ''; + if (hint.indexOf('dyn:') === 0) { + return ''; + } + var opts = hint.split('|'); var sel = ''; + h += '
'; + meleeOpts.forEach(function(t, i) { + h += ''; }); h += '
'; h += '
'; @@ -337,12 +340,31 @@ function renderMobAttackTypeWidget(data) { return h; } +function mobAtkPillSelect(val, btn) { + var pill = document.getElementById('mob_atktype_pill'); + if (!pill) return; + var opts = pill.querySelectorAll('.seg-opt'); + var idx = -1; + opts.forEach(function(o, i) { + if (o.getAttribute('data-v') === val) { + o.classList.add('seg-active'); + idx = i; + } else { + o.classList.remove('seg-active'); + } + }); + pill.className = pill.className.replace(/seg-idx-\d/g, 'seg-idx-' + idx); +} + // collectMobAttackType reads the Atk Type widget into an array: the selected // melee type first, then any optional ranged/science types. function collectMobAttackType() { var melee = 'crush'; - var r = document.querySelector('input[name="mob_atktype_melee"]:checked'); - if (r) melee = r.value; + var pill = document.getElementById('mob_atktype_pill'); + if (pill) { + var active = pill.querySelector('.seg-opt.seg-active'); + if (active) melee = active.getAttribute('data-v'); + } var out = [melee]; var rng = document.getElementById('mob_atktype_ranged'); var sci = document.getElementById('mob_atktype_science'); @@ -852,7 +874,7 @@ function saveMob() { hasValues = ced_saveSectionFields(sec, sectionObj, cardPath, fieldID) || hasValues; } - if (sec.id === 'combat' && document.querySelector('input[name="mob_atktype_melee"]')) { + if (sec.id === 'combat' && document.getElementById('mob_atktype_pill')) { sectionObj['attack_types'] = collectMobAttackType(); hasValues = true; } diff --git a/internal/admin/static/moduleeditor.js b/internal/admin/static/moduleeditor.js index 739fc2e..fe71f88 100644 --- a/internal/admin/static/moduleeditor.js +++ b/internal/admin/static/moduleeditor.js @@ -13,11 +13,11 @@ var MODULE_SECTIONS = [ ['max_hit', 'Max Hit', 'number', '10', 'narrow'], ['base_xp', 'Base XP', 'number', '50', 'narrow'], ['category', 'Category', 'select', 'combat|utility|enchant|processing|transport'], - ['element', 'Element', 'text', 'e.g. fire'], + ['element', 'Element', 'select', '|bio|eco|hydro|solar|chaos'], ['destination', 'Destination', 'number', '5', 'narrow'], ], inline: [ - {label:'Junk Cost', key:'junk_cost', type:'kv', valType:'number'}, + {label:'Junk Cost', key:'junk_cost', type:'kv_search', searchEntity:'items', valType:'number'}, ] }, { @@ -144,6 +144,8 @@ function renderCard(sec, data) { sec.inline.forEach(function(il) { if (il.type === 'kv') { h += ced_renderKV(sec, il, data); + } else if (il.type === 'kv_search') { + h += ced_renderKVSearch(sec, il, data); } }); } @@ -274,7 +276,7 @@ function saveModule() { if (sec.inline) { sec.inline.forEach(function(il) { - if (il.type === 'kv') { + if (il.type === 'kv' || il.type === 'kv_search') { var kvEl = document.querySelector('.obj-kv-list[data-sec="' + sec.id + '"][data-key="' + il.key + '"]'); if (kvEl) { var map = {}; diff --git a/internal/admin/static/talktree.js b/internal/admin/static/talktree.js index b8c7106..e55653e 100644 --- a/internal/admin/static/talktree.js +++ b/internal/admin/static/talktree.js @@ -207,7 +207,7 @@ else if (type === 'number') { var n = parseFloat(el.value); val = isNaN(n) ? 0 : n; } else { val = el.value; } talkSyncPath(path, val); - if (path.endsWith('.goto')) renderTalkTree(window._talkData); + if (path.endsWith('.goto') && e.type === 'change') renderTalkTree(window._talkData); } else if (tag === 'SELECT') { talkSyncPath(path, el.value); } @@ -492,7 +492,7 @@ label = label || 'Auto-advance to'; return '
' + '' + esc(label) + '' + - '' + + '' + '
'; } @@ -530,7 +530,7 @@ html += '
'; html += '' + (idx + 1) + ''; html += ''; - html += ''; + html += ''; html += '
'; if (opt.condition && typeof opt.condition === 'object' && !Array.isArray(opt.condition)) { @@ -582,7 +582,7 @@ html += '
' + '' + (options.length + 1) + '' + '' + - '' + + '' + '' + '' + '
'; diff --git a/internal/admin/static/techeditor.js b/internal/admin/static/techeditor.js index c9d7c24..812cded 100644 --- a/internal/admin/static/techeditor.js +++ b/internal/admin/static/techeditor.js @@ -10,8 +10,8 @@ var TECH_SECTIONS = [ ['name', 'Name', 'text', 'Protect from Melee'], ['level', 'Level', 'number', '37', 'narrow'], ['drain_rate', 'Drain Rate', 'number', '0.5', 'narrow'], - ['category', 'Category', 'text', 'protection'], - ['group', 'Group', 'text', 'melee'], + ['category', 'Category', 'select', '|accuracy|combo|defense|protection|ranged|science|strength|utility'], + ['group', 'Group', 'select', '|accuracy_tier|defense_tier|protection|ranged_tier|regen_tier|science_tier|strength_tier'], ] }, { diff --git a/internal/admin/templates/drops.html b/internal/admin/templates/drops.html index fb67c86..f270fe8 100644 --- a/internal/admin/templates/drops.html +++ b/internal/admin/templates/drops.html @@ -1,7 +1,7 @@ {{define "body-drops"}}
- +
diff --git a/internal/admin/templates/files.html b/internal/admin/templates/files.html index 5a5106f..c9e0003 100644 --- a/internal/admin/templates/files.html +++ b/internal/admin/templates/files.html @@ -1,7 +1,7 @@ {{define "body-files"}}
- +
diff --git a/internal/admin/templates/hazards.html b/internal/admin/templates/hazards.html index d7d4548..102165a 100644 --- a/internal/admin/templates/hazards.html +++ b/internal/admin/templates/hazards.html @@ -1,7 +1,7 @@ {{define "body-hazards"}}
- +
diff --git a/internal/admin/templates/items.html b/internal/admin/templates/items.html index 7efeded..f1c8ee5 100644 --- a/internal/admin/templates/items.html +++ b/internal/admin/templates/items.html @@ -1,7 +1,7 @@ {{define "body-items"}}
- +
diff --git a/internal/admin/templates/mobs.html b/internal/admin/templates/mobs.html index f53b3e3..14ac360 100644 --- a/internal/admin/templates/mobs.html +++ b/internal/admin/templates/mobs.html @@ -1,7 +1,7 @@ {{define "body-mobs"}}
- +
diff --git a/internal/admin/templates/modules.html b/internal/admin/templates/modules.html index e46af1b..09c75a9 100644 --- a/internal/admin/templates/modules.html +++ b/internal/admin/templates/modules.html @@ -1,7 +1,7 @@ {{define "body-modules"}}
- +
diff --git a/internal/admin/templates/objects.html b/internal/admin/templates/objects.html index 9be1b66..3062c70 100644 --- a/internal/admin/templates/objects.html +++ b/internal/admin/templates/objects.html @@ -1,7 +1,7 @@ {{define "body-objects"}}
- +
diff --git a/internal/admin/templates/players.html b/internal/admin/templates/players.html index 43627d8..22ffc47 100644 --- a/internal/admin/templates/players.html +++ b/internal/admin/templates/players.html @@ -1,7 +1,7 @@ {{define "body-players"}}

Players

- + diff --git a/internal/admin/templates/techs.html b/internal/admin/templates/techs.html index 813d57b..b4b678a 100644 --- a/internal/admin/templates/techs.html +++ b/internal/admin/templates/techs.html @@ -1,7 +1,7 @@ {{define "body-techs"}}
- +
diff --git a/internal/admin/templates/triggers.html b/internal/admin/templates/triggers.html index 3b32765..d790427 100644 --- a/internal/admin/templates/triggers.html +++ b/internal/admin/templates/triggers.html @@ -1,7 +1,7 @@ {{define "body-triggers"}}
- +
-- cgit v1.2.3
NameLevelRoomAccount