diff --git a/admin-web/index.html b/admin-web/index.html index 6f4834c..20c39f3 100644 --- a/admin-web/index.html +++ b/admin-web/index.html @@ -45,8 +45,19 @@ .live-ws-bar-actions .btn { margin-top: 0; margin-right: 0; } details.live-details > summary { cursor: pointer; color: #cfe3ff; font-weight: 600; list-style-position: outside; } details.live-details > summary::-webkit-details-marker { color: #9eb0d6; } - .live-json-pre { max-height: 320px; overflow: auto; font-size: 11px; margin: 8px 0 0; padding: 10px; background: #0f1522; border: 1px solid #2f3b5a; border-radius: 6px; white-space: pre-wrap; word-break: break-word; } .quest-world-panel { margin-top: 12px; padding-top: 12px; border-top: 1px solid #2a3551; } + .jv-root { margin-top: 8px; font-family: ui-monospace, Consolas, monospace; font-size: 12px; line-height: 1.45; } + .jv-node { margin: 2px 0 2px 0; border-left: 1px solid #2f3b5a; padding-left: 8px; } + .jv-node > .jv-sum { cursor: pointer; color: #9eb0d6; user-select: none; } + .jv-node > .jv-sum:hover { color: #cfe3ff; } + .jv-ch { margin-top: 4px; padding-left: 4px; } + .jv-row { margin: 2px 0; } + .jv-key { color: #7eb8ff; margin-right: 4px; } + .jv-idx { color: #b8a0ff; margin-right: 4px; } + .jv-str { color: #7de29f; } + .jv-lit { color: #ffb86c; } + .jv-null { color: #9eb0d6; font-style: italic; } + .jv-empty { color: #6a7a9e; }
@@ -96,6 +107,8 @@ _heroLiveWsLastAt: null, _liveSnapshotOpen: false, _heroQuestWorldOpen: false, + _heroLiveSnapshot: null, + _jsonViewerOpenPaths: null, }; state._confirmAction = null; @@ -612,6 +625,8 @@ state._heroLiveWsStatus = "disconnected"; state._heroLiveWsError = ""; state._heroLiveWsLastAt = null; + state._heroLiveSnapshot = null; + state._jsonViewerOpenPaths = null; render(); } function startHeroMovementPoll(durationSec = 55) { @@ -649,6 +664,8 @@ state._heroLiveWsStatus = "connecting"; state._heroLiveWsError = ""; state._heroLiveWsLastAt = null; + state._heroLiveSnapshot = null; + state._jsonViewerOpenPaths = Object.create(null); render(); ws.onopen = () => { state._heroLiveWsStatus = "connected"; @@ -672,8 +689,18 @@ render(); return; } - if (data && data.id && state.selectedHeroId === data.id) { - state.selectedHero = data; + let hero = null; + let snap = null; + if (data && data.hero && typeof data.hero === "object" && data.hero.id != null) { + hero = data.hero; + snap = data; + } else if (data && data.id != null) { + hero = data; + snap = { hero: data, heroMove: null }; + } + if (hero && state.selectedHeroId === hero.id) { + state.selectedHero = hero; + state._heroLiveSnapshot = snap; state._heroLiveWsLastAt = Date.now(); render(); } @@ -684,6 +711,53 @@ } }; } + function jsonChildPath(parent, segment) { + if (parent === "$") return `$.${segment}`; + return `${parent}.${segment}`; + } + function jsonViewerToggle(path, ev) { + if (ev) ev.preventDefault(); + if (!state._jsonViewerOpenPaths) state._jsonViewerOpenPaths = Object.create(null); + if (state._jsonViewerOpenPaths[path]) delete state._jsonViewerOpenPaths[path]; + else state._jsonViewerOpenPaths[path] = true; + render(); + } + function jsonTreeHtml(value, path) { + const pathArg = JSON.stringify(path); + const open = state._jsonViewerOpenPaths && state._jsonViewerOpenPaths[path]; + const openAttr = open ? " open" : ""; + if (value === null) return `null`; + if (value === undefined) return `undefined`; + const t = typeof value; + if (t === "boolean" || t === "number") return `${e(String(value))}`; + if (t === "string") return `"${e(value)}"`; + if (Array.isArray(value)) { + if (value.length === 0) return `[]`; + const inner = value.map((item, i) => { + const cp = `${path}[${i}]`; + return `Нет данных — подключите live или дождитесь сообщения.
`; + } + return `${e(JSON.stringify(h, null, 2))}`
- : `Нет JSON — подключите live или дождитесь первого сообщения.
`; const openAttr = state._liveSnapshotOpen ? " open" : ""; return `/admin-ws/hero/{heroId}, авторизация как у API.