Timers: "to 9:30a" under a countdown-to-time, smaller status lines, titles that fit
A ProPresenter count_down_to_time timer's detail line reads "to 9:30a" (or "to 21:30" on a 24-hour clock) instead of a sentence. The status and detail lines under the digits are drawn at 0.8 of the title's size, and fit() budgets them at that share. A title that would be cut off shrinks to fit its card (measured off-screen, down to 8 px) instead of ending in an ellipsis; a changed label re-fits. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
0acaf47ac7
commit
1db6c2c89f
@@ -428,7 +428,11 @@ export default function create({ root, moduleApi }) {
|
|||||||
} else if (!isLtc && badge) {
|
} else if (!isLtc && badge) {
|
||||||
badge.remove();
|
badge.remove();
|
||||||
}
|
}
|
||||||
setText(card.labelEl, item.missing ? (lastSoloLabel || 'Timer') : item.timer.label);
|
const label = item.missing ? (lastSoloLabel || 'Timer') : item.timer.label;
|
||||||
|
if (card.labelEl.textContent !== label) {
|
||||||
|
setText(card.labelEl, label);
|
||||||
|
needFit = true; // the title's size follows its length
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMissingCard(card) {
|
function updateMissingCard(card) {
|
||||||
@@ -772,6 +776,27 @@ export default function create({ root, moduleApi }) {
|
|||||||
|
|
||||||
/* ── sizing: the column count that gives the biggest digits ─────── */
|
/* ── sizing: the column count that gives the biggest digits ─────── */
|
||||||
|
|
||||||
|
/** Status and detail lines are drawn at this share of the label's size (style.css agrees). */
|
||||||
|
const LINE_SCALE = 0.8;
|
||||||
|
|
||||||
|
/* The title fits its card: measured off-screen, it shrinks (down to a
|
||||||
|
floor) before the card would cut it off. Uppercase and letter-spaced
|
||||||
|
like the CSS draws it. */
|
||||||
|
const measurer = document.createElement('canvas').getContext('2d');
|
||||||
|
let labelFamily = '';
|
||||||
|
function labelWidth(text, px) {
|
||||||
|
if (!labelFamily) labelFamily = getComputedStyle(root).fontFamily || 'sans-serif';
|
||||||
|
measurer.font = `700 ${px}px ${labelFamily}`;
|
||||||
|
const t = String(text || '').toUpperCase();
|
||||||
|
return measurer.measureText(t).width + 0.08 * px * Math.max(0, t.length - 1);
|
||||||
|
}
|
||||||
|
function fitLabel(card, meta, avail) {
|
||||||
|
const room = Math.max(24, avail - (card.nameEl.querySelector('.tm-ltc-badge') ? 34 : 0));
|
||||||
|
const natural = labelWidth(card.labelEl.textContent, meta);
|
||||||
|
const px = natural > room ? Math.max(8, Math.floor((meta * room) / natural)) : meta;
|
||||||
|
card.labelEl.style.fontSize = px === meta ? '' : `${px}px`;
|
||||||
|
}
|
||||||
|
|
||||||
function fit() {
|
function fit() {
|
||||||
const list = [...cards.values()];
|
const list = [...cards.values()];
|
||||||
const n = list.length;
|
const n = list.length;
|
||||||
@@ -803,7 +828,7 @@ export default function create({ root, moduleApi }) {
|
|||||||
const ch = (H - PAD * 2 - headings * HEAD_H - GAP * (rows - 1) - (headings ? (headings - 1) * GAP : 0)) / rows - CARD_PAD;
|
const ch = (H - PAD * 2 - headings * HEAD_H - GAP * (rows - 1) - (headings ? (headings - 1) * GAP : 0)) / rows - CARD_PAD;
|
||||||
if (cw <= 0 || ch <= 0) continue;
|
if (cw <= 0 || ch <= 0) continue;
|
||||||
const meta = Math.max(9, Math.min(22, Math.floor(ch * 0.16)));
|
const meta = Math.max(9, Math.min(22, Math.floor(ch * 0.16)));
|
||||||
const fullBudget = ch - (1 + metaLines) * 1.3 * meta;
|
const fullBudget = ch - 1.3 * meta - metaLines * 1.3 * meta * LINE_SCALE;
|
||||||
const compactBudget = ch - 1.3 * meta;
|
const compactBudget = ch - 1.3 * meta;
|
||||||
let full = Infinity;
|
let full = Infinity;
|
||||||
let compact = Infinity;
|
let compact = Infinity;
|
||||||
@@ -831,6 +856,7 @@ export default function create({ root, moduleApi }) {
|
|||||||
for (const c of list) {
|
for (const c of list) {
|
||||||
const px = Math.max(11, Math.floor(Math.min((best.cw * 1.55) / c.chars, best.budget)));
|
const px = Math.max(11, Math.floor(Math.min((best.cw * 1.55) / c.chars, best.budget)));
|
||||||
c.timeEl.style.fontSize = px + 'px';
|
c.timeEl.style.fontSize = px + 'px';
|
||||||
|
fitLabel(c, best.meta, best.cw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,14 +171,14 @@ function fmtMs(ms) {
|
|||||||
return h ? `${h}:${pad(m)}:${pad(sec)}` : `${m}:${pad(sec)}`;
|
return h ? `${h}:${pad(m)}:${pad(sec)}` : `${m}:${pad(sec)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ProPresenter's count_down_to_time target: seconds since midnight + am/pm/24h. */
|
/** ProPresenter's count_down_to_time target, short: "9:30a", "12:05p", or "21:30" for a 24-hour clock. */
|
||||||
function timeOfDayText(seconds, period) {
|
function timeOfDayText(seconds, period) {
|
||||||
const s = Number(seconds);
|
const s = Number(seconds);
|
||||||
if (!Number.isFinite(s)) return '';
|
if (!Number.isFinite(s)) return '';
|
||||||
const h = Math.floor(s / 3600) % 24;
|
const h = Math.floor(s / 3600) % 24;
|
||||||
const m = Math.floor((s % 3600) / 60);
|
const m = Math.floor((s % 3600) / 60);
|
||||||
const p = String(period || '').toLowerCase();
|
const p = String(period || '').toLowerCase();
|
||||||
if (p === 'am' || p === 'pm') return `${h % 12 || 12}:${String(m).padStart(2, '0')} ${p}`;
|
if (p === 'am' || p === 'pm') return `${h % 12 || 12}:${String(m).padStart(2, '0')}${p[0]}`;
|
||||||
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +278,7 @@ function createProPresenterSource({ config, log }) {
|
|||||||
}
|
}
|
||||||
if (entry.count_down_to_time) {
|
if (entry.count_down_to_time) {
|
||||||
const at = timeOfDayText(entry.count_down_to_time.time_of_day, entry.count_down_to_time.period);
|
const at = timeOfDayText(entry.count_down_to_time.time_of_day, entry.count_down_to_time.period);
|
||||||
return { type: 'countdown_to_time', targetMs: null, detail: at ? `Counts down to ${at}` : 'Counts down to a time of day' };
|
return { type: 'countdown_to_time', targetMs: null, detail: at ? `to ${at}` : 'to a time of day' };
|
||||||
}
|
}
|
||||||
if (entry.elapsed) {
|
if (entry.elapsed) {
|
||||||
const end = Number(entry.elapsed.end_time);
|
const end = Number(entry.elapsed.end_time);
|
||||||
|
|||||||
@@ -196,7 +196,7 @@
|
|||||||
[data-module="propresenter-timers"] .tm-detail {
|
[data-module="propresenter-timers"] .tm-detail {
|
||||||
flex: none;
|
flex: none;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
font-size: var(--tm-meta-size);
|
font-size: calc(var(--tm-meta-size) * 0.8); /* smaller than the title; fit() budgets the same share */
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|||||||
Reference in New Issue
Block a user