diff options
Diffstat (limited to 'apps/web/src/preview/components/toolbar.tsx')
| -rw-r--r-- | apps/web/src/preview/components/toolbar.tsx | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/apps/web/src/preview/components/toolbar.tsx b/apps/web/src/preview/components/toolbar.tsx new file mode 100644 index 00000000..b44e42ef --- /dev/null +++ b/apps/web/src/preview/components/toolbar.tsx @@ -0,0 +1,148 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useEditor } from "@/editor/use-editor"; +import { formatTimecode } from "opencut-wasm"; +import { invokeAction } from "@/actions"; +import { EditableTimecode } from "@/components/editable-timecode"; +import { Button } from "@/components/ui/button"; +import { + FullScreenIcon, + GridTableIcon, + PauseIcon, + PlayIcon, +} from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { getGuideById } from "@/guides"; +import { Separator } from "@/components/ui/separator"; +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectSeparator, +} from "@/components/ui/select"; +import { PREVIEW_ZOOM_PRESETS } from "@/preview/zoom"; +import { usePreviewViewport } from "./preview-viewport"; +import { GridPopover } from "./guide-popover"; +import { usePreviewStore } from "@/preview/preview-store"; + +export function PreviewToolbar({ + onToggleFullscreen, +}: { + onToggleFullscreen: () => void; +}) { + const activeGuide = usePreviewStore((state) => state.activeGuide); + const activeGuideDefinition = getGuideById(activeGuide); + + return ( + <div className="grid grid-cols-[1fr_auto_1fr] items-center pb-3 pt-5 px-5"> + <TimecodeDisplay /> + <PlayPauseButton /> + <div className="justify-self-end flex items-center gap-2.5"> + <ZoomSelect /> + <Separator orientation="vertical" className="h-4" /> + {/* v0.4.0 */} + {/* <GridPopover> + <Button + variant={activeGuideDefinition ? "secondary" : "text"} + size="icon" + > + {activeGuideDefinition ? ( + activeGuideDefinition.renderTriggerIcon() + ) : ( + <HugeiconsIcon icon={GridTableIcon} /> + )} + </Button> + </GridPopover> */} + <Button variant="text" onClick={onToggleFullscreen}> + <HugeiconsIcon icon={FullScreenIcon} /> + </Button> + </div> + </div> + ); +} + +function TimecodeDisplay() { + const editor = useEditor(); + const totalDuration = useEditor((e) => e.timeline.getTotalDuration()); + const fps = useEditor((e) => e.project.getActive().settings.fps); + const [currentTime, setCurrentTime] = useState(() => + editor.playback.getCurrentTime(), + ); + + useEffect(() => { + const handler = (e: Event) => + setCurrentTime((e as CustomEvent<{ time: number }>).detail.time); + window.addEventListener("playback-update", handler); + window.addEventListener("playback-seek", handler); + return () => { + window.removeEventListener("playback-update", handler); + window.removeEventListener("playback-seek", handler); + }; + }, []); + + return ( + <div className="flex items-center"> + <EditableTimecode + time={currentTime} + duration={totalDuration} + format="HH:MM:SS:FF" + fps={fps} + onTimeChange={({ time }) => editor.playback.seek({ time })} + className="text-center" + /> + <span className="text-muted-foreground px-2 font-mono text-xs">/</span> + <span className="text-muted-foreground font-mono text-xs"> + {formatTimecode({ time: totalDuration, format: "HH:MM:SS:FF", rate: fps })} + </span> + </div> + ); +} + +function ZoomSelect() { + const { isAtFit, zoomPercent, fitToScreen, setViewportPercent } = + usePreviewViewport(); + + const displayLabel = isAtFit ? "Fit" : `${zoomPercent}%`; + + const onValueChange = (value: string) => { + if (value === "fit") { + fitToScreen(); + } else { + setViewportPercent({ percent: Number(value) }); + } + }; + + return ( + <Select + value={isAtFit ? "fit" : String(zoomPercent)} + onValueChange={onValueChange} + > + <SelectTrigger className="tabular-nums">{displayLabel}</SelectTrigger> + <SelectContent> + <SelectItem value="fit">Fit</SelectItem> + <SelectSeparator /> + {PREVIEW_ZOOM_PRESETS.map((preset) => ( + <SelectItem key={preset} value={String(preset)}> + {preset}% + </SelectItem> + ))} + </SelectContent> + </Select> + ); +} + +function PlayPauseButton() { + const isPlaying = useEditor((e) => e.playback.getIsPlaying()); + + return ( + <Button + variant="text" + size="icon" + onClick={() => invokeAction("toggle-play")} + > + <HugeiconsIcon icon={isPlaying ? PauseIcon : PlayIcon} /> + </Button> + ); +} |
