aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/static/map.js
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-10 00:43:29 -0400
committerhistoria <[not public]>2026-07-10 00:43:29 -0400
commit6e7c0b3ec4253bb58a815979b1f4574d08cb1f53 (patch)
treedbff1952bdbf14cd58a6128775e263b53fc64ec1 /internal/admin/static/map.js
parent9836513d287396fabcba85956366e6410d7363ef (diff)
downloadthehouseoficarus-6e7c0b3ec4253bb58a815979b1f4574d08cb1f53.tar.gz
feat(admin): add triggers to rooms, add global triggers
Diffstat (limited to 'internal/admin/static/map.js')
-rw-r--r--internal/admin/static/map.js184
1 files changed, 57 insertions, 127 deletions
diff --git a/internal/admin/static/map.js b/internal/admin/static/map.js
index ab20a86..7c47b4e 100644
--- a/internal/admin/static/map.js
+++ b/internal/admin/static/map.js
@@ -26,6 +26,7 @@ var oppositeDir = {
up:'down', down:'up'
};
var saveTimer = null;
+var _triggersDirty = false;
var upTarget = {}, downTarget = {};
var _mapMouseCleanup = false;
@@ -530,6 +531,7 @@ function updateView() {
function selectRoom(id) {
if (saveTimer) { clearTimeout(saveTimer); saveTimer = null; }
+ _triggersDirty = false;
var prev = selectedRoom;
selectedRoom = id;
multiSelectedIds.clear();
@@ -575,8 +577,7 @@ function renderSidePanel(data) {
html += '<button class="panel-tab' + (currentPanelTab === 5 ? ' active' : '') + '" onclick="switchPanelTab(5)">Items</button>';
html += '<button class="panel-tab' + (currentPanelTab === 6 ? ' active' : '') + '" onclick="switchPanelTab(6)">Hazard</button>';
html += '<button class="panel-tab' + (currentPanelTab === 7 ? ' active' : '') + '" onclick="switchPanelTab(7)">Agility</button>';
- html += '<button class="panel-tab' + (currentPanelTab === 8 ? ' active' : '') + '" onclick="switchPanelTab(8)">Enter/Exit</button>';
- html += '<button class="panel-tab' + (currentPanelTab === 9 ? ' active' : '') + '" onclick="switchPanelTab(9)">Triggers</button>';
+ html += '<button class="panel-tab' + (currentPanelTab === 8 ? ' active' : '') + '" onclick="switchPanelTab(8)">Triggers</button>';
html += '</div>';
html += '<div class="panel-tab-content">';
@@ -598,11 +599,14 @@ function renderSidePanel(data) {
}
}
else if (currentPanelTab === 8) html += renderEnterExitTab(r);
- else if (currentPanelTab === 9) html += renderTriggersTab(r);
html += '</div>';
panel.innerHTML = html;
setTimeout(function() {
+ if (currentPanelTab >= 8) {
+ var content = panel.querySelector('.panel-tab-content');
+ if (content) { bindTriggerInputSave(content); ced_setupSearchFields(content); }
+ }
var cf = document.querySelector('#panelContent .color-field');
if (cf) {
bindColorField(cf);
@@ -1406,9 +1410,9 @@ function switchPanelTab(idx) {
}
}
else if (idx === 8) html = renderEnterExitTab(r);
- else if (idx === 9) html = renderTriggersTab(r);
var content = document.querySelector('.panel-tab-content');
if (content) content.innerHTML = html;
+ if (idx >= 8) { setTimeout(function() { bindTriggerInputSave(content); ced_setupSearchFields(content); }, 10); }
var tabs = document.querySelectorAll('.panel-tab');
tabs.forEach(function(t, i) { t.classList.toggle('active', i === idx); });
if (idx === 0) {
@@ -2076,6 +2080,43 @@ function autoSave() {
saveTimer = setTimeout(function() { doSavePanel(); }, 600);
}
+function bindTriggerInputSave(container) {
+ var inputs = container.querySelectorAll('input:not([type="checkbox"]):not([type="radio"]), textarea');
+ inputs.forEach(function(el) {
+ el.removeEventListener('input', _triggerInputHandler);
+ el.addEventListener('input', _triggerInputHandler);
+ });
+ var checks = container.querySelectorAll('input[type="checkbox"], input[type="radio"]');
+ checks.forEach(function(el) {
+ el.removeEventListener('change', _triggerInputHandler);
+ el.addEventListener('change', _triggerInputHandler);
+ });
+}
+
+function _syncTriggersToData() {
+ if (!window._editorData || !window._panelTriggerSecs) return;
+ ced_syncDOMToData(window._editorData);
+ if (typeof ie_syncAllInteractions === 'function') {
+ ie_syncAllInteractions(window._editorData, window._panelTriggerSecs);
+ }
+ if (currentRoomData && currentRoomData.room) {
+ window._panelTriggerSecs.forEach(function(sec) {
+ currentRoomData.room[sec.path] = window._editorData[sec.path];
+ });
+ }
+}
+
+function _triggerInputHandler() {
+ _syncTriggersToData();
+ _triggersDirty = true;
+}
+
+function saveTriggers() {
+ _syncTriggersToData();
+ _triggersDirty = false;
+ saveNow();
+}
+
function saveNow() {
if (window._colorPickerActive) return;
if (saveTimer) clearTimeout(saveTimer);
@@ -2104,8 +2145,6 @@ async function doSavePanel() {
r.hazard = currentRoomData.room.hazard || '';
r.on_enter = currentRoomData.room.on_enter;
r.on_exit = currentRoomData.room.on_exit;
- r.on_flag_change = currentRoomData.room.on_flag_change;
- r.on_global_flag_change = currentRoomData.room.on_global_flag_change;
if (r.block_transport === undefined) r.block_transport = currentRoomData.room.block_transport;
}
@@ -2126,6 +2165,7 @@ async function doSavePanel() {
await API.put('/api/rooms/' + id, r);
notify('Room #' + id + ' saved', 'success');
+ _triggersDirty = false;
updateUndoBar();
await loadMap();
selectRoom(id);
@@ -3382,51 +3422,26 @@ function renderEnterExitTab(r) {
currentRoomData.room.on_exit = window._editorData.on_exit;
}
switchPanelTab(8);
- autoSave();
+ _triggersDirty = true;
};
window._ieRenderOnly = window._ieRenderCurrent;
window._ieSyncAll = ie_syncAllInteractions;
+ window._panelTriggerSecs = [
+ {id: 'on_enter', path: 'on_enter', isArray: true, interactionStyle: true, itemIDAllowed: false, label: 'Trigger'},
+ {id: 'on_exit', path: 'on_exit', isArray: true, interactionStyle: true, itemIDAllowed: false, label: 'Trigger'}
+ ];
- var h = '<h3 style="margin:8px 0 4px">On-Enter Triggers</h3>';
+ var h = '<div style="margin:8px 0"><button class="btn btn-primary" onclick="saveTriggers()">Save Triggers</button></div>';
+ h += '<h3 style="margin:8px 0 4px">On-Enter Triggers</h3>';
h += '<p style="color:#888;font-size:11px;margin:0 0 6px">Fires when a player enters this room. First matching entry wins.</p>';
h += renderTriggerListHTML(r.on_enter, 'on_enter', 'on_enter', false);
- h += '<h3 style="margin:16px 0 4px">On-Exit Triggers</h3>';
+ h += '<hr style="border:none;border-top:1px solid var(--border);margin:12px 0">';
+ h += '<h3 style="margin:8px 0 4px">On-Exit Triggers</h3>';
h += '<p style="color:#888;font-size:11px;margin:0 0 6px">Fires when a player leaves this room. First matching entry wins.</p>';
h += renderTriggerListHTML(r.on_exit, 'on_exit', 'on_exit', false);
return h;
}
-// ── Triggers tab (on_flag_change / on_global_flag_change) ──
-
-function renderTriggersTab(r) {
- var fc = Array.isArray(r.on_flag_change) ? r.on_flag_change : [];
- var gfc = Array.isArray(r.on_global_flag_change) ? r.on_global_flag_change : [];
- window._editorData = { on_flag_change: fc, on_global_flag_change: gfc };
-
- window._ieRenderCurrent = function() {
- if (window._ieSyncAll) window._ieSyncAll(window._editorData, [
- {id: 'on_flag_change', path: 'on_flag_change', isArray: true, interactionStyle: true, itemIDAllowed: false, label: 'Trigger'},
- {id: 'on_global_flag_change', path: 'on_global_flag_change', isArray: true, interactionStyle: true, itemIDAllowed: false, label: 'Trigger'}
- ]);
- if (currentRoomData && currentRoomData.room) {
- currentRoomData.room.on_flag_change = window._editorData.on_flag_change;
- currentRoomData.room.on_global_flag_change = window._editorData.on_global_flag_change;
- }
- switchPanelTab(9);
- autoSave();
- };
- window._ieRenderOnly = window._ieRenderCurrent;
- window._ieSyncAll = ie_syncAllInteractions;
-
- var h = '<h3 style="margin:8px 0 4px">On-Flag-Change Triggers</h3>';
- h += '<p style="color:#888;font-size:11px;margin:0 0 6px">Fires when a player flag changes. Use the condition\'s "In Room" gate to scope to this room (delete it to make global).</p>';
- h += renderFlagTriggerListHTML(r.on_flag_change, 'on_flag_change', 'on_flag_change', 'player');
- h += '<h3 style="margin:16px 0 4px">On-Global-Flag-Change Triggers</h3>';
- h += '<p style="color:#888;font-size:11px;margin:0 0 6px">Fires when a global flag changes (no player involved).</p>';
- h += renderFlagTriggerListHTML(r.on_global_flag_change, 'on_global_flag_change', 'on_global_flag_change', 'global');
- return h;
-}
-
// renderTriggerListHTML emits the HTML for a []Trigger block using the shared
// ie_renderArraySection. It's a thin shim that builds the ctx the card
// editors construct.
@@ -3469,61 +3484,10 @@ function renderTriggerListHTML(arr, secId, secPath, allowItem) {
return '_mapAddTrigger(\'' + secPath + '\')';
}
};
- return ie_renderArraySection(ctx);
-}
-
-// renderFlagTriggerListHTML is like renderTriggerListHTML but adds the
-// on_player_flag / on_global_flag subscription header per entry.
-function renderFlagTriggerListHTML(arr, secId, secPath, kind) {
- var visMap = {};
- arr = Array.isArray(arr) ? arr : [];
- var sec = {
- id: secId,
- path: secPath,
- isArray: true,
- interactionStyle: true,
- itemIDAllowed: false,
- label: 'Trigger',
- fields: []
- };
- var flagKey = kind === 'player' ? 'on_player_flag' : 'on_global_flag';
-
- var h = '';
- arr.forEach(function(item, idx) {
- visMap[idx] = visMap[idx] || {};
- var condExists = !!(item.condition && typeof item.condition === 'object');
- var lockVal = !!item.lock;
- var steps = item.steps || [];
- var flagName = item[flagKey] || '';
-
- h += '<div class="obj-sub-row ie-inter-row" data-idx="' + idx + '">';
- h += '<div class="ie-inter-header">';
- h += '<label class="ie-lock-lbl"><input type="checkbox" class="ie-lock-cb"' + (lockVal ? ' checked' : '') + '> Lock Player</label>';
- if (idx > 0) h += '<button class="btn btn-sm ie-trig-up" onclick="ie_moveRow(\'' + escAttr(secPath) + '\',' + idx + ',' + (idx - 1) + ')" title="Move up">&#9650;</button>';
- if (idx < arr.length - 1) h += '<button class="btn btn-sm ie-trig-dn" onclick="ie_moveRow(\'' + escAttr(secPath) + '\',' + idx + ',' + (idx + 1) + ')" title="Move down">&#9660;</button>';
- h += '<span class="ie-cond-leaf-label">' + esc(kind === 'player' ? 'Player Flag' : 'Global Flag') + '</span>';
- h += '<input class="ie-flag-name" data-ie-flag-key="' + escAttr(flagKey) + '" data-idx="' + idx + '" value="' + escAttr(flagName) + '" placeholder="' + escAttr(kind === 'player' ? 'player flag name' : 'global flag name') + '" style="flex:1;min-width:80px" oninput="_mapSyncFlag(\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(flagKey) + '\')">';
- h += '<span class="ie-cond-eq">=</span>';
- h += '<input class="ie-flag-value" data-idx="' + idx + '" value="' + escAttr(item.value !== undefined ? String(item.value) : '') + '" placeholder="exists" style="width:60px" oninput="_mapSyncFlag(\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(flagKey) + '\')">';
- h += '<span class="ie-inter-header-spacer"></span>';
- if (!condExists) {
- h += '<button class="btn btn-sm ie-add-btn" onclick="ie_addRootConditionGroup(\'' + escAttr(secPath) + '\',' + idx + ',\'entry\')">+ Add Condition</button>';
- }
- h += '<button class="btn btn-sm btn-danger ie-inter-remove" onclick="_mapRemoveTrigger(\'' + escAttr(secPath) + '\',' + idx + ')">Remove Trigger</button>';
- h += '</div>';
- h += '<div class="ie-inter-panel" style="display:' + (condExists ? 'block' : 'none') + '">';
- h += '<div class="ie-inter-panel-body">' + ie_renderCond(item.condition || null, secId, idx, secPath, 'entry') + '</div>';
- h += '</div>';
- h += '<div class="ie-inter-steps">';
- h += ie_renderStepList(steps, secId, idx, secPath);
- h += '</div>';
- h += '</div>';
- });
- h += '<button class="btn btn-sm obj-sub-add" onclick="_mapAddFlagTrigger(\'' + escAttr(secPath) + '\',\'' + escAttr(flagKey) + '\')">+ Add Trigger</button>';
- return h;
+ return '<div class="obj-card" data-card="' + escAttr(secId) + '" style="max-width:100%">' + ie_renderArraySection(ctx) + '</div>';
}
-// _mapAddTrigger / _mapRemoveTrigger / _mapAddFlagTrigger: mutation helpers
+// _mapAddTrigger / _mapRemoveTrigger: mutation helpers
// called from the inline onclick handlers. They sync the DOM, splice the
// _editorData array, and re-render.
function _mapAddTrigger(secPath) {
@@ -3537,18 +3501,6 @@ function _mapAddTrigger(secPath) {
window._ieRenderCurrent();
}
-function _mapAddFlagTrigger(secPath, flagKey) {
- if (!window._editorData) return;
- if (!window._editorData[secPath]) window._editorData[secPath] = [];
- var entry = {steps: []};
- entry[flagKey] = '';
- window._editorData[secPath].push(entry);
- if (currentRoomData && currentRoomData.room) {
- currentRoomData.room[secPath] = window._editorData[secPath];
- }
- window._ieRenderCurrent();
-}
-
function _mapRemoveTrigger(secPath, idx) {
if (!window._editorData || !window._editorData[secPath]) return;
window._editorData[secPath].splice(idx, 1);
@@ -3557,25 +3509,3 @@ function _mapRemoveTrigger(secPath, idx) {
}
window._ieRenderCurrent();
}
-
-function _mapSyncFlag(secPath, idx, flagKey) {
- if (!window._editorData || !window._editorData[secPath]) return;
- var row = document.querySelector('.ie-inter-row[data-idx="' + idx + '"]');
- if (!row) return;
- var nameEl = row.querySelector('.ie-flag-name');
- var valEl = row.querySelector('.ie-flag-value');
- if (nameEl) {
- window._editorData[secPath][idx][flagKey] = nameEl.value.trim();
- }
- if (valEl) {
- var v = valEl.value.trim();
- if (v === '') delete window._editorData[secPath][idx].value;
- else {
- try { window._editorData[secPath][idx].value = JSON.parse(v); } catch(e) { window._editorData[secPath][idx].value = v; }
- }
- }
- if (currentRoomData && currentRoomData.room) {
- currentRoomData.room[secPath] = window._editorData[secPath];
- }
- autoSave();
-}