Timers: "Group by source" on the All timers tile, off by default
Off, every timer sits in one grid filled row by row — a wrapping flex row rather than a CSS grid, so the last row's cards widen to use the whole width and nothing is left empty; a source in trouble is named in one line above the cards, and its own cards dim. On, the sections per source come back, and "Source headings" appears (showWhen) to hide their titles. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
96eff1a5c5
commit
dde9a00ee1
@@ -64,6 +64,7 @@ const LTC_KEY = 'ltc:ltc';
|
|||||||
the same values module.json gives the full "All timers" schema. */
|
the same values module.json gives the full "All timers" schema. */
|
||||||
const DEFAULTS = {
|
const DEFAULTS = {
|
||||||
showNames: true,
|
showNames: true,
|
||||||
|
groupBySource: false,
|
||||||
showHeadings: true,
|
showHeadings: true,
|
||||||
showStatus: true,
|
showStatus: true,
|
||||||
showDetail: true,
|
showDetail: true,
|
||||||
@@ -391,7 +392,18 @@ export default function create({ root, moduleApi }) {
|
|||||||
if (!items.length && source.timers.length) continue;
|
if (!items.length && source.timers.length) continue;
|
||||||
groups.push({ id: source.id, source, items });
|
groups.push({ id: source.id, source, items });
|
||||||
}
|
}
|
||||||
return groups;
|
if (pref('groupBySource') === true) return groups;
|
||||||
|
// Grouping off: every timer in one grid, filled row by row. A source in
|
||||||
|
// trouble is still named, in one line above the cards.
|
||||||
|
const trouble = groups.filter((g) => g.source && g.source.status !== 'ok');
|
||||||
|
return [{
|
||||||
|
id: 'all',
|
||||||
|
source: null,
|
||||||
|
flat: true,
|
||||||
|
items: groups.flatMap((g) => g.items),
|
||||||
|
status: trouble.length ? 'error' : 'ok',
|
||||||
|
message: trouble.map((g) => `${g.source.label}: ${g.source.message || 'unavailable'}`).join(' · '),
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── cards ──────────────────────────────────────────────────────── */
|
/* ── cards ──────────────────────────────────────────────────────── */
|
||||||
@@ -677,7 +689,8 @@ export default function create({ root, moduleApi }) {
|
|||||||
setText(card.detailEl, detailText);
|
setText(card.detailEl, detailText);
|
||||||
card.detailEl.hidden = !detailText || !showDetail;
|
card.detailEl.hidden = !detailText || !showDetail;
|
||||||
const kindCls = source.kind === 'ltc' ? ' tm-card-ltc' : source.kind === 'clock' ? ' tm-card-clock' : '';
|
const kindCls = source.kind === 'ltc' ? ' tm-card-ltc' : source.kind === 'clock' ? ' tm-card-clock' : '';
|
||||||
setCardClass(card, `tm-card is-${tone}${extra}${kindCls}`);
|
const stale = source.status === 'error' ? ' is-stale' : '';
|
||||||
|
setCardClass(card, `tm-card is-${tone}${extra}${kindCls}${stale}`);
|
||||||
}
|
}
|
||||||
if (charsChanged) fit();
|
if (charsChanged) fit();
|
||||||
}
|
}
|
||||||
@@ -748,7 +761,8 @@ export default function create({ root, moduleApi }) {
|
|||||||
wrap.classList.toggle('tm-mono', mono);
|
wrap.classList.toggle('tm-mono', mono);
|
||||||
needFit = true;
|
needFit = true;
|
||||||
}
|
}
|
||||||
const showHeadings = !solo && pref('showHeadings') !== false;
|
// Headings belong to sections; without grouping there is one grid and no heading.
|
||||||
|
const showHeadings = !solo && pref('groupBySource') === true && pref('showHeadings') !== false;
|
||||||
wrap.classList.toggle('tm-headings', showHeadings);
|
wrap.classList.toggle('tm-headings', showHeadings);
|
||||||
for (const [cls, on] of [
|
for (const [cls, on] of [
|
||||||
['tm-nostatus', pref('showStatus') === false],
|
['tm-nostatus', pref('showStatus') === false],
|
||||||
@@ -781,18 +795,24 @@ export default function create({ root, moduleApi }) {
|
|||||||
structureChanged = true;
|
structureChanged = true;
|
||||||
}
|
}
|
||||||
const source = g.source;
|
const source = g.source;
|
||||||
const status = source ? source.status : 'ok';
|
const status = g.status ?? (source ? source.status : 'ok');
|
||||||
const message = source ? source.message : '';
|
const message = g.message ?? (source ? source.message : '');
|
||||||
ge.headEl.hidden = !showHeadings;
|
ge.headEl.hidden = !showHeadings;
|
||||||
setText(ge.titleEl, source ? source.label : 'Timer');
|
setText(ge.titleEl, source ? source.label : 'Timers');
|
||||||
setText(ge.noteEl, status !== 'ok' ? message : g.items.length ? '' : 'no timers');
|
setText(ge.noteEl, status !== 'ok' ? message : g.items.length ? '' : 'no timers');
|
||||||
// Without headings the source's trouble still has to be said somewhere.
|
// Without headings the source's trouble still has to be said somewhere.
|
||||||
const emptyText = status !== 'ok' ? message : 'No timers';
|
const emptyText = status !== 'ok' ? message : 'No timers';
|
||||||
const showEmpty = !showHeadings && (status !== 'ok' || !g.items.length);
|
const showEmpty = !showHeadings && (status !== 'ok' || !g.items.length);
|
||||||
ge.emptyEl.hidden = !showEmpty;
|
ge.emptyEl.hidden = !showEmpty;
|
||||||
setText(ge.emptyEl, showEmpty ? emptyText : '');
|
setText(ge.emptyEl, showEmpty ? emptyText : '');
|
||||||
ge.el.classList.toggle('is-down', status !== 'ok');
|
ge.emptyEl.dataset.tone = showEmpty && status !== 'ok' ? 'warn' : '';
|
||||||
ge.el.classList.toggle('is-stale', status === 'error' && g.items.length > 0);
|
// In the flat grid a card dims for its own source (paint), not the whole grid.
|
||||||
|
ge.el.classList.toggle('is-down', !g.flat && status !== 'ok');
|
||||||
|
ge.el.classList.toggle('is-stale', !g.flat && status === 'error' && g.items.length > 0);
|
||||||
|
if (ge.gridEl.classList.contains('is-flow') !== Boolean(g.flat)) {
|
||||||
|
ge.gridEl.classList.toggle('is-flow', Boolean(g.flat));
|
||||||
|
structureChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
const els = [];
|
const els = [];
|
||||||
for (const item of g.items) {
|
for (const item of g.items) {
|
||||||
@@ -920,6 +940,7 @@ export default function create({ root, moduleApi }) {
|
|||||||
if (!best) return;
|
if (!best) return;
|
||||||
for (const g of groups) {
|
for (const g of groups) {
|
||||||
g.gridEl.style.gridTemplateColumns = `repeat(${best.cols}, 1fr)`;
|
g.gridEl.style.gridTemplateColumns = `repeat(${best.cols}, 1fr)`;
|
||||||
|
g.gridEl.style.setProperty('--tm-cols', String(best.cols));
|
||||||
// Space is shared in proportion to each group's rows, so a card is
|
// Space is shared in proportion to each group's rows, so a card is
|
||||||
// the same height under every heading.
|
// the same height under every heading.
|
||||||
g.el.style.flexGrow = String(Math.max(1, Math.ceil(g.count / best.cols)));
|
g.el.style.flexGrow = String(Math.max(1, Math.ceil(g.count / best.cols)));
|
||||||
|
|||||||
@@ -156,11 +156,18 @@
|
|||||||
"default": false,
|
"default": false,
|
||||||
"group": "Show"
|
"group": "Show"
|
||||||
},
|
},
|
||||||
|
"groupBySource": {
|
||||||
|
"type": "switch",
|
||||||
|
"label": "Group by source",
|
||||||
|
"default": false,
|
||||||
|
"group": "Show"
|
||||||
|
},
|
||||||
"showHeadings": {
|
"showHeadings": {
|
||||||
"type": "switch",
|
"type": "switch",
|
||||||
"label": "Source headings",
|
"label": "Source headings",
|
||||||
"default": true,
|
"default": true,
|
||||||
"group": "Show"
|
"group": "Show",
|
||||||
|
"showWhen": "groupBySource"
|
||||||
},
|
},
|
||||||
"clock24h": {
|
"clock24h": {
|
||||||
"type": "switch",
|
"type": "switch",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
visually, per group: the LTC card keeps ticking when only ProPresenter
|
visually, per group: the LTC card keeps ticking when only ProPresenter
|
||||||
is unreachable. When the ProdDash feed is down everything is old. */
|
is unreachable. When the ProdDash feed is down everything is old. */
|
||||||
[data-module="propresenter-timers"] .tm-group.is-stale .tm-card,
|
[data-module="propresenter-timers"] .tm-group.is-stale .tm-card,
|
||||||
|
[data-module="propresenter-timers"] .tm-card.is-stale,
|
||||||
[data-module="propresenter-timers"] .tm-wrap.is-feed-down .tm-card { opacity: 0.45; }
|
[data-module="propresenter-timers"] .tm-wrap.is-feed-down .tm-card { opacity: 0.45; }
|
||||||
|
|
||||||
[data-module="propresenter-timers"] .tm-hint {
|
[data-module="propresenter-timers"] .tm-hint {
|
||||||
@@ -126,6 +127,21 @@
|
|||||||
}
|
}
|
||||||
[data-module="propresenter-timers"] .tm-grid:empty { display: none; }
|
[data-module="propresenter-timers"] .tm-grid:empty { display: none; }
|
||||||
|
|
||||||
|
/* Grouping off: one grid for every timer, filled row by row. A wrapping
|
||||||
|
flex row instead of a CSS grid, so the last row's cards widen to use the
|
||||||
|
whole width — nothing is left empty. Rows share the height equally. */
|
||||||
|
[data-module="propresenter-timers"] .tm-grid.is-flow {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: stretch;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
[data-module="propresenter-timers"] .tm-grid.is-flow .tm-card {
|
||||||
|
flex: 1 1 calc((100% - var(--tm-gap, 8px) * (var(--tm-cols, 1) - 1)) / var(--tm-cols, 1));
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
[data-module="propresenter-timers"] .tm-empty[data-tone="warn"] { color: var(--tm-warn); }
|
||||||
|
|
||||||
/* Compact cards (short tiles) and "Status lines" off: the lines under the
|
/* Compact cards (short tiles) and "Status lines" off: the lines under the
|
||||||
digits go, the colour stays. A note that explains a missing value (no
|
digits go, the colour stays. A note that explains a missing value (no
|
||||||
LTC input, a solo timer no longer offered) must never be hidden. */
|
LTC input, a solo timer no longer offered) must never be hidden. */
|
||||||
|
|||||||
Reference in New Issue
Block a user