Skip to content

Commit ff10ebc

Browse files
author
DavidQ
committed
skin-editor: add checkbox spacing, require 2+ selections for flatten, and clear selections after flatten
1 parent 3ab8674 commit ff10ebc

2 files changed

Lines changed: 89 additions & 36 deletions

File tree

tools/Skin Editor/main.js

Lines changed: 88 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { safeParseJson, toPrettyJson } from "../shared/debugInspectorData.js";
22
import { registerToolBootContract } from "../shared/toolBootContract.js";
3+
import { readSharedPaletteHandoff } from "../shared/assetUsageIntegration.js";
34
import {
45
clearGameSkinOverride,
56
loadGameSkin,
@@ -76,7 +77,8 @@ const state = {
7677
presetSkin: null,
7778
selectedObjectKey: "",
7879
selectedObjectKeys: [],
79-
selectedColorSwatch: ""
80+
selectedColorSwatch: "",
81+
skipAutoSelectOnce: false
8082
};
8183

8284
function normalizeText(value) {
@@ -118,11 +120,11 @@ function getGameOptionById(gameId) {
118120
}
119121

120122
function getSelectedGameOption() {
121-
return getGameOptionById(state.activeGameId) || GAME_OPTIONS[0] || null;
123+
return getGameOptionById(state.activeGameId);
122124
}
123125

124126
function resolveActiveGameOption(initialGameId = "") {
125-
const selected = getGameOptionById(initialGameId) || GAME_OPTIONS[0] || null;
127+
const selected = getGameOptionById(initialGameId);
126128
state.activeGameId = selected ? selected.id : "";
127129
return selected;
128130
}
@@ -132,6 +134,22 @@ function getObjectKeys() {
132134
return Object.keys(objects);
133135
}
134136

137+
function getValidSelectedObjectKeys() {
138+
const keys = getObjectKeys();
139+
if (!keys.length || !Array.isArray(state.selectedObjectKeys)) {
140+
return [];
141+
}
142+
return Array.from(new Set(state.selectedObjectKeys.filter((key) => keys.includes(key))));
143+
}
144+
145+
function updateFlattenButtonState() {
146+
const validSelection = getValidSelectedObjectKeys();
147+
state.selectedObjectKeys = validSelection;
148+
if (refs.flattenObjectsButton instanceof HTMLButtonElement) {
149+
refs.flattenObjectsButton.disabled = validSelection.length < 2;
150+
}
151+
}
152+
135153
function setStatus(message) {
136154
if (refs.statusText instanceof HTMLElement) {
137155
refs.statusText.textContent = message;
@@ -249,18 +267,26 @@ function ensureSelectedObjectKey() {
249267
if (!keys.length) {
250268
state.selectedObjectKey = "";
251269
state.selectedObjectKeys = [];
270+
updateFlattenButtonState();
271+
return;
272+
}
273+
274+
const normalizedSelection = getValidSelectedObjectKeys();
275+
if (state.skipAutoSelectOnce && !normalizeText(state.selectedObjectKey)) {
276+
state.skipAutoSelectOnce = false;
277+
state.selectedObjectKeys = normalizedSelection;
278+
updateFlattenButtonState();
252279
return;
253280
}
281+
254282
if (!keys.includes(state.selectedObjectKey)) {
255283
state.selectedObjectKey = keys[0];
256284
}
257-
const normalizedSelection = Array.isArray(state.selectedObjectKeys)
258-
? state.selectedObjectKeys.filter((key) => keys.includes(key))
259-
: [];
260285
if (!normalizedSelection.includes(state.selectedObjectKey)) {
261286
normalizedSelection.unshift(state.selectedObjectKey);
262287
}
263288
state.selectedObjectKeys = Array.from(new Set(normalizedSelection));
289+
updateFlattenButtonState();
264290
}
265291

266292
function parseHexForPicker(value) {
@@ -611,6 +637,7 @@ function syncSelectedObjectUiFromSelection() {
611637
function selectObjectKey(objectKey) {
612638
state.selectedObjectKey = objectKey;
613639
syncSelectedObjectUiFromSelection();
640+
updateFlattenButtonState();
614641
renderObjectList();
615642
renderPaletteList();
616643
renderObjectControls();
@@ -624,6 +651,7 @@ function setObjectSelected(objectKey, selected) {
624651
: currentSelection.filter((key) => key !== objectKey);
625652
state.selectedObjectKeys = nextSelection;
626653
syncSelectedObjectUiFromSelection();
654+
updateFlattenButtonState();
627655
renderObjectList();
628656
renderPaletteList();
629657
renderObjectControls();
@@ -691,38 +719,45 @@ function renderPaletteList() {
691719
return;
692720
}
693721
refs.paletteList.innerHTML = "";
694-
const objects = toObject(state.activeSkin?.objects);
695-
const colorMap = new Map();
696-
Object.entries(objects).forEach(([objectKey, objectValue]) => {
697-
const shapeObject = toObject(objectValue);
698-
Object.entries(shapeObject).forEach(([propertyKey, propertyValue]) => {
699-
const color = typeof propertyValue === "string" ? normalizeText(propertyValue) : "";
722+
const sharedPalette = readSharedPaletteHandoff();
723+
const entries = Array.isArray(sharedPalette?.colors) ? sharedPalette.colors : [];
724+
const swatches = entries
725+
.map((entry, index) => {
726+
const color = normalizeText(entry?.hex);
700727
if (!parseHexForPicker(color)) {
701-
return;
702-
}
703-
const token = color.toLowerCase();
704-
if (!colorMap.has(token)) {
705-
colorMap.set(token, {
706-
id: `${objectKey}.${propertyKey}`,
707-
label: `${objectKey}.${propertyKey}`,
708-
color
709-
});
728+
return null;
710729
}
711-
});
712-
});
713-
const swatches = Array.from(colorMap.values());
730+
const swatchName = normalizeText(entry?.name) || `Swatch ${index + 1}`;
731+
const swatchSymbol = normalizeText(entry?.symbol);
732+
const suffix = swatchSymbol ? ` [${swatchSymbol}]` : "";
733+
return {
734+
id: `${sharedPalette?.paletteId || "shared-palette"}.${index}`,
735+
label: `${swatchName}${suffix}`,
736+
color
737+
};
738+
})
739+
.filter((entry) => Boolean(entry));
740+
741+
if (!sharedPalette) {
742+
const empty = document.createElement("p");
743+
empty.className = "skin-editor-empty";
744+
empty.textContent = "No shared palette selected. Open Palette Browser and select Use in Workspace Manager.";
745+
refs.paletteList.appendChild(empty);
746+
return;
747+
}
714748

715749
if (!swatches.length) {
716750
const empty = document.createElement("p");
717751
empty.className = "skin-editor-empty";
718-
empty.textContent = "No object colors found.";
752+
empty.textContent = "Shared palette has no valid swatches.";
719753
refs.paletteList.appendChild(empty);
720754
return;
721755
}
722756

723757
const paletteLabel = document.createElement("p");
724758
paletteLabel.className = "skin-editor-empty";
725-
paletteLabel.textContent = `Palette rebuilt from object colors (${swatches.length}).`;
759+
const paletteName = normalizeText(sharedPalette.displayName) || normalizeText(sharedPalette.paletteId) || "Shared Palette";
760+
paletteLabel.textContent = `Shared palette '${paletteName}' (${swatches.length}).`;
726761
refs.paletteList.appendChild(paletteLabel);
727762

728763
const selectedObjectColor = normalizeText(state.selectedColorSwatch || getSelectedObjectColorValue()).toLowerCase();
@@ -757,6 +792,9 @@ function renderObjectList() {
757792
}
758793
refs.objectList.innerHTML = "";
759794
const keys = getObjectKeys();
795+
const selectedObjectKeys = getValidSelectedObjectKeys();
796+
state.selectedObjectKeys = selectedObjectKeys;
797+
updateFlattenButtonState();
760798
if (!keys.length) {
761799
const note = document.createElement("p");
762800
note.className = "skin-editor-empty";
@@ -772,7 +810,7 @@ function renderObjectList() {
772810
const checkbox = document.createElement("input");
773811
checkbox.type = "checkbox";
774812
checkbox.className = "skin-editor-object-check";
775-
checkbox.checked = state.selectedObjectKeys.includes(objectKey);
813+
checkbox.checked = selectedObjectKeys.includes(objectKey);
776814
checkbox.addEventListener("click", (event) => {
777815
event.stopPropagation();
778816
});
@@ -1062,6 +1100,7 @@ function drawSelectedObjectPreview() {
10621100

10631101
function renderWorkbench() {
10641102
ensureSelectedObjectKey();
1103+
updateFlattenButtonState();
10651104
syncSelectedObjectUiFromSelection();
10661105
renderObjectList();
10671106
renderPaletteList();
@@ -1077,14 +1116,15 @@ function setCurrentSkinDocument(rawSkin, source = "loaded") {
10771116
const normalized = sanitizePositiveDimensionsInDocument(toObjectCentricSkinDocument(game, rawSkin));
10781117
state.activeSkin = deepClone(normalized) || normalized;
10791118
state.selectedObjectKeys = [];
1119+
state.skipAutoSelectOnce = false;
10801120
updateEditorFromState(source);
10811121
renderWorkbench();
10821122
}
10831123

10841124
async function loadActiveSkinForSelectedGame() {
10851125
const game = getSelectedGameOption();
10861126
if (!game) {
1087-
setStatus("No supported game context was resolved.");
1127+
setStatus("Missing game context. Launch Skin Editor from a game/workspace link that includes a valid gameId.");
10881128
return;
10891129
}
10901130
const fallbackSkin = {
@@ -1333,9 +1373,10 @@ function flattenSelectedObjects() {
13331373
state.activeSkin.objects = {};
13341374
}
13351375
const objects = state.activeSkin.objects;
1336-
const selectedKeys = Array.from(
1337-
new Set(state.selectedObjectKeys.filter((key) => Object.prototype.hasOwnProperty.call(objects, key)))
1338-
);
1376+
const selectedKeys = getValidSelectedObjectKeys()
1377+
.filter((key) => Object.prototype.hasOwnProperty.call(objects, key));
1378+
state.selectedObjectKeys = selectedKeys;
1379+
updateFlattenButtonState();
13391380
if (selectedKeys.length < 2) {
13401381
setStatus("Select at least 2 objects to flatten.");
13411382
return;
@@ -1361,10 +1402,11 @@ function flattenSelectedObjects() {
13611402
color: firstColor,
13621403
components
13631404
};
1364-
state.selectedObjectKey = nextKey;
1365-
state.selectedObjectKeys = [nextKey];
1405+
state.selectedObjectKey = "";
1406+
state.selectedObjectKeys = [];
1407+
state.skipAutoSelectOnce = true;
13661408
if (refs.newShapeName instanceof HTMLInputElement) {
1367-
refs.newShapeName.value = nextKey;
1409+
refs.newShapeName.value = "";
13681410
}
13691411
updateEditorFromState("visual-editor");
13701412
renderWorkbench();
@@ -1416,6 +1458,7 @@ async function loadPresetFromQuery() {
14161458
}
14171459

14181460
function bindEvents() {
1461+
updateFlattenButtonState();
14191462
refs.loadButton?.addEventListener("click", () => {
14201463
void loadActiveSkinForSelectedGame();
14211464
});
@@ -1453,9 +1496,19 @@ function bindEvents() {
14531496

14541497
async function bootSkinEditor() {
14551498
const { gameId, presetLoaded } = await loadPresetFromQuery();
1456-
resolveActiveGameOption(gameId);
1499+
const resolvedGame = resolveActiveGameOption(gameId);
14571500
bindEvents();
1501+
if (!resolvedGame) {
1502+
setStatus("Missing game context. Launch Skin Editor from a game/workspace link that includes a valid gameId.");
1503+
updateFlattenButtonState();
1504+
renderPaletteList();
1505+
return;
1506+
}
14581507
await loadActiveSkinForSelectedGame();
1508+
if (!readSharedPaletteHandoff()) {
1509+
setStatus("Shared palette is required. Open Palette Browser and use 'Use in Workspace Manager' first.");
1510+
return;
1511+
}
14591512
if (presetLoaded) {
14601513
setStatus("Loaded game preset. Object workbench is ready.");
14611514
}

tools/Skin Editor/skinEditor.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ body[data-tool-id="skin-editor"] .skin-editor-object-row {
8585
body[data-tool-id="skin-editor"] .skin-editor-object-check {
8686
width: 16px;
8787
height: 16px;
88-
margin: 0;
88+
margin: 0 0 0 6px;
8989
accent-color: #7c3aed;
9090
}
9191

0 commit comments

Comments
 (0)