From 6adb8ddd28f2b3370f001bfe8cd54656b5d097ce Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Fri, 10 Jul 2026 15:13:41 -0400 Subject: feat(admin): exit conditions in map gui match style of all other conditions --- internal/admin/static/admin.css | 33 +----- internal/admin/static/intereditor.js | 50 ++++++++- internal/admin/static/map.js | 201 ++++++++++------------------------- 3 files changed, 108 insertions(+), 176 deletions(-) (limited to 'internal') diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css index 896ea1a..5bf9b14 100644 --- a/internal/admin/static/admin.css +++ b/internal/admin/static/admin.css @@ -58,7 +58,6 @@ body{font-family:monospace;background:var(--bg);color:var(--text);display:flex;f .selection-box{fill:rgba(100,200,255,0.1);stroke:#5cf;stroke-width:1.5;stroke-dasharray:5,5;pointer-events:none} .selection-box.subtract{fill:rgba(255,80,80,0.1);stroke:#f55} .rm.multisel{cursor:pointer} -.bulk-swatch{background:#888!important} .notification{position:fixed;bottom:16px;left:16px;background:var(--panel);border:1px solid var(--border);padding:10px 16px;border-radius:4px;font-size:12px;z-index:100;animation:fadeIn .2s} .notification.error{border-color:var(--danger)} .notification.success{border-color:var(--success)} @@ -80,18 +79,11 @@ body{font-family:monospace;background:var(--bg);color:var(--text);display:flex;f .talk-node-del{background:var(--danger);color:#fff} .talk-node-body{display:none;padding:6px 0 2px 0} .talk-node.open>.talk-node-body{display:block} -.talk-props{padding:0 0 4px 0} -.talk-prop{margin:2px 0;font-size:11px;line-height:1.4} .talk-prop-label{color:#aaa;font-size:10px;text-transform:uppercase;letter-spacing:.3px;margin-right:4px} -.talk-prop-val{color:var(--text)} -.talk-prop-goto{color:#8cf;font-weight:bold} -.talk-prop-json{color:#888;font-size:10px;background:var(--input-bg);padding:1px 4px;border-radius:2px} .talk-seq-line{display:flex;gap:6px;margin:2px 0 2px 10px;font-size:11px;color:#bbb;align-items:center} .talk-seq-num{color:#666;min-width:14px;text-align:right;flex-shrink:0} -.talk-action-summary{color:#e8c47c;font-size:10px} .talk-options{margin-top:6px;padding-top:4px;padding-bottom:6px;border-top:1px solid rgba(255,255,255,.05)} .talk-options-label{font-size:10px;color:#888;text-transform:uppercase;letter-spacing:.3px} -.talk-option{display:flex;align-items:center;gap:5px;margin:3px 0;padding:3px 6px;font-size:11px;border-left:1px solid #444;border-radius:0 3px 3px 0} .talk-option-empty{color:#666;font-style:italic;border-left-color:transparent} .talk-opt-arrow{color:#555;flex-shrink:0} .talk-opt-text{color:var(--text);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} @@ -109,8 +101,6 @@ body{font-family:monospace;background:var(--bg);color:var(--text);display:flex;f .talk-section-remove{padding:1px 5px;font-size:9px;line-height:1.4;border:none;border-radius:2px;background:var(--danger);color:#fff;cursor:pointer;font-family:monospace} .talk-input{width:100%;padding:4px 6px;font-size:11px;font-family:monospace;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px} .talk-input:focus{border-color:#4a7aaa;outline:none} -.talk-textarea{width:100%;padding:4px 6px;font-size:11px;font-family:monospace;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;resize:vertical;min-height:32px} -.talk-textarea:focus{border-color:#4a7aaa;outline:none} .talk-input-sm{padding:2px 4px;font-size:10px} .talk-input-num{width:60px;padding:2px 4px;font-size:10px;font-family:monospace;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px} .talk-input-num:focus{border-color:#4a7aaa;outline:none} @@ -118,11 +108,6 @@ body{font-family:monospace;background:var(--bg);color:var(--text);display:flex;f .talk-select:focus{border-color:#4a7aaa;outline:none} .talk-check-label{font-size:10px;color:#bbb;display:inline-flex;align-items:center;gap:3px;cursor:pointer;margin-right:10px} .talk-check-label input[type=checkbox]{width:auto;margin:0;cursor:pointer} -.talk-inline-row{display:flex;gap:4px;align-items:center;margin-bottom:3px} -.talk-inline-row .talk-input{flex:1;min-width:0} -.talk-form-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:3px 8px} -.talk-form-grid .talk-field-row{display:flex;flex-direction:column;gap:1px} -.talk-form-grid .talk-field-row .talk-prop-label{font-size:9px} .talk-kv-section{margin-top:4px;padding:4px 6px;background:rgba(255,255,255,.02);border:1px solid var(--border);border-radius:4px} .talk-kv-header{display:flex;align-items:center;gap:6px;margin-bottom:3px} .talk-kv-header .talk-prop-label{font-size:9px;color:#aaa;text-transform:uppercase;letter-spacing:.3px} @@ -188,13 +173,7 @@ body{font-family:monospace;background:var(--bg);color:var(--text);display:flex;f .tab{padding:6px 14px;font-size:12px;background:transparent;color:#aaa;border:none;border-bottom:2px solid transparent;cursor:pointer;font-family:monospace} .tab:hover{color:var(--text)} .tab.active{color:var(--text);border-bottom-color:var(--accent)} -.kv-row{display:flex;gap:6px;align-items:center;margin-bottom:6px} -.kv-row input{flex:1;min-width:0} -.kv-row button{flex-shrink:0} .color-swatch{display:inline-block;width:16px;height:16px;border-radius:2px;border:1px solid #555;vertical-align:middle;margin-right:4px} -.color-picker{display:flex;flex-wrap:wrap;gap:2px;margin-top:4px;max-height:200px;overflow-y:auto} -.color-picker .swatch{width:18px;height:18px;border:1px solid transparent;cursor:pointer;border-radius:1px} -.color-picker .swatch:hover,.color-picker .swatch.selected{border-color:#fff;transform:scale(1.2);z-index:1} .editor-container{display:flex;flex:1;min-height:0} .editor-list{width:260px;background:var(--panel);border-right:1px solid var(--border);overflow-y:auto;padding:10px;flex-shrink:0} .editor-main{flex:1;overflow-y:auto;padding:16px} @@ -235,11 +214,10 @@ g.ud-hover text{font-size:11px} g.ud-hover:hover text{font-weight:bold} .cm-overlay{position:fixed;inset:0;z-index:199} .cm-menu{position:fixed;z-index:200;background:var(--panel);border:1px solid var(--border);border-radius:4px;padding:4px 0;min-width:140px;box-shadow:0 4px 16px rgba(0,0,0,.4)} -.cm-menu button{display:block;width:100%;padding:6px 12px;text-align:left;font-size:12px;font-family:monospace;background:none;border:none;color:var(--text);cursor:pointer} -.cm-menu button:hover{background:var(--accent)} +.cm-menu button:not(.btn){display:block;width:100%;padding:6px 12px;text-align:left;font-size:12px;font-family:monospace;background:none;border:none;color:var(--text);cursor:pointer} +.cm-menu button:not(.btn):hover{background:var(--accent)} .cm-menu button.danger{color:#f55} .cm-menu button.danger:hover{background:var(--danger)} -.cm-menu button.muted{color:#888} .cm-menu .cm-title{font-size:11px;color:#aaa;text-transform:uppercase;letter-spacing:.5px;padding:4px 12px 6px;border-bottom:1px solid var(--border);margin-bottom:4px} .cm-item-has-sub{position:relative} .cm-item-has-sub>button{display:block;width:100%;padding:6px 24px 6px 12px;text-align:left;font-size:12px;font-family:monospace;background:none;border:none;color:var(--text);cursor:pointer} @@ -273,8 +251,7 @@ g.ud-hover:hover text{font-weight:bold} .panel-tab:hover{color:var(--text)} .panel-tab.active{color:var(--text);border-bottom-color:var(--accent)} .panel-tab-content{padding:4px 0} -.cond-clause input,.cond-clause select{padding:4px 6px;font-size:11px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:2px;font-family:monospace} -.cond-clause select{min-width:120px}.exit-section{margin-top:12px;border-top:1px solid var(--border);padding-top:8px} +.exit-section{margin-top:12px;border-top:1px solid var(--border);padding-top:8px} .exit-section h3{font-size:11px;color:#aaa;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px} .exit-row{display:flex;gap:4px;align-items:center;margin-bottom:4px;padding:3px 4px;background:rgba(255,255,255,.02);border-radius:3px;font-size:11px} .exit-row .exit-dir{color:#6cf;font-weight:bold;min-width:28px;text-transform:uppercase} @@ -390,7 +367,6 @@ g.ud-hover:hover text{font-weight:bold} .ie-cond-root{padding:0} .ie-cond-modebar{display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap} .ie-cond-modesel{font-size:11px;padding:2px 4px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:3px;font-family:monospace} -.ie-cond-sm-label{font-size:10px;color:#888;text-transform:uppercase;letter-spacing:.5px} .ie-cond-notlbl{font-size:10px;color:var(--text);display:inline-flex;align-items:center;gap:2px;white-space:nowrap;cursor:pointer} .ie-cond-not{accent-color:var(--danger);margin:0} .ie-cond-children{display:flex;flex-direction:column;gap:4px} @@ -413,7 +389,6 @@ g.ud-hover:hover text{font-weight:bold} .ie-act-label{font-size:10px;color:#6af;text-transform:uppercase;letter-spacing:.3px;font-weight:bold;white-space:nowrap;min-width:90px} .ie-act-input{font-size:11px;padding:3px 6px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:3px;font-family:monospace} .ie-act-rm{flex-shrink:0} -.ie-act-addbar{display:flex;gap:3px;flex-wrap:wrap;margin-top:2px} .ie-act-kv{background:var(--bg);border:1px solid var(--border);border-radius:3px;padding:6px} .ie-act-kv-header{display:flex;align-items:center;gap:8px;margin-bottom:4px} .ie-kv-list{display:flex;flex-direction:column;gap:3px} @@ -435,9 +410,7 @@ g.ud-hover:hover text{font-weight:bold} .obj-sub-row.ie-inter-row .ie-inter-remove{white-space:nowrap;flex-shrink:0} .ie-inter-field-x{flex-shrink:0;margin-bottom:3px} .ie-inter-panel{background:rgba(0,0,0,.15);border-radius:4px;padding:6px} -.ie-inter-panel-row{display:flex;gap:6px;align-items:flex-start} .ie-inter-panel-body{flex:1;min-width:0} -.ie-inter-panel-x{margin-top:4px;flex-shrink:0} /* ── Step list: boxed steps with right-justified move/remove controls ── */ .ie-inter-steps{margin-top:4px} diff --git a/internal/admin/static/intereditor.js b/internal/admin/static/intereditor.js index a17225a..c4f16e7 100644 --- a/internal/admin/static/intereditor.js +++ b/internal/admin/static/intereditor.js @@ -326,10 +326,11 @@ function ie_collectLeaf(el) { // ── Render condition ── -function ie_renderCond(condObj, secId, idx, secPath, kind) { +function ie_renderCond(condObj, secId, idx, secPath, kind, opts) { kind = kind || 'entry'; var h = ''; var isGroup = condObj && (condObj.all_of || condObj.any_of); + var rootChildCount = isGroup ? (condObj.all_of || condObj.any_of || []).length : 0; if (condObj && typeof condObj === 'object') { var condTitleText = ''; if (kind && kind.indexOf('step-') === 0) { @@ -343,13 +344,13 @@ function ie_renderCond(condObj, secId, idx, secPath, kind) { h += ie_renderGroup(condObj, secId, secPath, idx, '', kind); h += ''; } - var showAddbar = !isGroup; + var showAddbar = !isGroup || (opts && opts.alwaysOuterAddbar && rootChildCount === 0); if (showAddbar) { h += '
'; IE_COND_TYPES.forEach(function(ct) { - h += ''; + h += ''; }); - h += ''; + h += ''; h += '
'; } return h; @@ -881,6 +882,47 @@ function ie_addGroup(secId, secPath, idx, kind, groupPath) { window._ieRenderOnly(); } +function ie_addOuterLeaf(secId, secPath, idx, kind, leafType) { + var ed = window._editorData; + if (!ed) return; + _ie_syncCond(ed, secId, secPath, idx, kind); + var cond = _ie_getCond(ed, secPath, idx, kind); + + var leaf = {}; + leaf[leafType] = (leafType === 'min_credits' || leafType === 'room') ? 0 : ''; + + var newCond; + if (!cond) { + newCond = leaf; + } else { + newCond = {all_of: [cond, leaf]}; + } + + _ie_setCond(ed, secPath, idx, kind, newCond); + ced_syncDOMToData(ed); + window._ieRenderOnly(); +} + +function ie_addOuterGroup(secId, secPath, idx, kind) { + var ed = window._editorData; + if (!ed) return; + _ie_syncCond(ed, secId, secPath, idx, kind); + var cond = _ie_getCond(ed, secPath, idx, kind); + + var newSub = {all_of: []}; + + var newCond; + if (!cond) { + newCond = newSub; + } else { + newCond = {all_of: [cond, newSub]}; + } + + _ie_setCond(ed, secPath, idx, kind, newCond); + ced_syncDOMToData(ed); + window._ieRenderOnly(); +} + function ie_toggleCondMode(secId, secPath, idx, kind, groupPath) { var ed = window._editorData; if (!ed) return; diff --git a/internal/admin/static/map.js b/internal/admin/static/map.js index 8bd0e2d..bd884b7 100644 --- a/internal/admin/static/map.js +++ b/internal/admin/static/map.js @@ -1558,6 +1558,33 @@ function renderExitsSection(r) { } var editingExitDir = null; +var _exitPrevEditorData = null; +var _exitPrevIERenderOnly = null; +var _exitPrevIERenderCurrent = null; + +function _renderExitCondCard(cond) { + var h = '
'; + h += '
Conditions (Blocked unless met)
'; + h += '
'; + h += '
'; + h += '
'; + h += '
'; + h += ie_renderCond(cond, 'exitCond', 0, 'exitConds', 'exit', { alwaysOuterAddbar: true }); + h += '
'; + h += '
'; + h += '
'; + return h; +} + +function _renderExitCond() { + var menu = document.querySelector('.exit-modal'); + if (!menu) return; + var cond = (window._editorData && window._editorData.exitConds && window._editorData.exitConds[0]) + ? window._editorData.exitConds[0].condition : null; + var card = menu.querySelector('.obj-card[data-card="exitCond"]'); + if (card) card.outerHTML = _renderExitCondCard(cond); + ced_setupSearchFields(menu); +} function editExitCondition(dir) { editingExitDir = dir; @@ -1570,7 +1597,13 @@ function editExitCondition(dir) { var hidden = exit.hidden || false; var alwaysBlocked = exit.always_blocked || false; - var parsed = parseCondition(cond); + _exitPrevEditorData = window._editorData; + _exitPrevIERenderOnly = window._ieRenderOnly; + _exitPrevIERenderCurrent = window._ieRenderCurrent; + + window._editorData = { exitConds: [ { condition: cond } ] }; + window._ieRenderCurrent = _renderExitCond; + window._ieRenderOnly = _renderExitCond; var overlay = document.createElement('div'); overlay.className = 'cm-overlay exit-overlay'; @@ -1578,132 +1611,46 @@ function editExitCondition(dir) { menu.className = 'cm-menu exit-modal'; menu.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);min-width:520px;max-width:90vw;max-height:85vh;overflow-y:auto;overflow-x:hidden;padding:14px;z-index:201'; - var h = '

Exit: ' + esc(dir) + ' → #' + exit.room + '

'; + var h = '
'; + h += '

Exit: ' + esc(dir) + ' → #' + exit.room + '

'; + h += ''; + h += '
'; h += '
'; h += '
'; h += '
'; h += '
'; - h += ''; - h += '
'; - h += ''; - h += '
'; - h += ''; - h += ''; - h += ''; - h += '
'; + h += _renderExitCondCard(cond); + h += '
Settings are automatically saved
'; + h += ''; menu.innerHTML = h; document.body.appendChild(overlay); document.body.appendChild(menu); - overlay.onclick = closeExitModal; - - menu._condClauses = parsed.clauses; - menu._condMode = parsed.mode; - renderConditionClauses(menu); -} - -function parseCondition(cond) { - var clauses = []; - if (!cond || Object.keys(cond).length === 0) return {clauses: clauses, mode: 'all'}; - var arr = cond.all_of; - if (!arr || !Array.isArray(arr) || arr.length === 0) arr = cond.any_of; - if (arr && Array.isArray(arr) && arr.length > 0) { - arr.forEach(function(c) { - var clause = extractClause(c); - if (clause) clauses.push(clause); - }); - return {clauses: clauses, mode: 'all'}; - } - var clause = extractClause(cond); - if (clause) clauses.push(clause); - return {clauses: clauses, mode: 'all'}; + overlay.onclick = saveExitCondition; + ced_setupSearchFields(menu); + bindExitInputSave(menu); } -function extractClause(c) { - var not = c.not === true; - if (c.player_flag && c.player_flag !== '') return {type: 'player_flag', value: c.player_flag, not: not}; - if (c.global_flag && c.global_flag !== '') return {type: 'global_flag', value: c.global_flag, not: not}; - if (c.has_item && c.has_item !== '') return {type: 'has_item', value: c.has_item, not: not}; - if (c.min_credits && c.min_credits !== 0) return {type: 'min_credits', value: String(c.min_credits), not: not}; - if (c.value && typeof c.value === 'object' && c.value.key) return {type: 'value', value: c.value.key + '=' + (c.value.value || ''), not: not}; - return null; -} - -function renderConditionClauses(menu) { - var container = menu.querySelector('#condClauses'); - if (!container) return; - var clauses = menu._condClauses || []; - var h = ''; - clauses.forEach(function(clause, i) { - h += '
'; - h += ''; - if (clause.type === 'value') { - var kv = clause.value ? clause.value.split('=', 2) : ['','']; - var kn = (kv[0] || '').trim(); - var kvv = (kv[1] || '').trim(); - h += ''; - h += '='; - h += ''; - } else { - h += ''; - } - h += ''; - h += ''; - h += '
'; - }); - container.innerHTML = h; -} - -function updateClauseData(el) { - var clauseEl = el.closest('.cond-clause'); - var menu = el.closest('.exit-modal'); - if (!clauseEl || !menu) return; - var idx = parseInt(clauseEl.getAttribute('data-idx')); - var clause = menu._condClauses[idx]; - if (!clause) return; - clause.type = clauseEl.querySelector('.cond-type').value; - clause.not = clauseEl.querySelector('.cond-not').checked; - if (clause.type === 'value') { - var k = clauseEl.querySelector('.cond-val-key'); - var v = clauseEl.querySelector('.cond-val-val'); - clause.value = (k ? k.value.trim() : '') + '=' + (v ? v.value.trim() : ''); - } else { - var val = clauseEl.querySelector('.cond-val'); - clause.value = val ? val.value : ''; - } -} - -function addConditionClause() { - var menu = document.querySelector('.exit-modal'); - if (!menu) return; - if (!menu._condClauses) menu._condClauses = []; - menu._condClauses.push({type: 'player_flag', value: '', not: false}); - renderConditionClauses(menu); -} - -function removeConditionClause(btn) { - var menu = btn.closest('.exit-modal'); - var clauseEl = btn.closest('.cond-clause'); - if (!menu || !clauseEl) return; - var idx = parseInt(clauseEl.getAttribute('data-idx')); - menu._condClauses.splice(idx, 1); - renderConditionClauses(menu); +function bindExitInputSave(menu) { + menu.addEventListener('input', collectExitCondition); + menu.addEventListener('change', collectExitCondition); } function closeExitModal() { editingExitDir = null; + if (_exitPrevEditorData !== undefined) { + window._editorData = _exitPrevEditorData; + window._ieRenderOnly = _exitPrevIERenderOnly; + window._ieRenderCurrent = _exitPrevIERenderCurrent; + } var ov = document.querySelector('.exit-overlay'); if (ov) ov.remove(); var m = document.querySelector('.exit-modal'); if (m) m.remove(); } -function saveExitCondition() { +function collectExitCondition() { if (!editingExitDir || !currentRoomData || !currentRoomData.room) return; var menu = document.querySelector('.exit-modal'); if (!menu) return; @@ -1724,47 +1671,17 @@ function saveExitCondition() { var alwaysBlockedEl = document.getElementById('exitAlwaysBlocked'); exit.always_blocked = alwaysBlockedEl ? alwaysBlockedEl.checked : false; - var clauseEls = menu.querySelectorAll('.cond-clause'); - clauseEls.forEach(function(el) { updateClauseData(el); }); - - var clauses = (menu._condClauses || []).filter(function(c) { return c.value && c.value !== '' && c.value !== '='; }); - var condition = null; - if (clauses.length === 1) { - condition = buildSingleCondition(clauses[0]); - } else if (clauses.length > 1) { - condition = {all_of: clauses.map(function(c) { return buildSingleCondition(c); })}; - } - exit.condition = condition; + var condRoot = menu.querySelector('.ie-cond-root'); + exit.condition = condRoot ? (ie_cleanCondition(ie_collectCond(condRoot)) || null) : null; r.exits = exits; autoSave(); - closeExitModal(); - reRenderPanel(); -} - -function buildSingleCondition(clause) { - var obj = {}; - if (clause.not) obj.not = true; - if (clause.type === 'value') { - var parts = (clause.value || '').split('=', 2); - obj.value = {key: (parts[0] || '').trim(), value: (parts[1] || '').trim()}; - } else { - obj[clause.type] = clause.value; - } - return obj; } -function clearExitCondition() { - var menu = document.querySelector('.exit-modal'); - if (!menu) return; - menu._condClauses = []; - renderConditionClauses(menu); - var bmsg = document.getElementById('exitBmsg'); - if (bmsg) bmsg.value = ''; - var hx = document.getElementById('exitHidden'); - if (hx) hx.checked = false; - var ab = document.getElementById('exitAlwaysBlocked'); - if (ab) ab.checked = false; +function saveExitCondition() { + collectExitCondition(); + closeExitModal(); + reRenderPanel(); } function parseFlagInput(val) { -- cgit v1.2.3