From bb05c7c19e5d30c3fba82d93f99976dcb4521575 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Wed, 15 Apr 2026 00:48:51 +0200 Subject: [PATCH] Update project sequence creation and timing handling --- src/app/App.tsx | 100 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index 337b8d42..97528771 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -4384,7 +4384,10 @@ export function App({ store, initialStatusMessage }: AppProps) { const createDefaultProjectSequenceControlStep = ( stepClass: "held" | "impulse", targetKey: string, - previousStep?: Extract | null + previousStep?: Extract | null, + timing?: Partial< + Pick + > ): Extract => { const targetOption = resolveSequenceControlTargetOption(targetKey); @@ -4401,6 +4404,13 @@ export function App({ store, initialStatusMessage }: AppProps) { } return { + startMinute: timing?.startMinute ?? 0, + durationMinutes: + timing?.durationMinutes ?? + (stepClass === "held" + ? DEFAULT_HELD_SEQUENCE_CLIP_DURATION_MINUTES + : DEFAULT_IMPULSE_SEQUENCE_CLIP_DURATION_MINUTES), + lane: timing?.lane ?? 0, stepClass, type: "controlEffect", effect: createProjectScheduleEffectFromOption({ @@ -4411,6 +4421,9 @@ export function App({ store, initialStatusMessage }: AppProps) { }; }; + const getNextProjectSequenceClipLane = (sequence: ProjectSequence): number => + getNextSequenceClipLane(sequence.clips); + const updateProjectSequence = ( sequenceId: string, label: string, @@ -4447,7 +4460,9 @@ export function App({ store, initialStatusMessage }: AppProps) { }; const handleAddProjectSequence = () => { - const nextSequence = createProjectSequence(); + const nextSequence = createProjectSequence({ + durationMinutes: DEFAULT_PROJECT_SEQUENCE_DURATION_MINUTES + }); updateProjectSequences( "Add project sequence", @@ -4532,7 +4547,14 @@ export function App({ store, initialStatusMessage }: AppProps) { : "Added impulse control clip.", (sequence) => { sequence.clips.push( - createDefaultProjectSequenceControlStep(stepClass, targetKey) + createDefaultProjectSequenceControlStep(stepClass, targetKey, null, { + startMinute: 0, + durationMinutes: + stepClass === "held" + ? DEFAULT_HELD_SEQUENCE_CLIP_DURATION_MINUTES + : DEFAULT_IMPULSE_SEQUENCE_CLIP_DURATION_MINUTES, + lane: getNextProjectSequenceClipLane(sequence) + }) ); } ); @@ -4548,6 +4570,9 @@ export function App({ store, initialStatusMessage }: AppProps) { "Added dialogue clip.", (sequence) => { sequence.clips.push({ + startMinute: 0, + durationMinutes: DEFAULT_IMPULSE_SEQUENCE_CLIP_DURATION_MINUTES, + lane: getNextProjectSequenceClipLane(sequence), stepClass: "impulse", type: "startDialogue", dialogueId @@ -4769,6 +4794,75 @@ export function App({ store, initialStatusMessage }: AppProps) { ); }; + const updateProjectSequenceDurationMinutes = ( + sequenceId: string, + durationMinutes: number + ) => { + updateProjectSequence( + sequenceId, + "Set project sequence duration", + "Updated sequence duration.", + (sequence) => { + if (!Number.isFinite(durationMinutes) || durationMinutes < 1) { + throw new Error( + "Sequence duration must be a finite number of minutes greater than zero." + ); + } + + sequence.durationMinutes = Math.max(1, Math.trunc(durationMinutes)); + + for (const clip of sequence.clips) { + if (clip.startMinute >= sequence.durationMinutes) { + clip.startMinute = Math.max(0, sequence.durationMinutes - 1); + } + + if (clip.startMinute + clip.durationMinutes > sequence.durationMinutes) { + clip.durationMinutes = Math.max( + 1, + sequence.durationMinutes - clip.startMinute + ); + } + } + } + ); + }; + + const updateProjectSequenceClipTiming = ( + sequenceId: string, + stepIndex: number, + timing: { + startMinute: number; + durationMinutes: number; + lane: number; + } + ) => { + updateProjectSequence(sequenceId, "Set project sequence clip timing", "Updated sequence clip timing.", (sequence) => { + const clip = sequence.clips[stepIndex]; + + if (clip === undefined) { + throw new Error("Selected project sequence clip no longer exists."); + } + + if ( + !Number.isFinite(timing.startMinute) || + !Number.isFinite(timing.durationMinutes) || + !Number.isFinite(timing.lane) + ) { + throw new Error("Sequence clip timing must use finite minute values."); + } + + clip.startMinute = Math.min( + Math.max(0, Math.trunc(timing.startMinute)), + Math.max(0, sequence.durationMinutes - 1) + ); + clip.durationMinutes = Math.min( + Math.max(1, Math.trunc(timing.durationMinutes)), + Math.max(1, sequence.durationMinutes - clip.startMinute) + ); + clip.lane = Math.max(0, Math.trunc(timing.lane)); + }); + }; + const updateWorldTimeOfDaySettings = ( label: string, successMessage: string,