import type { KeyboardEvent as ReactKeyboardEvent } from "react"; import { HOURS_PER_DAY, formatTimeOfDayHours } from "../document/project-time-settings"; import { formatControlEffectValue, getControlTargetRefKey } from "../controls/control-surface"; import { findProjectScheduleRoutineEffect, PROJECT_SCHEDULE_WEEKDAYS, formatProjectScheduleDaySelection, formatProjectScheduleWeekdayLabel, getProjectScheduleTimelineSegments, type ProjectScheduler, type ProjectScheduleRoutine, type ProjectScheduleWeekday } from "../scheduler/project-scheduler"; import { getProjectScheduleEffectOptionId, getProjectScheduleTargetOptionForRoutine, listProjectScheduleEffectOptions, type ProjectScheduleEffectOptionId, type ProjectScheduleTargetOption } from "../scheduler/project-schedule-control-options"; interface ProjectSchedulePaneProps { targetOptions: ProjectScheduleTargetOption[]; scheduler: ProjectScheduler; selectedRoutineId: string | null; onSelectRoutine(routineId: string | null): void; onAddRoutine(targetKey: string): void; onDeleteRoutine(routineId: string): void; onClose(): void; onSetRoutineTarget(routineId: string, targetKey: string): void; onSetRoutineTitle(routineId: string, title: string): void; onSetRoutineEnabled(routineId: string, enabled: boolean): void; onSetRoutineStartHour(routineId: string, startHour: number): void; onSetRoutineEndHour(routineId: string, endHour: number): void; onSetRoutinePriority(routineId: string, priority: number): void; onSetRoutineEffectOption( routineId: string, effectOptionId: ProjectScheduleEffectOptionId ): void; onSetRoutineDays( routineId: string, mode: "everyDay" | "selectedDays", days: ProjectScheduleWeekday[] ): void; onSetRoutineNumericValue(routineId: string, value: number): void; onSetRoutineColorValue(routineId: string, colorHex: string): void; onSetRoutineAnimationClip(routineId: string, clipName: string): void; onSetRoutineAnimationLoop(routineId: string, loop: boolean): void; onSetActorRoutinePresence(routineId: string, active: boolean): void; onSetActorRoutineAnimationClip( routineId: string, clipName: string | null ): void; onSetActorRoutineAnimationLoop(routineId: string, loop: boolean): void; onSetActorRoutinePath(routineId: string, pathId: string | null): void; onSetActorRoutinePathSpeed(routineId: string, speed: number): void; onSetActorRoutinePathLoop(routineId: string, loop: boolean): void; } function handleCommitOnEnter( event: ReactKeyboardEvent, commit: () => void ) { if (event.key !== "Enter") { return; } event.currentTarget.blur(); commit(); } function getRoutineSummary(routine: ProjectScheduleRoutine): string { const summaryParts = [ formatProjectScheduleDaySelection(routine.days), `${formatTimeOfDayHours(routine.startHour)}-${formatTimeOfDayHours(routine.endHour)}` ]; if (routine.target.kind === "actor") { const presenceEffect = findProjectScheduleRoutineEffect( routine, "setActorPresence" ); const animationEffect = findProjectScheduleRoutineEffect( routine, "playActorAnimation" ); const pathEffect = findProjectScheduleRoutineEffect(routine, "followActorPath"); summaryParts.push(presenceEffect?.active === false ? "Hidden" : "Present"); if (animationEffect !== null) { summaryParts.push(formatControlEffectValue(animationEffect)); } if (pathEffect !== null) { summaryParts.push(formatControlEffectValue(pathEffect)); } } else { summaryParts.push(formatControlEffectValue(routine.effects[0]!)); } summaryParts.push(`P${routine.priority}`); return summaryParts.join(" · "); } function isRoutineEffectInactive(routine: ProjectScheduleRoutine): boolean { if (routine.target.kind === "actor") { return ( findProjectScheduleRoutineEffect(routine, "setActorPresence")?.active === false ); } const effect = routine.effects[0]; if (effect === undefined) { return false; } switch (effect.type) { case "stopModelAnimation": case "stopSound": return true; case "setModelInstanceVisible": return !effect.visible; case "setInteractionEnabled": case "setLightEnabled": return !effect.enabled; default: return false; } } function getRoutineNumericValue(routine: ProjectScheduleRoutine): number | null { const effect = routine.effects[0]; if (effect === undefined) { return null; } switch (effect.type) { case "setSoundVolume": return effect.volume; case "setLightIntensity": case "setAmbientLightIntensity": case "setSunLightIntensity": return effect.intensity; default: return null; } } function getRoutineColorValue(routine: ProjectScheduleRoutine): string | null { const effect = routine.effects[0]; if (effect === undefined) { return null; } switch (effect.type) { case "setLightColor": case "setAmbientLightColor": case "setSunLightColor": return effect.colorHex; default: return null; } } function groupTargetOptions( targetOptions: ProjectScheduleTargetOption[] ): Array<{ groupLabel: string; options: ProjectScheduleTargetOption[] }> { const grouped = new Map(); for (const option of targetOptions) { const entries = grouped.get(option.groupLabel) ?? []; entries.push(option); grouped.set(option.groupLabel, entries); } return [...grouped.entries()].map(([groupLabel, options]) => ({ groupLabel, options })); } export function ProjectSchedulePane({ targetOptions, scheduler, selectedRoutineId, onSelectRoutine, onAddRoutine, onDeleteRoutine, onClose, onSetRoutineTarget, onSetRoutineTitle, onSetRoutineEnabled, onSetRoutineStartHour, onSetRoutineEndHour, onSetRoutinePriority, onSetRoutineEffectOption, onSetRoutineDays, onSetRoutineNumericValue, onSetRoutineColorValue, onSetRoutineAnimationClip, onSetRoutineAnimationLoop, onSetActorRoutinePresence, onSetActorRoutineAnimationClip, onSetActorRoutineAnimationLoop, onSetActorRoutinePath, onSetActorRoutinePathSpeed, onSetActorRoutinePathLoop }: ProjectSchedulePaneProps) { const selectedRoutine = selectedRoutineId === null ? null : scheduler.routines[selectedRoutineId] ?? null; const selectedTargetOption = selectedRoutine === null ? null : getProjectScheduleTargetOptionForRoutine( targetOptions, selectedRoutine.target ); const selectedEffectOptionId = selectedRoutine === null || selectedRoutine.target.kind === "actor" ? null : getProjectScheduleEffectOptionId(selectedRoutine.effects[0]!); const selectedEffectOptions = selectedTargetOption === null || selectedTargetOption.target.kind === "actor" ? [] : listProjectScheduleEffectOptions(selectedTargetOption); const selectedActorPresenceEffect = selectedRoutine === null || selectedRoutine.target.kind !== "actor" ? null : findProjectScheduleRoutineEffect(selectedRoutine, "setActorPresence"); const selectedActorAnimationEffect = selectedRoutine === null || selectedRoutine.target.kind !== "actor" ? null : findProjectScheduleRoutineEffect(selectedRoutine, "playActorAnimation"); const selectedActorPathEffect = selectedRoutine === null || selectedRoutine.target.kind !== "actor" ? null : findProjectScheduleRoutineEffect(selectedRoutine, "followActorPath"); const selectedRoutineDays = selectedRoutine === null ? PROJECT_SCHEDULE_WEEKDAYS : selectedRoutine.days.mode === "everyDay" ? PROJECT_SCHEDULE_WEEKDAYS : selectedRoutine.days.days; const hourTicks = Array.from({ length: HOURS_PER_DAY }, (_, hour) => hour); return (
Schedule
Scheduler-owned orchestration over typed control targets and global project time.
Targets
{hourTicks.map((hour) => (
{String(hour).padStart(2, "0")}
))}
{targetOptions.length === 0 ? (
No scheduler-addressable control targets are authored in this project yet.
) : ( targetOptions.map((targetOption) => { const routines = Object.values(scheduler.routines) .filter( (routine) => getControlTargetRefKey(routine.target) === targetOption.key ) .sort((left, right) => left.startHour - right.startHour); return (
{targetOption.label}
{targetOption.groupLabel} · {targetOption.subtitle}
{routines.map((routine) => getProjectScheduleTimelineSegments(routine).map( (segment) => ( ) ) )}
); }) )}
); }