Refine live control and show overview

This commit is contained in:
hnzio committed 2026-10-24 09:45:01 +02:00
1 parent ec596d442e
commit b1474bc33d
16 files changed
+1580 -405

No files matched your search

+319 -179
View File
@@ -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