diff options
| author | Maze Winther <mazewinther@gmail.com> | 2025-07-15 14:16:54 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2025-07-15 14:16:54 +0200 |
| commit | 1fdf2a051529e40cf7a0857d31a83057b878f08d (patch) | |
| tree | a81961f9efba930bb4d813d4abdc8fd98bf379d5 /apps/web/src | |
| parent | 5e86ed9efca3e783d6503f153545f3600cad331a (diff) | |
| download | opencut-1fdf2a051529e40cf7a0857d31a83057b878f08d.tar.gz | |
cleanup
Diffstat (limited to 'apps/web/src')
| -rw-r--r-- | apps/web/src/components/editor/snap-indicator.tsx | 65 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline-playhead.tsx | 14 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline-track.tsx | 13 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline.tsx | 412 | ||||
| -rw-r--r-- | apps/web/src/hooks/use-timeline-snapping.ts | 32 | ||||
| -rw-r--r-- | apps/web/src/stores/timeline-store.ts | 39 |
6 files changed, 214 insertions, 361 deletions
diff --git a/apps/web/src/components/editor/snap-indicator.tsx b/apps/web/src/components/editor/snap-indicator.tsx index 398e06c6..83a1f678 100644 --- a/apps/web/src/components/editor/snap-indicator.tsx +++ b/apps/web/src/components/editor/snap-indicator.tsx @@ -2,74 +2,53 @@ import { SnapPoint } from "@/hooks/use-timeline-snapping"; import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; +import type { TimelineTrack } from "@/types/timeline"; interface SnapIndicatorProps { snapPoint: SnapPoint | null; zoomLevel: number; - timelineHeight: number; isVisible: boolean; + tracks: TimelineTrack[]; + timelineRef: React.RefObject<HTMLDivElement>; + trackLabelsRef?: React.RefObject<HTMLDivElement>; } export function SnapIndicator({ snapPoint, zoomLevel, - timelineHeight, isVisible, + tracks, + timelineRef, + trackLabelsRef, }: SnapIndicatorProps) { if (!isVisible || !snapPoint) { return null; } - const leftPosition = - snapPoint.time * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; + const timelineContainerHeight = timelineRef.current?.offsetHeight || 400; + const totalHeight = timelineContainerHeight - 8; // 8px padding from edges - const getIndicatorColor = () => { - switch (snapPoint.type) { - case "grid": - return "bg-blue-400"; - case "element-start": - case "element-end": - return "bg-green-400"; - case "playhead": - return "bg-red-400"; - default: - return "bg-gray-400"; - } - }; + // Get dynamic track labels width, fallback to 0 if no tracks or no ref + const trackLabelsWidth = + tracks.length > 0 && trackLabelsRef?.current + ? trackLabelsRef.current.offsetWidth + : 0; - const getIndicatorLabel = () => { - switch (snapPoint.type) { - case "grid": - return "Grid"; - case "element-start": - return "Start"; - case "element-end": - return "End"; - case "playhead": - return "Playhead"; - default: - return ""; - } - }; + const leftPosition = + trackLabelsWidth + + snapPoint.time * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; return ( <div - className="absolute top-0 pointer-events-none z-50" + className="absolute pointer-events-none z-[90]" style={{ left: `${leftPosition}px`, - height: `${timelineHeight}px`, + top: 0, + height: `${totalHeight}px`, + width: "2px", }} > - {/* Snap line */} - <div className={`w-0.5 h-full ${getIndicatorColor()} opacity-80`} /> - - {/* Snap label */} - <div - className={`absolute top-0 left-1 px-1 py-0.5 text-xs text-white rounded ${getIndicatorColor()} opacity-90 whitespace-nowrap`} - style={{ transform: "translateY(-100%)" }} - > - {getIndicatorLabel()} ({snapPoint.time.toFixed(1)}s) - </div> + <div className={`w-0.5 h-full bg-primary/40 opacity-80`} /> </div> ); } diff --git a/apps/web/src/components/editor/timeline-playhead.tsx b/apps/web/src/components/editor/timeline-playhead.tsx index ce4ed1ff..78aed3a8 100644 --- a/apps/web/src/components/editor/timeline-playhead.tsx +++ b/apps/web/src/components/editor/timeline-playhead.tsx @@ -20,6 +20,7 @@ interface TimelinePlayheadProps { trackLabelsRef?: React.RefObject<HTMLDivElement>;
timelineRef: React.RefObject<HTMLDivElement>;
playheadRef?: React.RefObject<HTMLDivElement>;
+ isSnappingToPlayhead?: boolean;
}
export function TimelinePlayhead({
@@ -34,6 +35,7 @@ export function TimelinePlayhead({ trackLabelsRef,
timelineRef,
playheadRef: externalPlayheadRef,
+ isSnappingToPlayhead = false,
}: TimelinePlayheadProps) {
const internalPlayheadRef = useRef<HTMLDivElement>(null);
const playheadRef = externalPlayheadRef || internalPlayheadRef;
@@ -73,11 +75,15 @@ export function TimelinePlayhead({ }}
onMouseDown={handlePlayheadMouseDown}
>
- {/* The red line spanning full height */}
- <div className="absolute left-0 w-0.5 bg-foreground cursor-col-resize h-full" />
+ {/* The playhead line spanning full height */}
+ <div
+ className={`absolute left-0 w-0.5 cursor-col-resize h-full ${isSnappingToPlayhead ? "bg-primary" : "bg-foreground"}`}
+ />
- {/* Red dot indicator at the top (in ruler area) */}
- <div className="absolute top-1 left-1/2 transform -translate-x-1/2 w-3 h-3 bg-foreground rounded-full border-2 border-foreground shadow-sm" />
+ {/* Playhead dot indicator at the top (in ruler area) */}
+ <div
+ className={`absolute top-1 left-1/2 transform -translate-x-1/2 w-3 h-3 rounded-full border-2 shadow-sm ${isSnappingToPlayhead ? "bg-primary border-primary" : "bg-foreground border-foreground"}`}
+ />
</div>
);
}
diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index aeb6f040..2be143eb 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -49,22 +49,15 @@ export function TimelineTrackContent({ clearSelectedElements, insertTrackAt, snappingEnabled, - gridSnappingEnabled, - elementSnappingEnabled, - playheadSnappingEnabled, - snapThreshold, - gridInterval, } = useTimelineStore(); const { currentTime } = usePlaybackStore(); // Initialize snapping hook const { snapElementPosition } = useTimelineSnapping({ - snapThreshold, - gridInterval, - enableGridSnapping: snappingEnabled && gridSnappingEnabled, - enableElementSnapping: snappingEnabled && elementSnappingEnabled, - enablePlayheadSnapping: snappingEnabled && playheadSnappingEnabled, + snapThreshold: 10, + enableElementSnapping: snappingEnabled, + enablePlayheadSnapping: snappingEnabled, }); const timelineRef = useRef<HTMLDivElement>(null); diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index 882af66e..e83b64b9 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -16,9 +16,7 @@ import { Music, TypeIcon, Magnet, - Grid3X3, - Move, - Target, + Lock, } from "lucide-react"; import { Tooltip, @@ -40,13 +38,6 @@ import { useTimelineZoom } from "@/hooks/use-timeline-zoom"; import { processMediaFiles } from "@/lib/media-processing"; import { toast } from "sonner"; import { useState, useRef, useEffect, useCallback } from "react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "../ui/select"; import { TimelineTrackContent } from "./timeline-track"; import { TimelinePlayhead, @@ -55,7 +46,7 @@ import { import { SelectionBox } from "./selection-box"; import { useSelectionBox } from "@/hooks/use-selection-box"; import { SnapIndicator } from "./snap-indicator"; -import { useTimelineSnapping, SnapPoint } from "@/hooks/use-timeline-snapping"; +import { SnapPoint } from "@/hooks/use-timeline-snapping"; import type { DragData, TimelineTrack } from "@/types/timeline"; import { getTrackHeight, @@ -85,15 +76,7 @@ export function Timeline() { undo, redo, snappingEnabled, - gridSnappingEnabled, - elementSnappingEnabled, - playheadSnappingEnabled, - snapThreshold, - gridInterval, toggleSnapping, - toggleGridSnapping, - toggleElementSnapping, - togglePlayheadSnapping, dragState, } = useTimelineStore(); const { mediaItems, addMediaItem } = useMediaStore(); @@ -135,7 +118,7 @@ export function Timeline() { const lastVerticalSync = useRef(0); // Timeline playhead ruler handlers - const { handleRulerMouseDown, isDraggingRuler } = useTimelinePlayheadRuler({ + const { handleRulerMouseDown } = useTimelinePlayheadRuler({ currentTime, duration, zoomLevel, @@ -162,15 +145,6 @@ export function Timeline() { }, }); - // Initialize snapping functionality - const { snapElementPosition } = useTimelineSnapping({ - snapThreshold, - gridInterval, - enableGridSnapping: snappingEnabled && gridSnappingEnabled, - enableElementSnapping: snappingEnabled && elementSnappingEnabled, - enablePlayheadSnapping: snappingEnabled && playheadSnappingEnabled, - }); - // Calculate snap indicator state const [currentSnapPoint, setCurrentSnapPoint] = useState<SnapPoint | null>( null @@ -724,202 +698,168 @@ export function Timeline() { onMouseLeave={() => setIsInTimeline(false)} > {/* Toolbar */} - <div className="border-b flex items-center px-2 py-1 gap-1"> - <TooltipProvider delayDuration={500}> - {/* Play/Pause Button */} - <Tooltip> - <TooltipTrigger asChild> - <Button - variant="text" - size="icon" - onClick={toggle} - className="mr-2" - > - {isPlaying ? ( - <Pause className="h-4 w-4" /> - ) : ( - <Play className="h-4 w-4" /> - )} - </Button> - </TooltipTrigger> - <TooltipContent> - {isPlaying ? "Pause (Space)" : "Play (Space)"} - </TooltipContent> - </Tooltip> - <div className="w-px h-6 bg-border mx-1" /> - {/* Time Display */} - <div - className="text-xs text-muted-foreground font-mono px-2" - style={{ minWidth: "18ch", textAlign: "center" }} - > - {currentTime.toFixed(1)}s / {duration.toFixed(1)}s - </div> - {/* Test Clip Button - for debugging */} - {tracks.length === 0 && ( - <> - <div className="w-px h-6 bg-border mx-1" /> - <Tooltip> - <TooltipTrigger asChild> - <Button - variant="outline" - size="sm" - onClick={() => { - const trackId = addTrack("media"); - addElementToTrack(trackId, { - type: "media", - mediaId: "test", - name: "Test Clip", - duration: TIMELINE_CONSTANTS.DEFAULT_TEXT_DURATION, - startTime: 0, - trimStart: 0, - trimEnd: 0, - }); - }} - className="text-xs" - > - Add Test Clip - </Button> - </TooltipTrigger> - <TooltipContent>Add a test clip to try playback</TooltipContent> - </Tooltip> - </> - )} - <div className="w-px h-6 bg-border mx-1" /> - <Tooltip> - <TooltipTrigger asChild> - <Button variant="text" size="icon" onClick={handleSplitSelected}> - <Scissors className="h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent>Split element (Ctrl+S)</TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button - variant="text" - size="icon" - onClick={handleSplitAndKeepLeft} - > - <ArrowLeftToLine className="h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent>Split and keep left (Ctrl+Q)</TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button - variant="text" - size="icon" - onClick={handleSplitAndKeepRight} - > - <ArrowRightToLine className="h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent>Split and keep right (Ctrl+W)</TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button variant="text" size="icon" onClick={handleSeparateAudio}> - <SplitSquareHorizontal className="h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent>Separate audio (Ctrl+D)</TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button - variant="text" - size="icon" - onClick={handleDuplicateSelected} - > - <Copy className="h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent>Duplicate element (Ctrl+D)</TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button variant="text" size="icon" onClick={handleFreezeSelected}> - <Snowflake className="h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent>Freeze frame (F)</TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button variant="text" size="icon" onClick={handleDeleteSelected}> - <Trash2 className="h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent>Delete element (Delete)</TooltipContent> - </Tooltip> - - <div className="w-px h-6 bg-border mx-1" /> - - {/* Snapping Controls */} - <Tooltip> - <TooltipTrigger asChild> - <Button - variant={snappingEnabled ? "default" : "text"} - size="icon" - onClick={toggleSnapping} - > - <Magnet className="h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent>Toggle snapping (S)</TooltipContent> - </Tooltip> - - <Tooltip> - <TooltipTrigger asChild> - <Button - variant={ - gridSnappingEnabled && snappingEnabled ? "default" : "text" - } - size="icon" - onClick={toggleGridSnapping} - disabled={!snappingEnabled} - > - <Grid3X3 className="h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent>Toggle grid snapping</TooltipContent> - </Tooltip> - - <Tooltip> - <TooltipTrigger asChild> - <Button - variant={ - elementSnappingEnabled && snappingEnabled ? "default" : "text" - } - size="icon" - onClick={toggleElementSnapping} - disabled={!snappingEnabled} - > - <Move className="h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent>Toggle element snapping</TooltipContent> - </Tooltip> - - <Tooltip> - <TooltipTrigger asChild> - <Button - variant={ - playheadSnappingEnabled && snappingEnabled - ? "default" - : "text" - } - size="icon" - onClick={togglePlayheadSnapping} - disabled={!snappingEnabled} - > - <Target className="h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent>Toggle playhead snapping</TooltipContent> - </Tooltip> - </TooltipProvider> + <div className="border-b flex items-center justify-between px-2 py-1"> + <div className="flex items-center gap-1 w-full"> + <TooltipProvider delayDuration={500}> + {/* Play/Pause Button */} + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="text" + size="icon" + onClick={toggle} + className="mr-2" + > + {isPlaying ? ( + <Pause className="h-4 w-4" /> + ) : ( + <Play className="h-4 w-4" /> + )} + </Button> + </TooltipTrigger> + <TooltipContent> + {isPlaying ? "Pause (Space)" : "Play (Space)"} + </TooltipContent> + </Tooltip> + <div className="w-px h-6 bg-border mx-1" /> + {/* Time Display */} + <div + className="text-xs text-muted-foreground font-mono px-2" + style={{ minWidth: "18ch", textAlign: "center" }} + > + {currentTime.toFixed(1)}s / {duration.toFixed(1)}s + </div> + {/* Test Clip Button - for debugging */} + {tracks.length === 0 && ( + <> + <div className="w-px h-6 bg-border mx-1" /> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="outline" + size="sm" + onClick={() => { + const trackId = addTrack("media"); + addElementToTrack(trackId, { + type: "media", + mediaId: "test", + name: "Test Clip", + duration: TIMELINE_CONSTANTS.DEFAULT_TEXT_DURATION, + startTime: 0, + trimStart: 0, + trimEnd: 0, + }); + }} + className="text-xs" + > + Add Test Clip + </Button> + </TooltipTrigger> + <TooltipContent> + Add a test clip to try playback + </TooltipContent> + </Tooltip> + </> + )} + <div className="w-px h-6 bg-border mx-1" /> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="text" + size="icon" + onClick={handleSplitSelected} + > + <Scissors className="h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent>Split element (Ctrl+S)</TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="text" + size="icon" + onClick={handleSplitAndKeepLeft} + > + <ArrowLeftToLine className="h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent>Split and keep left (Ctrl+Q)</TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="text" + size="icon" + onClick={handleSplitAndKeepRight} + > + <ArrowRightToLine className="h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent>Split and keep right (Ctrl+W)</TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="text" + size="icon" + onClick={handleSeparateAudio} + > + <SplitSquareHorizontal className="h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent>Separate audio (Ctrl+D)</TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="text" + size="icon" + onClick={handleDuplicateSelected} + > + <Copy className="h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent>Duplicate element (Ctrl+D)</TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="text" + size="icon" + onClick={handleFreezeSelected} + > + <Snowflake className="h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent>Freeze frame (F)</TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="text" + size="icon" + onClick={handleDeleteSelected} + > + <Trash2 className="h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent>Delete element (Delete)</TooltipContent> + </Tooltip> + </TooltipProvider> + </div> + <div className="flex items-center gap-1"> + <TooltipProvider delayDuration={500}> + <Tooltip> + <TooltipTrigger asChild> + <Button variant="text" size="icon" onClick={toggleSnapping}> + <Lock className="h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent>Auto snapping</TooltipContent> + </Tooltip> + </TooltipProvider> + </div> </div> {/* Timeline Container */} @@ -939,6 +879,17 @@ export function Timeline() { trackLabelsRef={trackLabelsRef} timelineRef={timelineRef} playheadRef={playheadRef} + isSnappingToPlayhead={ + showSnapIndicator && currentSnapPoint?.type === "playhead" + } + /> + <SnapIndicator + snapPoint={currentSnapPoint} + zoomLevel={zoomLevel} + tracks={tracks} + timelineRef={timelineRef} + trackLabelsRef={trackLabelsRef} + isVisible={showSnapIndicator} /> {/* Timeline Header with Ruler */} <div className="flex bg-panel sticky top-0 z-10"> @@ -1138,17 +1089,6 @@ export function Timeline() { ))} </> )} - - {/* Snap Indicator */} - <SnapIndicator - snapPoint={currentSnapPoint} - zoomLevel={zoomLevel} - timelineHeight={Math.max( - 200, - Math.min(800, getTotalTracksHeight(tracks)) - )} - isVisible={showSnapIndicator} - /> </div> </ScrollArea> </div> diff --git a/apps/web/src/hooks/use-timeline-snapping.ts b/apps/web/src/hooks/use-timeline-snapping.ts index c7e1f15d..f7f96392 100644 --- a/apps/web/src/hooks/use-timeline-snapping.ts +++ b/apps/web/src/hooks/use-timeline-snapping.ts @@ -1,10 +1,10 @@ import { useCallback } from "react"; -import { TimelineElement, TimelineTrack } from "@/types/timeline"; +import { TimelineTrack } from "@/types/timeline"; import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; export interface SnapPoint { time: number; - type: "grid" | "element-start" | "element-end" | "playhead"; + type: "element-start" | "element-end" | "playhead"; elementId?: string; trackId?: string; } @@ -17,16 +17,12 @@ export interface SnapResult { export interface UseTimelineSnappingOptions { snapThreshold?: number; // Distance in pixels to trigger snapping - gridInterval?: number; // Grid interval in seconds - enableGridSnapping?: boolean; enableElementSnapping?: boolean; enablePlayheadSnapping?: boolean; } export function useTimelineSnapping({ snapThreshold = 10, - gridInterval = 1, - enableGridSnapping = true, enableElementSnapping = true, enablePlayheadSnapping = true, }: UseTimelineSnappingOptions = {}) { @@ -40,23 +36,6 @@ export function useTimelineSnapping({ ): SnapPoint[] => { const snapPoints: SnapPoint[] = []; - // Add grid snap points - if (enableGridSnapping) { - const gridStart = Math.floor(currentTime / gridInterval) * gridInterval; - const gridEnd = Math.ceil(currentTime / gridInterval) * gridInterval; - - // Add nearby grid points - for (let i = -2; i <= 2; i++) { - const gridTime = gridStart + i * gridInterval; - if (gridTime >= 0) { - snapPoints.push({ - time: gridTime, - type: "grid", - }); - } - } - } - // Add element snap points if (enableElementSnapping) { tracks.forEach((track) => { @@ -97,12 +76,7 @@ export function useTimelineSnapping({ return snapPoints; }, - [ - enableGridSnapping, - enableElementSnapping, - enablePlayheadSnapping, - gridInterval, - ] + [enableElementSnapping, enablePlayheadSnapping] ); const snapToNearestPoint = useCallback( diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts index 2734903a..f6b9bfd8 100644 --- a/apps/web/src/stores/timeline-store.ts +++ b/apps/web/src/stores/timeline-store.ts @@ -44,19 +44,9 @@ interface TimelineStore { // Snapping settings snappingEnabled: boolean; - gridSnappingEnabled: boolean; - elementSnappingEnabled: boolean; - playheadSnappingEnabled: boolean; - snapThreshold: number; - gridInterval: number; // Snapping actions toggleSnapping: () => void; - toggleGridSnapping: () => void; - toggleElementSnapping: () => void; - togglePlayheadSnapping: () => void; - setSnapThreshold: (threshold: number) => void; - setGridInterval: (interval: number) => void; // Multi-selection selectedElements: { trackId: string; elementId: string }[]; @@ -221,11 +211,6 @@ export const useTimelineStore = create<TimelineStore>((set, get) => { // Snapping settings defaults snappingEnabled: true, - gridSnappingEnabled: true, - elementSnappingEnabled: true, - playheadSnappingEnabled: true, - snapThreshold: 10, // pixels - gridInterval: 1, // seconds getSortedTracks: () => { const { _tracks } = get(); @@ -978,29 +963,5 @@ export const useTimelineStore = create<TimelineStore>((set, get) => { toggleSnapping: () => { set((state) => ({ snappingEnabled: !state.snappingEnabled })); }, - - toggleGridSnapping: () => { - set((state) => ({ gridSnappingEnabled: !state.gridSnappingEnabled })); - }, - - toggleElementSnapping: () => { - set((state) => ({ - elementSnappingEnabled: !state.elementSnappingEnabled, - })); - }, - - togglePlayheadSnapping: () => { - set((state) => ({ - playheadSnappingEnabled: !state.playheadSnappingEnabled, - })); - }, - - setSnapThreshold: (threshold) => { - set({ snapThreshold: Math.max(0, threshold) }); // Ensure non-negative threshold - }, - - setGridInterval: (interval) => { - set({ gridInterval: Math.max(0.1, interval) }); // Ensure minimum interval - }, }; }); |
