Skip to content

Commit 334e1b6

Browse files
author
DavidQ
committed
fix(workspace-tools): replace stale toolHints during sync and filter Workspace Manager tool list by game toolHints
1 parent 367441b commit 334e1b6

3 files changed

Lines changed: 103 additions & 66 deletions

File tree

games/metadata/games.index.metadata.json

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,8 @@
2828
"debugShowcase": false,
2929
"requiresService": false,
3030
"toolHints": [
31-
"skin-editor",
32-
"physics-sandbox",
33-
"state-inspector",
34-
"replay-visualizer",
35-
"palette-browser"
31+
"palette-browser",
32+
"skin-editor"
3633
]
3734
},
3835
{
@@ -55,11 +52,8 @@
5552
"debugShowcase": true,
5653
"requiresService": false,
5754
"toolHints": [
58-
"skin-editor",
59-
"sprite-editor",
60-
"tile-map-editor",
61-
"replay-visualizer",
62-
"palette-browser"
55+
"palette-browser",
56+
"skin-editor"
6357
],
6458
"engineClassesUsed": [
6559
"engine/core/Engine",
@@ -89,11 +83,8 @@
8983
"debugShowcase": false,
9084
"requiresService": false,
9185
"toolHints": [
92-
"skin-editor",
93-
"physics-sandbox",
94-
"performance-profiler",
95-
"state-inspector",
96-
"palette-browser"
86+
"palette-browser",
87+
"skin-editor"
9788
],
9889
"engineClassesUsed": [
9990
"engine/core/Engine",
@@ -157,11 +148,8 @@
157148
"debugShowcase": false,
158149
"requiresService": false,
159150
"toolHints": [
160-
"skin-editor",
161-
"sprite-editor",
162-
"tile-map-editor",
163-
"replay-visualizer",
164-
"palette-browser"
151+
"palette-browser",
152+
"skin-editor"
165153
],
166154
"engineClassesUsed": [
167155
"engine/core/Engine",
@@ -191,14 +179,13 @@
191179
"debugShowcase": true,
192180
"requiresService": false,
193181
"toolHints": [
194-
"vector-asset-studio",
195-
"vector-map-editor",
196-
"asset-pipeline-tool",
197182
"palette-browser",
198183
"asset-browser",
199184
"sprite-editor",
200185
"tile-map-editor",
201-
"parallax-editor"
186+
"parallax-editor",
187+
"vector-asset-studio",
188+
"asset-pipeline-tool"
202189
],
203190
"engineClassesUsed": [
204191
"engine/audio/index/GaplessLoopPlayer",
@@ -240,9 +227,6 @@
240227
"debugShowcase": false,
241228
"requiresService": false,
242229
"toolHints": [
243-
"sprite-editor",
244-
"tile-map-editor",
245-
"asset-pipeline-tool",
246230
"palette-browser",
247231
"asset-browser"
248232
],
@@ -276,9 +260,6 @@
276260
"debugShowcase": false,
277261
"requiresService": false,
278262
"toolHints": [
279-
"vector-asset-studio",
280-
"replay-visualizer",
281-
"state-inspector",
282263
"palette-browser",
283264
"asset-browser"
284265
],

scripts/sync-tool-hints-from-workspace-manager.mjs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,15 +193,17 @@ function syncToolHints(metadata) {
193193
const gameDir = getGameDirFromHref(game?.href);
194194
const existing = normalizeToolHints(game?.toolHints);
195195
const derivedFromKinds = [];
196+
const hasCatalog = !!gameDir && fs.existsSync(path.join(gameDir, "assets", GAME_ASSET_CATALOG_FILENAME));
196197

197198
readWorkspaceAssetKinds(gameDir).forEach((kind) => {
198199
const mapped = KIND_TO_TOOL_HINTS[kind] || [];
199200
mapped.forEach((toolId) => derivedFromKinds.push(toolId));
200201
});
201202

202203
const derivedFromToolsManifest = readToolHintsFromToolsManifest(gameDir);
203-
const merged = normalizeToolHints([...existing, ...derivedFromKinds, ...derivedFromToolsManifest]);
204-
const invalid = merged.filter((toolId) => !knownToolIds.has(toolId));
204+
const derived = normalizeToolHints([...derivedFromKinds, ...derivedFromToolsManifest]);
205+
const next = hasCatalog ? derived : existing;
206+
const invalid = next.filter((toolId) => !knownToolIds.has(toolId));
205207
if (invalid.length > 0) {
206208
throw new Error(`${gameId}: unknown tool id(s): ${invalid.join(", ")}`);
207209
}
@@ -212,8 +214,8 @@ function syncToolHints(metadata) {
212214
warnings.push(`${gameId}: no ${GAME_ASSET_CATALOG_FILENAME}`);
213215
}
214216

215-
if (JSON.stringify(existing) !== JSON.stringify(merged) || !Array.isArray(game.toolHints)) {
216-
game.toolHints = merged;
217+
if (JSON.stringify(existing) !== JSON.stringify(next) || !Array.isArray(game.toolHints)) {
218+
game.toolHints = next;
217219
updated += 1;
218220
}
219221
}
@@ -244,4 +246,4 @@ try {
244246
} catch (error) {
245247
console.error(`FAIL ${error instanceof Error ? error.message : String(error)}`);
246248
process.exit(1);
247-
}
249+
}

tools/Workspace Manager/main.js

Lines changed: 85 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ const refs = {
9797
};
9898

9999
const manifest = createToolHostManifest();
100-
const toolIds = manifest.tools.map((tool) => tool.id);
101-
const hasAvailableTools = toolIds.length > 0;
100+
const allToolIds = manifest.tools.map((tool) => tool.id);
101+
let toolIds = [...allToolIds];
102102
let currentGameFrame = null;
103103
let currentGameHostContextId = "";
104104
const TOOL_LAUNCH_PARAM_PREFIXES = Object.freeze({
@@ -112,6 +112,27 @@ function normalizeTextParam(value) {
112112
return typeof value === "string" ? value.trim() : "";
113113
}
114114

115+
function normalizeToken(value) {
116+
return normalizeTextParam(value).toLowerCase();
117+
}
118+
119+
function normalizeToolHintList(value) {
120+
if (!Array.isArray(value)) {
121+
return [];
122+
}
123+
const seen = new Set();
124+
const output = [];
125+
value.forEach((entry) => {
126+
const token = normalizeToken(entry);
127+
if (!token || seen.has(token)) {
128+
return;
129+
}
130+
seen.add(token);
131+
output.push(token);
132+
});
133+
return output;
134+
}
135+
115136
function normalizeLocalHrefParam(value, allowedPrefixes = []) {
116137
const normalized = normalizeTextParam(value).replace(/\\/g, "/");
117138
if (!normalized || !normalized.startsWith("/") || normalized.includes("..")) {
@@ -216,7 +237,7 @@ function updateStandaloneHref(toolId) {
216237
return;
217238
}
218239
const entry = getToolHostEntryById(manifest, toolId);
219-
const enabled = !!entry;
240+
const enabled = !!entry && toolIds.includes(toolId);
220241
refs.standaloneLink.href = enabled ? entry.launchPath : "#";
221242
refs.standaloneLink.setAttribute("aria-disabled", enabled ? "false" : "true");
222243
refs.standaloneLink.tabIndex = enabled ? 0 : -1;
@@ -243,16 +264,16 @@ function writeQueryToolId(toolId, replace = false) {
243264
function readInitialToolId() {
244265
const url = new URL(window.location.href);
245266
const fromQuery = url.searchParams.get("tool");
246-
if (fromQuery && getToolHostEntryById(manifest, fromQuery)) {
267+
if (fromQuery && getToolHostEntryById(manifest, fromQuery) && toolIds.includes(fromQuery)) {
247268
return fromQuery;
248269
}
249-
return manifest.tools[0]?.id || "";
270+
return toolIds[0] || "";
250271
}
251272

252273
function readRequestedToolIdFromQuery() {
253274
const url = new URL(window.location.href);
254275
const requested = (url.searchParams.get("tool") || "").trim();
255-
if (!requested || !getToolHostEntryById(manifest, requested)) {
276+
if (!requested || !getToolHostEntryById(manifest, requested) || !toolIds.includes(requested)) {
256277
return "";
257278
}
258279
return requested;
@@ -307,6 +328,7 @@ async function readGameEntryById(gameId) {
307328
const tags = Array.isArray(entry.tags)
308329
? entry.tags.map((value) => String(value || "").trim()).filter(Boolean)
309330
: [];
331+
const toolHints = normalizeToolHintList(entry.toolHints);
310332
return {
311333
id: String(entry.id || "").trim(),
312334
title: String(entry.title || entry.id || "Game").trim(),
@@ -317,6 +339,7 @@ async function readGameEntryById(gameId) {
317339
description: String(entry.description || "").trim(),
318340
classValues,
319341
tags,
342+
toolHints,
320343
sampleTrack: entry.sampleTrack === true,
321344
debugShowcase: entry.debugShowcase === true,
322345
requiresService: entry.requiresService === true
@@ -410,17 +433,17 @@ async function mountGameFrame(gameEntry) {
410433

411434
function syncControlState() {
412435
const selectedToolId = readSelectedToolId();
413-
const hasSelection = !!selectedToolId && !!getToolHostEntryById(manifest, selectedToolId);
436+
const hasSelection = !!selectedToolId && toolIds.includes(selectedToolId) && !!getToolHostEntryById(manifest, selectedToolId);
414437
const hasMount = !!runtime.getCurrentMount();
415438

416439
if (refs.mountButton instanceof HTMLButtonElement) {
417440
refs.mountButton.disabled = !hasSelection;
418441
}
419442
if (refs.prevButton instanceof HTMLButtonElement) {
420-
refs.prevButton.disabled = !hasAvailableTools;
443+
refs.prevButton.disabled = toolIds.length === 0;
421444
}
422445
if (refs.nextButton instanceof HTMLButtonElement) {
423-
refs.nextButton.disabled = !hasAvailableTools;
446+
refs.nextButton.disabled = toolIds.length === 0;
424447
}
425448
if (refs.unmountButton instanceof HTMLButtonElement) {
426449
refs.unmountButton.disabled = !hasMount;
@@ -432,13 +455,29 @@ function populateToolSelect(initialToolId) {
432455
return;
433456
}
434457

435-
refs.toolSelect.innerHTML = manifest.tools
458+
refs.toolSelect.innerHTML = toolIds
459+
.map((toolId) => getToolHostEntryById(manifest, toolId))
460+
.filter(Boolean)
436461
.map((tool) => `<option value="${tool.id}">${tool.displayName}</option>`)
437462
.join("");
438-
refs.toolSelect.value = getToolHostEntryById(manifest, initialToolId) ? initialToolId : (manifest.tools[0]?.id || "");
463+
refs.toolSelect.value = toolIds.includes(initialToolId) ? initialToolId : (toolIds[0] || "");
439464
updateSwitchMeta();
440465
}
441466

467+
function applyToolHintsFilterForGame(gameEntry, preferredToolId = "") {
468+
if (!gameEntry) {
469+
toolIds = [...allToolIds];
470+
} else {
471+
const allowed = normalizeToolHintList(gameEntry.toolHints)
472+
.filter((toolId) => !!getToolHostEntryById(manifest, toolId));
473+
toolIds = [...allowed];
474+
}
475+
const initialToolId = toolIds.includes(preferredToolId) ? preferredToolId : (toolIds[0] || "");
476+
populateToolSelect(initialToolId);
477+
updateStandaloneHref(initialToolId);
478+
syncControlState();
479+
}
480+
442481
const runtime = createToolHostRuntime({
443482
manifest,
444483
mountContainer: refs.mountContainer,
@@ -535,17 +574,28 @@ function bindEvents() {
535574

536575
window.addEventListener("popstate", () => {
537576
const gameId = readInitialGameId();
538-
const requestedToolId = readRequestedToolIdFromQuery();
539-
if (gameId && !requestedToolId && shouldMountGameFrameFromQuery()) {
577+
if (gameId) {
540578
void readGameEntryById(gameId).then((gameEntry) => {
541579
if (!gameEntry) {
542580
writeStatus(`Game "${gameId}" is not available for Workspace Manager launch.`);
581+
applyToolHintsFilterForGame(null);
582+
return;
583+
}
584+
const requestedToolId = (() => {
585+
const url = new URL(window.location.href);
586+
return (url.searchParams.get("tool") || "").trim();
587+
})();
588+
applyToolHintsFilterForGame(gameEntry, requestedToolId);
589+
if (!requestedToolId && shouldMountGameFrameFromQuery()) {
590+
void mountGameFrame(gameEntry);
543591
return;
544592
}
545-
void mountGameFrame(gameEntry);
593+
mountSelectedTool("popstate");
546594
});
547595
return;
548596
}
597+
applyToolHintsFilterForGame(null);
598+
const requestedToolId = readRequestedToolIdFromQuery();
549599
const toolId = requestedToolId || readInitialToolId();
550600
if (refs.toolSelect instanceof HTMLSelectElement) {
551601
refs.toolSelect.value = toolId;
@@ -562,29 +612,33 @@ function bindEvents() {
562612
}
563613

564614
async function init() {
565-
const requestedToolId = readRequestedToolIdFromQuery();
566-
const initialToolId = requestedToolId || readInitialToolId();
567-
populateToolSelect(initialToolId);
568-
updateStandaloneHref(initialToolId);
569-
syncControlState();
570-
bindEvents();
571-
572615
const initialGameId = readInitialGameId();
573-
if (initialGameId && !requestedToolId && shouldMountGameFrameFromQuery()) {
574-
const gameEntry = await readGameEntryById(initialGameId);
575-
if (gameEntry) {
576-
await mountGameFrame(gameEntry);
577-
return;
616+
let initialGameEntry = null;
617+
if (initialGameId) {
618+
initialGameEntry = await readGameEntryById(initialGameId);
619+
if (!initialGameEntry) {
620+
writeStatus(`Game "${initialGameId}" is not available for Workspace Manager launch.`);
578621
}
579-
writeStatus(`Game "${initialGameId}" is not available for Workspace Manager launch.`);
580622
}
581623

582-
if (!hasAvailableTools) {
583-
writeStatus("No active tools are currently available for Workspace Manager.");
624+
const rawRequestedToolId = (() => {
625+
const url = new URL(window.location.href);
626+
return (url.searchParams.get("tool") || "").trim();
627+
})();
628+
applyToolHintsFilterForGame(initialGameEntry, rawRequestedToolId);
629+
bindEvents();
630+
631+
const requestedToolId = readRequestedToolIdFromQuery();
632+
if (initialGameEntry && !requestedToolId && shouldMountGameFrameFromQuery()) {
633+
await mountGameFrame(initialGameEntry);
634+
return;
584635
}
585-
if (hasAvailableTools) {
586-
mountSelectedTool("init");
636+
637+
if (toolIds.length === 0) {
638+
writeStatus("No active tools are currently available for Workspace Manager.");
639+
return;
587640
}
641+
mountSelectedTool("init");
588642
}
589643

590644
void init();

0 commit comments

Comments
 (0)