PALETTE: swatch preview + tooltip + OK commit — TDD
Clicking a swatch instantly swaps the body palette class for a live preview; OK commits silently (POST, no reload); click-elsewhere or 10 s auto-dismiss reverts. Tooltip portal shows label, shoptalk, lock state. Locked swatches show × (disabled). 20 FTs green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,7 @@
|
||||
// console.log("apps/scripts/dashboard.js loading");
|
||||
const initialize = (inputSelector) => {
|
||||
// console.log("initialize called!");
|
||||
const textInput = document.querySelector(inputSelector);
|
||||
if (!textInput) return;
|
||||
textInput.oninput = () => {
|
||||
// console.log("oninput triggered");
|
||||
textInput.classList.remove("is-invalid");
|
||||
};
|
||||
textInput.oninput = () => textInput.classList.remove("is-invalid");
|
||||
};
|
||||
|
||||
const bindPaletteWheel = () => {
|
||||
@@ -18,6 +13,125 @@ const bindPaletteWheel = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// ── Palette swatch preview + commit ──────────────────────────────────────────
|
||||
|
||||
const bindPaletteSwatches = () => {
|
||||
const portal = document.getElementById('id_tooltip_portal');
|
||||
let activePreview = null;
|
||||
let originalPalette = null;
|
||||
let dismissTimer = null;
|
||||
|
||||
function currentBodyPalette() {
|
||||
return [...document.body.classList].find(c => c.startsWith('palette-'));
|
||||
}
|
||||
|
||||
function swapPalette(paletteName) {
|
||||
const old = currentBodyPalette();
|
||||
if (old) document.body.classList.remove(old);
|
||||
document.body.classList.add(paletteName);
|
||||
}
|
||||
|
||||
function showTooltip(swatch) {
|
||||
if (!portal) return;
|
||||
const label = swatch.dataset.label || '';
|
||||
const locked = swatch.dataset.locked === 'true';
|
||||
const date = swatch.dataset.unlockedDate || '';
|
||||
const shoptalk = swatch.dataset.shoptalk || '';
|
||||
const lockIcon = locked ? 'fa-lock' : 'fa-lock-open';
|
||||
const lockText = locked ? 'Locked' : `Unlocked — ${date}`.trim();
|
||||
|
||||
portal.innerHTML = `
|
||||
<h4 class="tt-title">${label}</h4>
|
||||
${shoptalk ? `<p class="tt-shoptalk"><em>${shoptalk}</em></p>` : ''}
|
||||
<p class="tt-lock"><i class="fa-solid ${lockIcon}"></i> ${lockText}</p>`;
|
||||
|
||||
const rect = swatch.getBoundingClientRect();
|
||||
portal.style.display = 'block';
|
||||
portal.style.position = 'fixed';
|
||||
portal.style.top = `${rect.bottom + 8}px`;
|
||||
portal.style.left = `${Math.min(rect.left, window.innerWidth - 280)}px`;
|
||||
portal.style.zIndex = '9999';
|
||||
}
|
||||
|
||||
function hideTooltip() {
|
||||
if (!portal) return;
|
||||
portal.style.display = 'none';
|
||||
portal.innerHTML = '';
|
||||
}
|
||||
|
||||
function dismiss() {
|
||||
if (!activePreview) return;
|
||||
clearTimeout(dismissTimer);
|
||||
const paletteName = activePreview.dataset.palette;
|
||||
activePreview.classList.remove('previewing');
|
||||
activePreview.querySelector('.palette-ok').style.display = '';
|
||||
document.body.classList.remove(paletteName);
|
||||
if (originalPalette) document.body.classList.add(originalPalette);
|
||||
activePreview = null;
|
||||
originalPalette = null;
|
||||
hideTooltip();
|
||||
}
|
||||
|
||||
async function commitPalette(swatch, paletteName) {
|
||||
// Silent commit — no animation, wipe already happened on preview
|
||||
const old = originalPalette;
|
||||
swatch.classList.remove('previewing');
|
||||
swatch.querySelector('.palette-ok').style.display = '';
|
||||
hideTooltip();
|
||||
activePreview = null;
|
||||
originalPalette = null;
|
||||
clearTimeout(dismissTimer);
|
||||
|
||||
// Remove old palette, keep new one (already on body from preview)
|
||||
if (old && old !== paletteName) {
|
||||
document.body.classList.remove(old);
|
||||
}
|
||||
|
||||
// Update active indicator
|
||||
document.querySelectorAll('.swatch').forEach(sw => {
|
||||
sw.classList.toggle('active', sw.classList.contains(paletteName));
|
||||
});
|
||||
|
||||
// POST to server
|
||||
const csrf = document.cookie.match(/csrftoken=([^;]+)/)?.[1] || '';
|
||||
await fetch('/dashboard/set_palette', {
|
||||
method: 'POST',
|
||||
headers: { 'Accept': 'application/json', 'X-CSRFToken': csrf },
|
||||
body: new URLSearchParams({ palette: paletteName }),
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.palette-item .swatch').forEach(swatch => {
|
||||
swatch.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
if (swatch.classList.contains('previewing')) return;
|
||||
|
||||
dismiss(); // clear any existing preview
|
||||
|
||||
originalPalette = currentBodyPalette();
|
||||
activePreview = swatch;
|
||||
|
||||
swatch.classList.add('previewing');
|
||||
showTooltip(swatch);
|
||||
swapPalette(swatch.dataset.palette);
|
||||
swatch.querySelector('.palette-ok').style.display = 'flex';
|
||||
|
||||
// Auto-dismiss after 10s
|
||||
dismissTimer = setTimeout(dismiss, 10000);
|
||||
});
|
||||
|
||||
const okBtn = swatch.querySelector('.btn-confirm.palette-ok');
|
||||
if (okBtn) {
|
||||
okBtn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
await commitPalette(swatch, swatch.dataset.palette);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', () => dismiss());
|
||||
};
|
||||
|
||||
const bindPaletteForms = () => {
|
||||
document.querySelectorAll('form[action*="set_palette"]').forEach(form => {
|
||||
form.addEventListener("submit", async (e) => {
|
||||
@@ -29,12 +143,10 @@ const bindPaletteForms = () => {
|
||||
});
|
||||
if (!resp.ok) return;
|
||||
const { palette } = await resp.json();
|
||||
// Swap body palette class
|
||||
[...document.body.classList]
|
||||
.filter(c => c.startsWith("palette-"))
|
||||
.forEach(c => document.body.classList.remove(c));
|
||||
document.body.classList.add(palette);
|
||||
// Update active swatch indicator
|
||||
document.querySelectorAll(".swatch").forEach(sw => {
|
||||
sw.classList.toggle("active", sw.classList.contains(palette));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user