Shell: every tile can be tinted from its gear
A colour picker at the foot of every gear window washes that tile's chrome — panel, border and title bar — in a colour of the operator's choosing, so one screen's tiles can be told apart at a glance. "Clear" takes it off again. The value saves with the layout under the reserved key __tint; it is the shell's, not a module's, so picking a colour paints the tile without remounting the module, and every tile now has a gear even when its module offers no settings of its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f29e43373b
commit
720e5bb590
+59
-3
@@ -261,6 +261,7 @@ function buildTile(tile) {
|
||||
el.className = 'tile';
|
||||
el.dataset.tileId = tile.id;
|
||||
if (man) el.dataset.module = man.id;
|
||||
applyTint(tile, el);
|
||||
|
||||
const head = document.createElement('div');
|
||||
head.className = 'tile-head';
|
||||
@@ -279,8 +280,9 @@ function buildTile(tile) {
|
||||
|
||||
head.append(dot, title, controls);
|
||||
|
||||
const hasSettings = man && Object.keys(tileSchema(tile, man).schema).length;
|
||||
if (hasSettings) {
|
||||
// Every tile has something to set: its own settings when the module
|
||||
// offers any, and the tint either way.
|
||||
if (man) {
|
||||
const gear = document.createElement('button');
|
||||
gear.className = 'tile-btn';
|
||||
gear.title = 'Settings';
|
||||
@@ -1133,6 +1135,23 @@ function unmountModule(tile) {
|
||||
/* ── per-tile settings popover ──────────────────────────────────────── */
|
||||
|
||||
/** `<input type="color">` only accepts #rrggbb — anything else falls back. */
|
||||
/* ── the tile tint ───────────────────────────────────────────────────
|
||||
Every tile can be washed in a colour of its own — a way to tell one
|
||||
screen's tiles apart at a glance (this campus green, that one amber).
|
||||
It is the shell's, not a module's: the key is reserved, it is offered in
|
||||
every gear window, and it saves with the layout like any other tile
|
||||
setting. A tile with no tint looks exactly as before. */
|
||||
const TINT_KEY = '__tint';
|
||||
|
||||
/** Paint (or clear) a tile's tint on its element. */
|
||||
function applyTint(tile, el = tileEls.get(tile.id)?.el) {
|
||||
if (!el) return;
|
||||
const hex = colorHex(tile.settings?.[TINT_KEY], '');
|
||||
if (hex) el.style.setProperty('--tile-tint', hex);
|
||||
else el.style.removeProperty('--tile-tint');
|
||||
el.classList.toggle('is-tinted', Boolean(hex));
|
||||
}
|
||||
|
||||
function colorHex(value, fallback) {
|
||||
const s = String(value ?? '').trim();
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(s)) return s.toLowerCase();
|
||||
@@ -1267,8 +1286,8 @@ function toggleSettingsPopover(tile, el, button = null) {
|
||||
closeSettingsPopovers();
|
||||
if (existing) return;
|
||||
const man = registry.get(tile.module);
|
||||
if (!man) return;
|
||||
const { schema, groups: groupMeta } = tileSchema(tile, man);
|
||||
if (!man || !Object.keys(schema).length) return;
|
||||
|
||||
const pop = document.createElement('div');
|
||||
pop.className = 'tile-settings';
|
||||
@@ -1588,6 +1607,43 @@ function toggleSettingsPopover(tile, el, button = null) {
|
||||
}
|
||||
}
|
||||
|
||||
/* The tint sits on its own at the foot of the window, under a hairline —
|
||||
it is the shell's setting, not one of the module's. Unlike those, a
|
||||
change needs no remount: nothing but the tile's own chrome reads it. */
|
||||
{
|
||||
const field = document.createElement('div');
|
||||
field.className = 'field color-field tint-field'; // the swatch reads like a module's colour field
|
||||
const text = document.createElement('span');
|
||||
text.textContent = 'Tile tint';
|
||||
const input = document.createElement('input');
|
||||
input.type = 'color';
|
||||
input.setAttribute('aria-label', 'Tile tint');
|
||||
const clear = document.createElement('button');
|
||||
clear.type = 'button';
|
||||
clear.className = 'tint-clear';
|
||||
clear.textContent = 'Clear';
|
||||
clear.title = 'No tint';
|
||||
const paint = () => {
|
||||
const hex = colorHex(tile.settings[TINT_KEY], '');
|
||||
// With no tint the swatch opens on the panel colour, so the picker
|
||||
// starts somewhere sane rather than at black.
|
||||
input.value = hex || colorHex(getComputedStyle(document.documentElement).getPropertyValue('--panel'), '#1a1d21');
|
||||
clear.hidden = !hex;
|
||||
};
|
||||
const set = (hex) => {
|
||||
if (hex) tile.settings[TINT_KEY] = hex;
|
||||
else delete tile.settings[TINT_KEY];
|
||||
applyTint(tile);
|
||||
paint();
|
||||
saveLayout();
|
||||
};
|
||||
input.addEventListener('input', () => set(colorHex(input.value, '')));
|
||||
clear.addEventListener('click', () => set(''));
|
||||
paint();
|
||||
field.append(text, clear, input);
|
||||
body.appendChild(field);
|
||||
}
|
||||
|
||||
// "Short day" appears once "Date" is on: a field with showWhen:"K" follows
|
||||
// K's switch, live — the same rule the admin page applies.
|
||||
if (shownWhen.length) {
|
||||
|
||||
@@ -234,6 +234,18 @@ body.menu-hidden #topbar {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* A tile the user gave a colour: a wash over the panel, a stronger edge and
|
||||
a tinted title bar. Module content keeps its own colours — this is the
|
||||
tile's chrome, so a tinted tile still reads like every other one. */
|
||||
.tile.is-tinted {
|
||||
background: color-mix(in srgb, var(--tile-tint) 14%, var(--panel));
|
||||
border-color: color-mix(in srgb, var(--tile-tint) 45%, var(--border));
|
||||
}
|
||||
.tile.is-tinted > .tile-head {
|
||||
background: color-mix(in srgb, var(--tile-tint) 20%, transparent);
|
||||
border-bottom-color: color-mix(in srgb, var(--tile-tint) 35%, var(--border));
|
||||
}
|
||||
|
||||
.tile.dragging,
|
||||
.tile.resizing {
|
||||
z-index: 40;
|
||||
@@ -599,6 +611,26 @@ body.fullscreen #grid { padding-bottom: 0; }
|
||||
}
|
||||
.tile-settings-bar:hover::before { opacity: 1; }
|
||||
.tile-settings .field[hidden] { display: none; }
|
||||
/* the tint, the shell's own setting, under a hairline at the foot */
|
||||
.tile-settings .tint-field {
|
||||
margin: 8px 0 0;
|
||||
padding-top: 9px;
|
||||
border-top: 1px solid var(--border);
|
||||
gap: 8px;
|
||||
}
|
||||
.tile-settings .tint-clear {
|
||||
flex: none;
|
||||
padding: 1px 7px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
background: none;
|
||||
color: var(--muted);
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tile-settings .tint-clear:hover { color: var(--text); background: var(--border); }
|
||||
.tile-settings .tint-clear[hidden] { display: none; }
|
||||
.tile-settings-bar:active { cursor: grabbing; }
|
||||
.tile-settings-body {
|
||||
flex: 1;
|
||||
|
||||
Reference in New Issue
Block a user