Skip to content

Commit 12976be

Browse files
author
DavidQ
committed
Get Palette Magager to work as designed
1 parent eb54754 commit 12976be

4 files changed

Lines changed: 264 additions & 21 deletions

File tree

tools/Palette Browser/index.html

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,6 @@
1919
</div>
2020
</details>
2121
<div class="palette-browser app-shell">
22-
<details class="accordion palette-browser__accordion" open>
23-
<summary class="accordion__summary">
24-
<span class="accordion__icon"></span>
25-
</summary>
26-
<div class="accordion__content">
27-
</div>
28-
</details>
2922
<div class="palette-browser__layout tools-platform-layout-grid">
3023
<aside class="panel palette-browser__panel tools-platform-resize-panel" data-panel-side="left">
3124
<h3>Palette List</h3>
@@ -61,10 +54,13 @@ <h3 id="paletteTitle">Palette Preview</h3>
6154
<input id="swatchSymbolInput" type="text" maxlength="2" placeholder="A" />
6255
</label>
6356
</div>
64-
<div class="palette-browser__actions tools-platform-control-row">
57+
<div class="palette-browser__actions palette-browser__actions--palette tools-platform-control-row">
6558
<button id="newPaletteButton" type="button">New Palette</button>
6659
<button id="duplicatePaletteButton" type="button">Duplicate Selected</button>
6760
<button id="renamePaletteButton" type="button">Rename Palette</button>
61+
<button id="deletePaletteButton" type="button">Delete Palette</button>
62+
</div>
63+
<div class="palette-browser__actions palette-browser__actions--swatch tools-platform-control-row">
6864
<button id="addSwatchButton" type="button">Add Swatch</button>
6965
<button id="deleteSwatchButton" type="button">Delete Swatch</button>
7066
</div>

tools/Palette Browser/main.js

Lines changed: 162 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { registerToolBootContract } from "../shared/toolBootContract.js";
99

1010
const CUSTOM_PALETTES_STORAGE_KEY = "toolboxaid.paletteBrowser.customPalettes";
11+
const HIDDEN_BUILTIN_PALETTES_STORAGE_KEY = "toolboxaid.paletteBrowser.hiddenBuiltins";
1112

1213
const refs = {
1314
searchInput: document.getElementById("paletteSearchInput"),
@@ -24,6 +25,7 @@ const refs = {
2425
newPaletteButton: document.getElementById("newPaletteButton"),
2526
duplicatePaletteButton: document.getElementById("duplicatePaletteButton"),
2627
renamePaletteButton: document.getElementById("renamePaletteButton"),
28+
deletePaletteButton: document.getElementById("deletePaletteButton"),
2729
addSwatchButton: document.getElementById("addSwatchButton"),
2830
deleteSwatchButton: document.getElementById("deleteSwatchButton"),
2931
validationText: document.getElementById("paletteValidationText"),
@@ -38,9 +40,18 @@ const state = {
3840
search: "",
3941
selectedPaletteId: "",
4042
selectedSwatchIndex: 0,
41-
customPalettes: loadCustomPalettes()
43+
customPalettes: loadCustomPalettes(),
44+
hiddenBuiltInPaletteIds: loadHiddenBuiltInPaletteIds()
4245
};
4346

47+
function hasDeleteOverrideParam() {
48+
const params = new URLSearchParams(window.location.search);
49+
const raw = params.get("overridReserveWorkBlock")
50+
?? params.get("overrideReserveWordBlock")
51+
?? "";
52+
return /^(1|true|yes|on)$/i.test(raw.trim());
53+
}
54+
4455
function applyLaunchContext() {
4556
const context = getSharedLaunchContext();
4657
const sourceLabel = context.sourceToolId
@@ -68,6 +79,23 @@ function saveCustomPalettes() {
6879
localStorage.setItem(CUSTOM_PALETTES_STORAGE_KEY, JSON.stringify(state.customPalettes));
6980
}
7081

82+
function loadHiddenBuiltInPaletteIds() {
83+
try {
84+
const raw = localStorage.getItem(HIDDEN_BUILTIN_PALETTES_STORAGE_KEY);
85+
if (!raw) {
86+
return [];
87+
}
88+
const parsed = JSON.parse(raw);
89+
return Array.isArray(parsed) ? parsed.filter((entry) => typeof entry === "string") : [];
90+
} catch {
91+
return [];
92+
}
93+
}
94+
95+
function saveHiddenBuiltInPaletteIds() {
96+
localStorage.setItem(HIDDEN_BUILTIN_PALETTES_STORAGE_KEY, JSON.stringify(state.hiddenBuiltInPaletteIds));
97+
}
98+
7199
function getBuiltInPalettes() {
72100
const paletteSource = globalThis.palettesList && typeof globalThis.palettesList === "object"
73101
? globalThis.palettesList
@@ -83,7 +111,8 @@ function getBuiltInPalettes() {
83111
hex: typeof entry?.hex === "string" ? entry.hex : "",
84112
name: typeof entry?.name === "string" ? entry.name : ""
85113
}))
86-
}));
114+
}))
115+
.filter((palette) => !state.hiddenBuiltInPaletteIds.includes(palette.id));
87116
}
88117

