Refine live control and show overview
This commit is contained in:
1 parent
ec596d442e
commit
b1474bc33d
16 files changed
+1580
-405
No files matched your search
+319
-179
@@ -11,23 +11,38 @@
|
||||
let promptSize = $state(1.55);
|
||||
let showRail = $state(true);
|
||||
let mobileRailOpen = $state(false);
|
||||
let isMobile = $state(false);
|
||||
let showTimes = $state(true);
|
||||
let menuOpen = $state(false);
|
||||
let expandedContext = $state<'section' | 'next' | null>(null);
|
||||
let popupMessage = $state('');
|
||||
let now = $state(Date.now());
|
||||
let loadedAt = $state(Date.now());
|
||||
let currentId = $state(untrack(() => data.live.current?.id ?? null));
|
||||
let loadedRevision = $state(untrack(() => data.live.liveRevision));
|
||||
let viewedUsageId = $state(untrack(() => data.live.current?.id ?? null));
|
||||
let pieceScroller = $state<HTMLElement>();
|
||||
let railScroller = $state<HTMLElement>();
|
||||
let liveRail = $state<HTMLElement>();
|
||||
let mobileRunButton = $state<HTMLButtonElement>();
|
||||
let railCloseButton = $state<HTMLButtonElement>();
|
||||
let liveMenuTrigger = $state<HTMLButtonElement>();
|
||||
let liveMenuPopover = $state<HTMLElement>();
|
||||
let scrollPositions: Record<string, number> = {};
|
||||
let outputWindow: Window | null = null;
|
||||
let allUsages = $derived(data.live.sections.flatMap((section) => section.usages));
|
||||
let viewedUsage = $derived(
|
||||
allUsages.find((usage) => usage.id === viewedUsageId) ?? data.live.current
|
||||
);
|
||||
let currentPosition = $derived(
|
||||
data.live.current ? allUsages.findIndex((usage) => usage.id === data.live.current?.id) + 1 : 0
|
||||
);
|
||||
let viewedPosition = $derived(
|
||||
viewedUsage ? allUsages.findIndex((usage) => usage.id === viewedUsage?.id) + 1 : 0
|
||||
);
|
||||
let previousUsage = $derived(viewedPosition > 1 ? allUsages[viewedPosition - 2] : null);
|
||||
let viewingLiveCurrent = $derived(viewedUsage?.id === data.live.current?.id);
|
||||
let runExpanded = $derived(isMobile ? mobileRailOpen : showRail);
|
||||
let currentSection = $derived(
|
||||
data.live.sections.find((section) => section.id === data.live.current?.sectionId) ?? null
|
||||
);
|
||||
@@ -51,10 +66,19 @@
|
||||
data.live.completedCount === data.live.totalCount
|
||||
)
|
||||
);
|
||||
let currentSectionTitle = $derived(
|
||||
currentSection?.name ?? (sessionComplete ? 'Sendungsende' : 'Live-Regie')
|
||||
);
|
||||
let nextTitle = $derived(
|
||||
data.live.next?.title ?? (sessionComplete ? 'Sendungsende erreicht' : 'Kein weiteres Piece')
|
||||
);
|
||||
let currentActualSeconds = $derived(
|
||||
(data.live.current?.actualDurationSeconds ?? 0) +
|
||||
(data.live.current ? Math.max(0, Math.floor((now - loadedAt) / 1000)) : 0)
|
||||
);
|
||||
let viewedActualSeconds = $derived(
|
||||
viewingLiveCurrent ? currentActualSeconds : (viewedUsage?.actualDurationSeconds ?? 0)
|
||||
);
|
||||
let sectionElapsedSeconds = $derived(
|
||||
(data.live.sectionTimer?.elapsedSeconds ?? 0) +
|
||||
(data.live.sectionTimer?.runningSinceUtc
|
||||
@@ -80,6 +104,7 @@
|
||||
loadedRevision = data.live.liveRevision;
|
||||
loadedAt = Date.now();
|
||||
if (changedPiece) {
|
||||
viewedUsageId = nextCurrentId;
|
||||
void tick().then(() => {
|
||||
if (pieceScroller)
|
||||
pieceScroller.scrollTop = nextCurrentId ? (scrollPositions[nextCurrentId] ?? 0) : 0;
|
||||
@@ -121,19 +146,36 @@
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
const mobileBreakpoint = matchMedia('(max-width: 46rem)');
|
||||
isMobile = mobileBreakpoint.matches;
|
||||
const handleBreakpointChange = (event: MediaQueryListEvent) => {
|
||||
isMobile = event.matches;
|
||||
if (!event.matches) void closeMobileRail(false);
|
||||
};
|
||||
const handleDocumentClick = (event: MouseEvent) => {
|
||||
const target = event.target;
|
||||
if (!(target instanceof Node)) return;
|
||||
if (menuOpen && !liveMenuPopover?.contains(target) && !liveMenuTrigger?.contains(target)) {
|
||||
void closeLiveMenu();
|
||||
}
|
||||
if (
|
||||
expandedContext &&
|
||||
!(target instanceof Element && target.closest('.live-context-title'))
|
||||
) {
|
||||
expandedContext = null;
|
||||
}
|
||||
};
|
||||
mobileBreakpoint.addEventListener('change', handleBreakpointChange);
|
||||
document.addEventListener('click', handleDocumentClick);
|
||||
const interval = setInterval(() => (now = Date.now()), 1000);
|
||||
return () => {
|
||||
mobileBreakpoint.removeEventListener('change', handleBreakpointChange);
|
||||
document.removeEventListener('click', handleDocumentClick);
|
||||
clearInterval(interval);
|
||||
};
|
||||
});
|
||||
|
||||
function persistSettings() {
|
||||
if (pieceScroller && currentId) scrollPositions[currentId] = pieceScroller.scrollTop;
|
||||
if (pieceScroller && viewedUsageId) scrollPositions[viewedUsageId] = pieceScroller.scrollTop;
|
||||
localStorage.setItem(
|
||||
`sendewerk-live-${data.live.show.id}`,
|
||||
JSON.stringify({
|
||||
@@ -155,7 +197,31 @@
|
||||
persistSettings();
|
||||
}
|
||||
|
||||
function toggleRunPanel() {
|
||||
void closeLiveMenu(false);
|
||||
if (isMobile) {
|
||||
if (mobileRailOpen) void closeMobileRail();
|
||||
else void openMobileRail();
|
||||
return;
|
||||
}
|
||||
toggleDesktopRail();
|
||||
}
|
||||
|
||||
function viewUsage(usageId: string) {
|
||||
if (pieceScroller && viewedUsageId) scrollPositions[viewedUsageId] = pieceScroller.scrollTop;
|
||||
viewedUsageId = usageId;
|
||||
void tick().then(() => {
|
||||
if (pieceScroller) pieceScroller.scrollTop = scrollPositions[usageId] ?? 0;
|
||||
});
|
||||
void closeMobileRail();
|
||||
}
|
||||
|
||||
function viewPreviousUsage() {
|
||||
if (previousUsage) viewUsage(previousUsage.id);
|
||||
}
|
||||
|
||||
async function openMobileRail() {
|
||||
await closeLiveMenu(false);
|
||||
mobileRailOpen = true;
|
||||
await tick();
|
||||
railCloseButton?.focus();
|
||||
@@ -169,12 +235,43 @@
|
||||
}
|
||||
|
||||
function handleWindowKeydown(event: KeyboardEvent) {
|
||||
if (mobileRailOpen && event.key === 'Escape') {
|
||||
if (event.key !== 'Escape') return;
|
||||
if (menuOpen) {
|
||||
event.preventDefault();
|
||||
void closeLiveMenu();
|
||||
} else if (expandedContext) {
|
||||
event.preventDefault();
|
||||
expandedContext = null;
|
||||
} else if (mobileRailOpen) {
|
||||
event.preventDefault();
|
||||
void closeMobileRail();
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleLiveMenu() {
|
||||
if (menuOpen) {
|
||||
await closeLiveMenu();
|
||||
return;
|
||||
}
|
||||
menuOpen = true;
|
||||
expandedContext = null;
|
||||
await tick();
|
||||
liveMenuPopover
|
||||
?.querySelector<HTMLElement>('button:not(:disabled), a[href]')
|
||||
?.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
async function closeLiveMenu(restoreFocus = true) {
|
||||
if (!menuOpen) return;
|
||||
menuOpen = false;
|
||||
await tick();
|
||||
if (restoreFocus) liveMenuTrigger?.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
function toggleContext(name: 'section' | 'next') {
|
||||
expandedContext = expandedContext === name ? null : name;
|
||||
}
|
||||
|
||||
function trapRailFocus(event: KeyboardEvent) {
|
||||
if (!mobileRailOpen || event.key !== 'Tab' || !liveRail) return;
|
||||
const focusable = Array.from(
|
||||
@@ -220,7 +317,7 @@
|
||||
}
|
||||
|
||||
const enhanceCommand: SubmitFunction = ({ formElement, formData, submitter }) => {
|
||||
formElement.closest<HTMLDetailsElement>('.live-menu')?.removeAttribute('open');
|
||||
void closeLiveMenu();
|
||||
const submittedFromRail = Boolean(formElement.closest('.live-rail'));
|
||||
const keepRailOpen = formElement.dataset.keepRailOpen === 'true';
|
||||
const command = formElement.elements.namedItem('commandId');
|
||||
@@ -238,8 +335,8 @@
|
||||
};
|
||||
};
|
||||
|
||||
const enhanceOutput: SubmitFunction = ({ formElement, submitter }) => {
|
||||
formElement.closest<HTMLDetailsElement>('.live-menu')?.removeAttribute('open');
|
||||
const enhanceOutput: SubmitFunction = ({ submitter }) => {
|
||||
void closeLiveMenu();
|
||||
popupMessage = '';
|
||||
outputWindow = window.open('about:blank', 'heinzlive-output');
|
||||
if (!outputWindow) popupMessage = 'Popup blockiert. Bitte Popups erlauben und erneut öffnen.';
|
||||
@@ -268,114 +365,167 @@
|
||||
|
||||
<div class:without-rail={!showRail} class="live-page live-controller">
|
||||
<header class="live-command-header" inert={mobileRailOpen}>
|
||||
<div class="live-section-context">
|
||||
<span class="live-pulse" aria-hidden="true"></span>
|
||||
<div>
|
||||
<small>{data.live.show.title}</small>
|
||||
<strong>{currentSection?.name ?? (sessionComplete ? 'Sendungsende' : 'Live-Regie')}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class:warning={hasActiveSectionTimer &&
|
||||
sectionRemainingSeconds <= 120 &&
|
||||
sectionRemainingSeconds >= 0}
|
||||
class:overrun={hasActiveSectionTimer && sectionRemainingSeconds < 0}
|
||||
class="section-countdown"
|
||||
aria-live="off"
|
||||
>
|
||||
<span>Abschnitt</span>
|
||||
<strong>
|
||||
{#if hasActiveSectionTimer}
|
||||
{sectionRemainingSeconds < 0
|
||||
? `+${formatCountdown(Math.abs(sectionRemainingSeconds))}`
|
||||
: formatCountdown(sectionRemainingSeconds)}
|
||||
{:else}Kein Countdown{/if}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<div class:active={Boolean(presentedUsage)} class="live-output-state" aria-live="polite">
|
||||
<span aria-hidden="true"></span>
|
||||
<div>
|
||||
<small>Ausgabe</small>
|
||||
<strong>{presentedUsage?.title ?? 'Leer'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
bind:this={mobileRunButton}
|
||||
class="live-mobile-run-button"
|
||||
type="button"
|
||||
aria-controls="live-run-sheet"
|
||||
aria-expanded={mobileRailOpen}
|
||||
onclick={openMobileRail}
|
||||
>
|
||||
Ablauf · Piece {currentPosition || data.live.completedCount}/{data.live.totalCount}
|
||||
</button>
|
||||
|
||||
<details class="live-menu">
|
||||
<summary>Menü</summary>
|
||||
<div class="live-menu-popover">
|
||||
<section>
|
||||
<strong>Ansicht</strong>
|
||||
<div class="font-control" aria-label="Prompter-Schriftgröße">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => changePromptSize(-0.1)}
|
||||
aria-label="Schrift verkleinern">A−</button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => changePromptSize(0.1)}
|
||||
aria-label="Schrift vergrößern">A+</button
|
||||
>
|
||||
</div>
|
||||
<button type="button" class="text-button" aria-pressed={!showTimes} onclick={toggleTimes}>
|
||||
{showTimes ? 'Zeiten ausblenden' : 'Zeiten einblenden'}
|
||||
</button>
|
||||
<div class="live-context-row">
|
||||
<div class="live-context-summary">
|
||||
<span class="live-pulse" aria-hidden="true"></span>
|
||||
<button
|
||||
type="button"
|
||||
class:expanded={expandedContext === 'section'}
|
||||
class="live-context-title section"
|
||||
aria-label={`Aktueller Abschnitt: ${currentSectionTitle}`}
|
||||
aria-expanded={expandedContext === 'section'}
|
||||
onclick={() => toggleContext('section')}
|
||||
>
|
||||
<span>{currentSectionTitle}</span>
|
||||
</button>
|
||||
<div class="live-next-inline">
|
||||
<small>Danach:</small>
|
||||
<button
|
||||
type="button"
|
||||
class="text-button live-desktop-rail-toggle"
|
||||
onclick={toggleDesktopRail}
|
||||
class:expanded={expandedContext === 'next'}
|
||||
class="live-context-title next"
|
||||
aria-label={`Danach: ${nextTitle}`}
|
||||
aria-expanded={expandedContext === 'next'}
|
||||
onclick={() => toggleContext('next')}
|
||||
>
|
||||
{showRail ? 'Ablauf ausblenden' : 'Ablauf einblenden'}
|
||||
<span>{nextTitle}</span>
|
||||
</button>
|
||||
<ThemeToggle />
|
||||
</section>
|
||||
<section>
|
||||
<strong>Ausgabekanal</strong>
|
||||
<form method="POST" action={liveAction('createOutput')} use:enhance={enhanceOutput}>
|
||||
<input type="hidden" name="showId" value={data.live.show.id} />
|
||||
<button class="secondary-button" type="submit">Ausgabe verbinden und öffnen</button>
|
||||
</form>
|
||||
<form method="POST" action={liveAction('rotateOutput')} use:enhance={enhanceOutput}>
|
||||
<input type="hidden" name="showId" value={data.live.show.id} />
|
||||
<button class="text-button" type="submit">Schlüssel erneuern</button>
|
||||
</form>
|
||||
<form method="POST" action={liveAction('clearOutput')} use:enhance={enhanceCommand}>
|
||||
<button class="text-button danger-text" type="submit">Ausgabekanal trennen</button>
|
||||
</form>
|
||||
</section>
|
||||
<section>
|
||||
<strong>Sendung</strong>
|
||||
{#if needsResume}<form
|
||||
method="POST"
|
||||
action={liveAction('start')}
|
||||
use:enhance={enhanceCommand}
|
||||
>
|
||||
<input type="hidden" name="showId" value={data.live.show.id} />
|
||||
<input type="hidden" name="revision" value={data.live.liveRevision} />
|
||||
<input type="hidden" name="commandId" value={data.commandId} />
|
||||
<button class="text-button" type="submit">Live-Stand wiederaufnehmen</button>
|
||||
</form>{/if}
|
||||
<a href={resolve('/sendungen/[id]', { id: data.live.show.id })}
|
||||
>Liveansicht verlassen · Zur Planung</a
|
||||
>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<button
|
||||
bind:this={liveMenuTrigger}
|
||||
class="live-menu-trigger"
|
||||
type="button"
|
||||
aria-haspopup="dialog"
|
||||
aria-controls="live-menu-popover"
|
||||
aria-expanded={menuOpen}
|
||||
onclick={toggleLiveMenu}
|
||||
>
|
||||
Menü
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="live-status-row">
|
||||
<div class:active={Boolean(presentedUsage)} class="live-output-control">
|
||||
<div class="live-output-state" aria-live="polite">
|
||||
<span aria-hidden="true"></span>
|
||||
<div>
|
||||
<small>Ausgabe</small>
|
||||
<strong title={presentedUsage?.title ?? undefined}
|
||||
>{presentedUsage?.title ?? 'Leer'}</strong
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{#if data.live.presentationUsageId}<form
|
||||
method="POST"
|
||||
action={liveAction('present')}
|
||||
use:enhance={enhanceCommand}
|
||||
>
|
||||
<input type="hidden" name="showId" value={data.live.show.id} />
|
||||
<input type="hidden" name="usageId" value="" />
|
||||
<input type="hidden" name="revision" value={data.live.liveRevision} />
|
||||
<input type="hidden" name="commandId" value={data.commandId} />
|
||||
<button class="text-button" type="submit">Präsentation leeren</button>
|
||||
</form>{/if}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class:warning={hasActiveSectionTimer &&
|
||||
sectionRemainingSeconds <= 120 &&
|
||||
sectionRemainingSeconds >= 0}
|
||||
class:overrun={hasActiveSectionTimer && sectionRemainingSeconds < 0}
|
||||
class="section-countdown"
|
||||
aria-live="off"
|
||||
>
|
||||
<span>Abschnitt</span>
|
||||
<strong>
|
||||
{#if hasActiveSectionTimer}
|
||||
{sectionRemainingSeconds < 0
|
||||
? `+${formatCountdown(Math.abs(sectionRemainingSeconds))}`
|
||||
: formatCountdown(sectionRemainingSeconds)}
|
||||
{:else}Kein Countdown{/if}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<button
|
||||
bind:this={mobileRunButton}
|
||||
class:active={runExpanded}
|
||||
class="live-run-button"
|
||||
type="button"
|
||||
aria-controls="live-run-sheet"
|
||||
aria-expanded={runExpanded}
|
||||
onclick={toggleRunPanel}
|
||||
>
|
||||
<AppIcon name="list" />
|
||||
<span>Ablauf · {currentPosition || data.live.completedCount}/{data.live.totalCount}</span>
|
||||
<AppIcon name="chevron-right" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#if menuOpen}<div
|
||||
bind:this={liveMenuPopover}
|
||||
class="live-menu-popover"
|
||||
id="live-menu-popover"
|
||||
role="dialog"
|
||||
aria-label="Regiemenü"
|
||||
inert={mobileRailOpen}
|
||||
>
|
||||
<section class="live-menu-show">
|
||||
<small>Sendung</small>
|
||||
<strong>{data.live.show.title}</strong>
|
||||
</section>
|
||||
<section>
|
||||
<strong>Ansicht</strong>
|
||||
<div class="font-control" aria-label="Prompter-Schriftgröße">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => changePromptSize(-0.1)}
|
||||
aria-label="Schrift verkleinern">A−</button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => changePromptSize(0.1)}
|
||||
aria-label="Schrift vergrößern">A+</button
|
||||
>
|
||||
</div>
|
||||
<button type="button" class="text-button" aria-pressed={!showTimes} onclick={toggleTimes}>
|
||||
{showTimes ? 'Zeiten ausblenden' : 'Zeiten einblenden'}
|
||||
</button>
|
||||
<ThemeToggle />
|
||||
</section>
|
||||
<section>
|
||||
<strong>Ausgabekanal</strong>
|
||||
<form method="POST" action={liveAction('createOutput')} use:enhance={enhanceOutput}>
|
||||
<input type="hidden" name="showId" value={data.live.show.id} />
|
||||
<button class="secondary-button" type="submit">Ausgabe verbinden und öffnen</button>
|
||||
</form>
|
||||
<form method="POST" action={liveAction('rotateOutput')} use:enhance={enhanceOutput}>
|
||||
<input type="hidden" name="showId" value={data.live.show.id} />
|
||||
<button class="text-button" type="submit">Schlüssel erneuern</button>
|
||||
</form>
|
||||
<form method="POST" action={liveAction('clearOutput')} use:enhance={enhanceCommand}>
|
||||
<button class="text-button danger-text" type="submit">Ausgabekanal trennen</button>
|
||||
</form>
|
||||
</section>
|
||||
<section>
|
||||
<strong>Sendung</strong>
|
||||
{#if needsResume}<form
|
||||
method="POST"
|
||||
action={liveAction('start')}
|
||||
use:enhance={enhanceCommand}
|
||||
>
|
||||
<input type="hidden" name="showId" value={data.live.show.id} />
|
||||
<input type="hidden" name="revision" value={data.live.liveRevision} />
|
||||
<input type="hidden" name="commandId" value={data.commandId} />
|
||||
<button class="text-button" type="submit">Live-Stand wiederaufnehmen</button>
|
||||
</form>{/if}
|
||||
<a href={resolve('/sendungen/[id]', { id: data.live.show.id })}
|
||||
>Liveansicht verlassen · Zur Planung</a
|
||||
>
|
||||
</section>
|
||||
</div>{/if}
|
||||
|
||||
<div class="live-messages">
|
||||
{#if form?.error}<div class="live-message form-message error" role="alert">
|
||||
{form.error}
|
||||
@@ -384,7 +534,7 @@
|
||||
</div>
|
||||
|
||||
<div class="live-workspace">
|
||||
{#if data.live.current}<article
|
||||
{#if viewedUsage}<article
|
||||
class="live-current"
|
||||
style={`--prompt-size: ${promptSize}rem`}
|
||||
aria-labelledby="current-title"
|
||||
@@ -392,51 +542,43 @@
|
||||
>
|
||||
<div class="live-current-scroll" bind:this={pieceScroller} onscroll={persistSettings}>
|
||||
<div class="live-piece-heading">
|
||||
<div class="live-kicker">
|
||||
<span>Piece {currentPosition} von {data.live.totalCount}</span>
|
||||
{#if showTimes}<strong>
|
||||
{formatClock(currentActualSeconds)} / {formatClock(
|
||||
data.live.current.durationSeconds
|
||||
)}
|
||||
</strong>{/if}
|
||||
</div>
|
||||
<h1 id="current-title">{data.live.current.title}</h1>
|
||||
<span class="live-piece-position">Piece {viewedPosition}/{data.live.totalCount}</span>
|
||||
<h1 id="current-title">{viewedUsage.title}</h1>
|
||||
{#if showTimes}<strong class="live-piece-time">
|
||||
{formatClock(viewedActualSeconds)} / {formatClock(viewedUsage.durationSeconds)}
|
||||
</strong>{/if}
|
||||
</div>
|
||||
|
||||
{#if data.live.current.introHtml}<section class="live-script-section live-intro">
|
||||
{#if viewedUsage.introHtml}<section class="live-script-section live-intro">
|
||||
<span>Einleitung</span>
|
||||
<!-- HTML is produced exclusively by the server-side allowlist sanitizer. -->
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
<div class="prompter-copy">{@html data.live.current.introHtml}</div>
|
||||
<div class="prompter-copy">{@html viewedUsage.introHtml}</div>
|
||||
</section>{/if}
|
||||
{#if data.live.current.moderationHtml}<section
|
||||
class="live-script-section moderation-card"
|
||||
>
|
||||
{#if viewedUsage.moderationHtml}<section class="live-script-section moderation-card">
|
||||
<span>Moderationsstichpunkte</span>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
<div class="live-markdown">{@html data.live.current.moderationHtml}</div>
|
||||
<div class="live-markdown">{@html viewedUsage.moderationHtml}</div>
|
||||
</section>{/if}
|
||||
{#if data.live.current.transitionHtml}<section
|
||||
class="live-script-section transition-card"
|
||||
>
|
||||
{#if viewedUsage.transitionHtml}<section class="live-script-section transition-card">
|
||||
<span>Überleitung</span>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
<div class="live-markdown">{@html data.live.current.transitionHtml}</div>
|
||||
<div class="live-markdown">{@html viewedUsage.transitionHtml}</div>
|
||||
</section>{/if}
|
||||
|
||||
{#if data.live.current.researchHtml || data.live.current.sources.length || data.live.current.tools.length || data.live.current.ownNotes}
|
||||
{#if viewedUsage.researchHtml || viewedUsage.sources.length || viewedUsage.tools.length || viewedUsage.ownNotes}
|
||||
<!-- External source and tool URLs are server-validated private snapshots. -->
|
||||
<!-- eslint-disable svelte/no-navigation-without-resolve -->
|
||||
<div class="live-support-actions" aria-label="Private Zusatzinformationen">
|
||||
{#if data.live.current.researchHtml}<details class="research-details">
|
||||
{#if viewedUsage.researchHtml}<details class="research-details">
|
||||
<summary>Recherche</summary>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
<div class="live-markdown">{@html data.live.current.researchHtml}</div>
|
||||
<div class="live-markdown">{@html viewedUsage.researchHtml}</div>
|
||||
</details>{/if}
|
||||
{#if data.live.current.sources.length}<details class="live-materials">
|
||||
<summary>Quellen · {data.live.current.sources.length}</summary>
|
||||
{#if viewedUsage.sources.length}<details class="live-materials">
|
||||
<summary>Quellen · {viewedUsage.sources.length}</summary>
|
||||
<ul>
|
||||
{#each data.live.current.sources as source (source.id)}<li>
|
||||
{#each viewedUsage.sources as source (source.id)}<li>
|
||||
<!-- The URL is a server-validated private HTTP(S) snapshot. -->
|
||||
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
|
||||
<a href={source.url} target="_blank" rel="noopener noreferrer"
|
||||
@@ -448,10 +590,10 @@
|
||||
</li>{/each}
|
||||
</ul>
|
||||
</details>{/if}
|
||||
{#if data.live.current.tools.length}<details class="live-materials">
|
||||
<summary>Werkzeuge · {data.live.current.tools.length}</summary>
|
||||
{#if viewedUsage.tools.length}<details class="live-materials">
|
||||
<summary>Werkzeuge · {viewedUsage.tools.length}</summary>
|
||||
<ul>
|
||||
{#each data.live.current.tools as tool (tool.id)}<li>
|
||||
{#each viewedUsage.tools as tool (tool.id)}<li>
|
||||
{#if tool.workUrl || tool.startUrl}<!-- The URL is a server-validated private HTTP(S) snapshot. -->
|
||||
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
|
||||
<a
|
||||
@@ -464,14 +606,14 @@
|
||||
</li>{/each}
|
||||
</ul>
|
||||
</details>{/if}
|
||||
{#if data.live.current.ownNotes}<details class="live-materials">
|
||||
{#if viewedUsage.ownNotes}<details class="live-materials">
|
||||
<summary>Eigene Notizen</summary>
|
||||
<p>{data.live.current.ownNotes}</p>
|
||||
<p>{viewedUsage.ownNotes}</p>
|
||||
</details>{/if}
|
||||
</div>
|
||||
<!-- eslint-enable svelte/no-navigation-without-resolve -->
|
||||
{/if}
|
||||
{#if !data.live.current.introHtml && !data.live.current.moderationHtml && !data.live.current.transitionHtml}
|
||||
{#if !viewedUsage.introHtml && !viewedUsage.moderationHtml && !viewedUsage.transitionHtml}
|
||||
<p class="live-empty-copy">Für dieses Piece sind keine Moderationstexte hinterlegt.</p>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -561,26 +703,27 @@
|
||||
{#each section.usages as usage, usageIndex (usage.id)}
|
||||
<div
|
||||
class:current={usage.id === data.live.current?.id}
|
||||
class:selected={usage.id === viewedUsage?.id}
|
||||
class:presented={usage.id === data.live.presentationUsageId}
|
||||
class:done={usage.status === 'done'}
|
||||
class:skipped={usage.status === 'skipped'}
|
||||
class="live-run-item"
|
||||
data-current={usage.id === data.live.current?.id}
|
||||
>
|
||||
<form method="POST" action={liveAction('select')} use:enhance={enhanceCommand}>
|
||||
<input type="hidden" name="showId" value={data.live.show.id} />
|
||||
<input type="hidden" name="usageId" value={usage.id} />
|
||||
<input type="hidden" name="revision" value={data.live.liveRevision} />
|
||||
<input type="hidden" name="commandId" value={data.commandId} />
|
||||
<button
|
||||
class="live-run-select"
|
||||
type="submit"
|
||||
disabled={!data.live.startedAtUtc || usage.id === data.live.current?.id}
|
||||
aria-label={`${usage.title} als aktuelles Piece auswählen`}
|
||||
<button
|
||||
class="live-run-select"
|
||||
type="button"
|
||||
disabled={!data.live.startedAtUtc || usage.id === viewedUsage?.id}
|
||||
aria-label={`${usage.title} in der Regie auswählen`}
|
||||
onclick={() => viewUsage(usage.id)}
|
||||
>
|
||||
<strong>{usage.title}</strong>
|
||||
<small
|
||||
>{statusLabel(usage.status)}{usage.id === data.live.presentationUsageId
|
||||
? ' · Ausgegeben'
|
||||
: ''}</small
|
||||
>
|
||||
<strong>{usage.title}</strong>
|
||||
<small>{statusLabel(usage.status)}</small>
|
||||
</button>
|
||||
</form>
|
||||
</button>
|
||||
<div class="live-run-actions">
|
||||
{#if usage.status === 'open'}<details class="live-run-order">
|
||||
<summary aria-label={`${usage.title} sortieren`}
|
||||
@@ -737,37 +880,20 @@
|
||||
</div>
|
||||
|
||||
<footer class="live-action-bar" inert={mobileRailOpen}>
|
||||
<div class="live-next-summary">
|
||||
<span>{data.live.current ? 'Nächstes Piece' : 'Live-Stand'}</span>
|
||||
<strong>
|
||||
{data.live.next?.title ??
|
||||
(sessionComplete ? 'Sendungsende erreicht' : 'Kein weiteres Piece')}
|
||||
</strong>
|
||||
</div>
|
||||
<div class="live-footer-actions">
|
||||
{#if data.live.presentationUsageId}<form
|
||||
class="live-clear-form"
|
||||
method="POST"
|
||||
action={liveAction('present')}
|
||||
use:enhance={enhanceCommand}
|
||||
{#if data.live.current && viewedUsage}<button
|
||||
class="secondary-button"
|
||||
type="button"
|
||||
disabled={!previousUsage}
|
||||
onclick={viewPreviousUsage}>Zurück</button
|
||||
>
|
||||
<form method="POST" action={liveAction('present')} use:enhance={enhanceCommand}>
|
||||
<input type="hidden" name="showId" value={data.live.show.id} />
|
||||
<input type="hidden" name="usageId" value="" />
|
||||
<input type="hidden" name="revision" value={data.live.liveRevision} />
|
||||
<input type="hidden" name="commandId" value={data.commandId} />
|
||||
<button class="text-button" type="submit">Präsentation leeren</button>
|
||||
</form>{/if}
|
||||
{#if data.live.current}<form
|
||||
method="POST"
|
||||
action={liveAction('present')}
|
||||
use:enhance={enhanceCommand}
|
||||
>
|
||||
<input type="hidden" name="showId" value={data.live.show.id} />
|
||||
<input type="hidden" name="usageId" value={data.live.current.id} />
|
||||
<input type="hidden" name="usageId" value={viewedUsage.id} />
|
||||
<input type="hidden" name="revision" value={data.live.liveRevision} />
|
||||
<input type="hidden" name="commandId" value={data.commandId} />
|
||||
<button class="secondary-button" type="submit">
|
||||
{data.live.presentationUsageId === data.live.current.id
|
||||
{data.live.presentationUsageId === viewedUsage.id
|
||||
? 'Erneut präsentieren'
|
||||
: 'Präsentieren'}
|
||||
</button>
|
||||
@@ -776,13 +902,27 @@
|
||||
<input type="hidden" name="showId" value={data.live.show.id} />
|
||||
<input type="hidden" name="revision" value={data.live.liveRevision} />
|
||||
<input type="hidden" name="commandId" value={data.commandId} />
|
||||
<button class="secondary-button" type="submit">Überspringen</button>
|
||||
<button
|
||||
class="secondary-button"
|
||||
type="submit"
|
||||
disabled={!viewingLiveCurrent}
|
||||
title={!viewingLiveCurrent
|
||||
? 'Zuerst im Ablauf zum aktuellen Live-Piece zurückkehren'
|
||||
: undefined}>Überspringen</button
|
||||
>
|
||||
</form>
|
||||
<form method="POST" action={liveAction('complete')} use:enhance={enhanceCommand}>
|
||||
<input type="hidden" name="showId" value={data.live.show.id} />
|
||||
<input type="hidden" name="revision" value={data.live.liveRevision} />
|
||||
<input type="hidden" name="commandId" value={data.commandId} />
|
||||
<button class="primary-button" type="submit">
|
||||
<button
|
||||
class="primary-button"
|
||||
type="submit"
|
||||
disabled={!viewingLiveCurrent}
|
||||
title={!viewingLiveCurrent
|
||||
? 'Zuerst im Ablauf zum aktuellen Live-Piece zurückkehren'
|
||||
: undefined}
|
||||
>
|
||||
{data.live.next
|
||||
? 'Erledigen & weiter'
|
||||
: hasOtherOpenUsages
|
||||
|
||||
Reference in new issue
Block a user