Release 0.5.9

This commit is contained in:
hnzio committed 2026-10-23 13:16:32 +02:00
1 parent 2851c7d936
commit c3033479b5
32 files changed
+1598 -50

No files matched your search

+110 -2
View File
@@ -62,12 +62,20 @@ export type LiveView = {
next: LiveUsage | null;
completedCount: number;
totalCount: number;
sectionTimer: null | {
sectionId: string;
sectionName: string;
plannedDurationSeconds: number;
elapsedSeconds: number;
runningSinceUtc: string | null;
};
};
export type PresentationView = {
revision: number;
content: null | {
mode: PresentationContent['mode'];
topics: string[];
title: string;
html: string;
source: PresentationContent['source'];
@@ -141,6 +149,59 @@ function closeActiveRun(database: Database.Database, showId: string, now: string
.run(now, showId);
}
function closeSectionTimer(database: Database.Database, showId: string, now: string) {
const timer = database
.prepare('SELECT id FROM live_section_timers WHERE show_id = ? AND ended_at_utc IS NULL')
.get(showId) as { id: string } | undefined;
if (!timer) return;
database
.prepare(
'UPDATE live_section_timer_runs SET ended_at_utc = ? WHERE timer_id = ? AND ended_at_utc IS NULL'
)
.run(now, timer.id);
database
.prepare('UPDATE live_section_timers SET ended_at_utc = ? WHERE id = ?')
.run(now, timer.id);
}
function syncSectionTimer(
database: Database.Database,
showId: string,
usageId: string,
now: string
) {
const section = database
.prepare(
`SELECT s.id, s.explicit_duration_seconds FROM piece_usages u
JOIN show_sections s ON s.id = u.show_section_id
WHERE u.id = ? AND s.show_id = ? AND s.is_reserve_bank = 0`
)
.get(usageId, showId) as { id: string; explicit_duration_seconds: number | null } | undefined;
if (!section) throw new Error('NOT_FOUND');
const active = database
.prepare(
'SELECT id, section_id FROM live_section_timers WHERE show_id = ? AND ended_at_utc IS NULL'
)
.get(showId) as { id: string; section_id: string } | undefined;
if (active?.section_id === section.id) return;
if (active) closeSectionTimer(database, showId, now);
if (!section.explicit_duration_seconds || section.explicit_duration_seconds <= 0) return;
const timerId = randomUUID();
database
.prepare(
`INSERT INTO live_section_timers
(id, show_id, section_id, planned_duration_seconds, started_at_utc)
VALUES (?, ?, ?, ?, ?)`
)
.run(timerId, showId, section.id, section.explicit_duration_seconds, now);
database
.prepare(
`INSERT INTO live_section_timer_runs (id, timer_id, started_at_utc)
VALUES (?, ?, ?)`
)
.run(randomUUID(), timerId, now);
}
function activateUsage(database: Database.Database, showId: string, usageId: string, now: string) {
usageBelongsToShow(database, showId, usageId);
database
@@ -164,6 +225,7 @@ function activateUsage(database: Database.Database, showId: string, usageId: str
'UPDATE show_live_states SET current_usage_id = ?, ended_at_utc = NULL WHERE show_id = ?'
)
.run(usageId, showId);
syncSectionTimer(database, showId, usageId, now);
}
function executeCommand(
@@ -268,6 +330,44 @@ export function getLiveView(showId: string, nowValue?: Date): LiveView {
const next =
usages.slice(currentIndex + 1).find((usage) => usage.status === 'open') ??
(current ? null : (usages.find((usage) => usage.status === 'open') ?? null));
const timer = database
.prepare(
`SELECT t.id, t.section_id, s.name AS section_name, t.planned_duration_seconds
FROM live_section_timers t JOIN show_sections s ON s.id = t.section_id
WHERE t.show_id = ? AND t.ended_at_utc IS NULL`
)
.get(showId) as
| { id: string; section_id: string; section_name: string; planned_duration_seconds: number }
| undefined;
let sectionTimer: LiveView['sectionTimer'] = null;
if (timer) {
const runs = database
.prepare(
`SELECT started_at_utc, ended_at_utc FROM live_section_timer_runs
WHERE timer_id = ? ORDER BY started_at_utc`
)
.all(timer.id) as { started_at_utc: string; ended_at_utc: string | null }[];
const running = runs.find((run) => run.ended_at_utc === null) ?? null;
const elapsedSeconds = runs.reduce(
(total, run) =>
total +
Math.max(
0,
Math.floor(
(new Date(run.ended_at_utc ?? now).getTime() - new Date(run.started_at_utc).getTime()) /
1_000
)
),
0
);
sectionTimer = {
sectionId: timer.section_id,
sectionName: timer.section_name,
plannedDurationSeconds: timer.planned_duration_seconds,
elapsedSeconds,
runningSinceUtc: running?.started_at_utc ?? null
};
}
return {
show: {
id: show.id,
@@ -287,7 +387,8 @@ export function getLiveView(showId: string, nowValue?: Date): LiveView {
next,
completedCount: usages.filter((usage) => usage.status === 'done' || usage.status === 'skipped')
.length,
totalCount: usages.length
totalCount: usages.length,
sectionTimer
};
}
@@ -353,10 +454,12 @@ function advanceCurrent(
.run(status, state.current_usage_id);
const next = ordered.slice(currentIndex + 1).find((usage) => usage.status === 'open');
if (next) activateUsage(database, showId, next.id, now);
else
else {
closeSectionTimer(database, showId, now);
database
.prepare('UPDATE show_live_states SET current_usage_id = NULL WHERE show_id = ?')
.run(showId);
}
},
nowValue
);
@@ -655,6 +758,11 @@ function getPresentationViewForShow(showId: string): PresentationView {
revision: state.presentation_revision,
content: {
mode: usage.presentation_mode_snapshot,
topics: (
database
.prepare('SELECT name FROM usage_topic_snapshots WHERE usage_id = ? ORDER BY position')
.all(state.presentation_usage_id) as { name: string }[]
).map((topic) => topic.name),
title: usage.output_title_snapshot || usage.title_snapshot,
html: renderMarkdown(usage.presentation_snapshot),
source: usage.presentation_source_url_snapshot