89118
function getAllPalettes() {
@@ -108,6 +137,23 @@ function isCustomPalette(palette) {
108137
return Boolean(palette && palette.source === "custom");
109138
}
110139

140+
function normalizePaletteNameForReservedCheck(name) {
141+
return String(name || "").toLowerCase().replace(/[^a-z0-9]/g, "");
142+
}
143+
144+
function isReadOnlyPalette(palette) {
145+
if (!palette) {
146+
return true;
147+
}
148+
if (!isCustomPalette(palette)) {
149+
return true;
150+
}
151+
const normalizedName = normalizePaletteNameForReservedCheck(palette.name);
152+
return normalizedName.includes("crayola")
153+
|| normalizedName.includes("w3c")
154+
|| normalizedName.includes("javascript");
155+
}
156+
111157
function validatePalette(palette) {
112158
if (!palette) {
113159
return ["Select a palette to validate."];
@@ -131,6 +177,17 @@ function validatePalette(palette) {
131177
return issues;
132178
}
133179

180+
function formatSwatchNameForDisplay(name) {
181+
return String(name || "Unnamed").replace(/([a-z0-9])([A-Z])/g, "$1\u200b$2");
182+
}
183+
184+
function hasReservedPaletteKeyword(name) {
185+
const normalizedName = normalizePaletteNameForReservedCheck(name);
186+
return normalizedName.includes("crayola")
187+
|| normalizedName.includes("w3c")
188+
|| normalizedName.includes("javascript");
189+
}
190+
134191
function renderPaletteList() {
135192
const palettes = getVisiblePalettes();
136193
refs.countText.textContent = `${palettes.length} palette${palettes.length === 1 ? "" : "s"}`;
@@ -139,7 +196,7 @@ function renderPaletteList() {
139196
return `
140197
<button type="button" data-palette-id="${palette.id}" class="${currentClass.trim()}">
141198
<strong>${palette.name}</strong>
142-
<span>${palette.entries.length} swatches | ${palette.source}</span>
199+
<span>(${palette.entries.length}) swatches | ${palette.source}</span>
143200
</button>
144201
`;
145202
}).join("");
@@ -156,6 +213,14 @@ function renderSelectedPalette() {
156213
refs.paletteSummaryText.textContent = "Select a palette to inspect its swatches.";
157214
refs.paletteSwatches.innerHTML = "";
158215
refs.paletteNameInput.value = "";
216+
refs.paletteNameInput.disabled = true;
217+
refs.swatchColorInput.disabled = true;
218+
refs.swatchNameInput.disabled = true;
219+
refs.swatchSymbolInput.disabled = true;
220+
refs.renamePaletteButton.disabled = true;
221+
refs.deletePaletteButton.disabled = true;
222+
refs.addSwatchButton.disabled = true;
223+
refs.deleteSwatchButton.disabled = true;
159224
refs.jsonPreview.textContent = "Palette JSON preview will appear here.";
160225
refs.validationText.textContent = "Validation summary will appear here.";
161226
return;
@@ -164,14 +229,23 @@ function renderSelectedPalette() {
164229
refs.paletteTitle.textContent = palette.name;
165230
refs.paletteSummaryText.textContent = `${palette.entries.length} swatches | source: ${palette.source}`;
166231
refs.paletteNameInput.value = palette.name;
167-
refs.paletteNameInput.disabled = !isCustomPalette(palette);
232+
const readOnly = isReadOnlyPalette(palette);
233+
const canOverrideDeleteGuard = hasDeleteOverrideParam();
234+
refs.paletteNameInput.disabled = readOnly;
235+
refs.swatchColorInput.disabled = readOnly;
236+
refs.swatchNameInput.disabled = readOnly;
237+
refs.swatchSymbolInput.disabled = readOnly;
238+
refs.renamePaletteButton.disabled = readOnly;
239+
refs.deletePaletteButton.disabled = canOverrideDeleteGuard ? false : readOnly;
240+
refs.addSwatchButton.disabled = readOnly;
241+
refs.deleteSwatchButton.disabled = readOnly;
168242

169243
refs.paletteSwatches.innerHTML = palette.entries.map((entry, index) => {
170244
const currentClass = index === state.selectedSwatchIndex ? " is-current" : "";
171245
return `
172246
<button type="button" data-swatch-index="${index}" class="${currentClass.trim()}">
173247
<span class="palette-browser__swatch-chip" style="background:${entry.hex}"></span>
174-
<strong>${entry.name || "Unnamed"}</strong>
248+
<strong class="palette-browser__swatch-name">${formatSwatchNameForDisplay(entry.name || "Unnamed")}</strong>
175249
<span>${entry.symbol || "-"}</span>
176250
</button>
177251
`;
@@ -215,7 +289,16 @@ function createCustomPalette(name, entries) {
215289
}
216290

217291
function createNewPalette() {
218-
const palette = createCustomPalette("new-palette", [
292+
const requestedName = window.prompt("Name for new palette:", "new-palette");
293+
if (requestedName === null) {
294+
return;
295+
}
296+
const nextName = requestedName.trim() || "new-palette";
297+
if (hasReservedPaletteKeyword(nextName)) {
298+
refs.validationText.textContent = "Palette name cannot include reserved terms: crayola, w3c, javascript.";
299+
return;
300+
}
301+
const palette = createCustomPalette(nextName, [
219302
{ symbol: "A", hex: "#ffffff", name: "White" }
220303
]);
221304
state.customPalettes.unshift(palette);
@@ -228,19 +311,39 @@ function duplicateSelectedPalette() {
228311
if (!palette) {
229312
return;
230313
}
231-
const duplicate = createCustomPalette(`${palette.name}-copy`, palette.entries);
314+
const suggestedName = `${palette.name}-copy`;
315+
let nextName = suggestedName;
316+
while (true) {
317+
const requestedName = window.prompt("Name for duplicated palette:", nextName);
318+
if (requestedName === null) {
319+
return;
320+
}
321+
nextName = requestedName.trim() || suggestedName;
322+
if (!hasReservedPaletteKeyword(nextName)) {
323+
break;
324+
}
325+
refs.validationText.textContent = "Palette name cannot include reserved terms: crayola, w3c, javascript.";
326+
}
327+
const duplicate = createCustomPalette(nextName, palette.entries);
232328
state.customPalettes.unshift(duplicate);
233329
saveCustomPalettes();
234330
setSelectedPalette(duplicate.id);
235331
}
236332

237333
function renameSelectedPalette() {
238334
const palette = getSelectedPalette();
239-
if (!isCustomPalette(palette)) {
335+
if (isReadOnlyPalette(palette)) {
240336
refs.validationText.textContent = "Duplicate a built-in palette before renaming it.";
241337
return;
242338
}
243-
palette.name = refs.paletteNameInput.value.trim() || palette.name;
339+
const requestedName = refs.paletteNameInput.value.trim() || palette.name;
340+
if (hasReservedPaletteKeyword(requestedName)) {
341+
refs.validationText.textContent = "Palette name cannot include reserved terms: crayola, w3c, javascript.";
342+
window.alert("Palette not renamed. Reserved terms are not allowed: crayola, w3c, javascript.");
343+
refs.paletteNameInput.value = palette.name;
344+
return;
345+
}
346+
palette.name = requestedName;
244347
saveCustomPalettes();
245348
renderPaletteList();
246349
renderSelectedPalette();
@@ -262,6 +365,26 @@ function addSwatchToSelectedPalette() {
262365
renderSelectedPalette();
263366
}
264367

368+
function updateSelectedSwatchFromInputs() {
369+
const palette = getSelectedPalette();
370+
if (isReadOnlyPalette(palette)) {
371+
return;
372+
}
373+
if (!palette || !Array.isArray(palette.entries) || palette.entries.length === 0) {
374+
return;
375+
}
376+
const index = Math.min(state.selectedSwatchIndex, palette.entries.length - 1);
377+
const entry = palette.entries[index];
378+
if (!entry) {
379+
return;
380+
}
381+
entry.hex = refs.swatchColorInput.value;
382+
entry.name = refs.swatchNameInput.value.trim() || "Unnamed";
383+
entry.symbol = refs.swatchSymbolInput.value.trim().slice(0, 2);
384+
saveCustomPalettes();
385+
renderSelectedPalette();
386+
}
387+
265388
function deleteSelectedSwatch() {
266389
const palette = getSelectedPalette();
267390
if (!isCustomPalette(palette)) {
@@ -333,6 +456,32 @@ function usePaletteInActiveTools() {
333456
: "Shared palette handoff was not updated because the payload was invalid.";
334457
}
335458

459+
function deleteSelectedPalette() {
460+
const palette = getSelectedPalette();
461+
if (!palette) {
462+
return;
463+
}
464+
const canOverrideDeleteGuard = hasDeleteOverrideParam();
465+
if (isReadOnlyPalette(palette) && !canOverrideDeleteGuard) {
466+
refs.validationText.textContent = "Built-in palettes cannot be deleted. Duplicate to create an editable copy.";
467+
return;
468+
}
469+
if (isCustomPalette(palette)) {
470+
state.customPalettes = state.customPalettes.filter((entry) => entry.id !== palette.id);
471+
saveCustomPalettes();
472+
} else if (canOverrideDeleteGuard) {
473+
if (!state.hiddenBuiltInPaletteIds.includes(palette.id)) {
474+
state.hiddenBuiltInPaletteIds.push(palette.id);
475+
saveHiddenBuiltInPaletteIds();
476+
}
477+
}
478+
const nextPalette = getVisiblePalettes()[0] ?? getAllPalettes()[0] ?? null;
479+
state.selectedPaletteId = nextPalette?.id ?? "";
480+
state.selectedSwatchIndex = 0;
481+
renderPaletteList();
482+
renderSelectedPalette();
483+
}
484+
336485
function renderStoredSelection() {
337486
const handoff = readSharedPaletteHandoff();
338487
if (!handoff) {
@@ -369,8 +518,12 @@ function bindEvents() {
369518
refs.newPaletteButton.addEventListener("click", createNewPalette);
370519
refs.duplicatePaletteButton.addEventListener("click", duplicateSelectedPalette);
371520
refs.renamePaletteButton.addEventListener("click", renameSelectedPalette);
521+
refs.deletePaletteButton.addEventListener("click", deleteSelectedPalette);
372522
refs.addSwatchButton.addEventListener("click", addSwatchToSelectedPalette);
373523
refs.deleteSwatchButton.addEventListener("click", deleteSelectedSwatch);
524+
refs.swatchColorInput.addEventListener("input", updateSelectedSwatchFromInputs);
525+
refs.swatchNameInput.addEventListener("input", updateSelectedSwatchFromInputs);
526+
refs.swatchSymbolInput.addEventListener("input", updateSelectedSwatchFromInputs);
374527
refs.copyPaletteJsonButton.addEventListener("click", copyPaletteJson);
375528
refs.exportPaletteJsonButton.addEventListener("click", exportPaletteJson);
376529
refs.usePaletteButton.addEventListener("click", usePaletteInActiveTools);

0 commit comments

Comments
 (0)