From 1fdf2a051529e40cf7a0857d31a83057b878f08d Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 15 Jul 2025 14:16:54 +0200 Subject: [PATCH] cleanup --- .../src/components/editor/snap-indicator.tsx | 65 +-- .../components/editor/timeline-playhead.tsx | 14 +- .../src/components/editor/timeline-track.tsx | 13 +- apps/web/src/components/editor/timeline.tsx | 412 ++++++++---------- apps/web/src/hooks/use-timeline-snapping.ts | 32 +- 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; + trackLabelsRef?: React.RefObject; } export function SnapIndicator({ snapPoint, zoomLevel, - timelineHeight, isVisible, + tracks, + timelineRef, + trackLabelsRef, }: SnapIndicatorProps) { if (!isVisible || !snapPoint) { return null; } + const timelineContainerHeight = timelineRef.current?.offsetHeight || 400; + const totalHeight = timelineContainerHeight - 8; // 8px padding from edges + + // 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 leftPosition = + trackLabelsWidth + snapPoint.time * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; - 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"; - } - }; - - 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 ""; - } - }; - return (
- {/* Snap line */} -
- - {/* Snap label */} -
- {getIndicatorLabel()} ({snapPoint.time.toFixed(1)}s) -
+
); } 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; timelineRef: React.RefObject; playheadRef?: React.RefObject; + isSnappingToPlayhead?: boolean; } export function TimelinePlayhead({ @@ -34,6 +35,7 @@ export function TimelinePlayhead({ trackLabelsRef, timelineRef, playheadRef: externalPlayheadRef, + isSnappingToPlayhead = false, }: TimelinePlayheadProps) { const internalPlayheadRef = useRef(null); const playheadRef = externalPlayheadRef || internalPlayheadRef; @@ -73,11 +75,15 @@ export function TimelinePlayhead({ }} onMouseDown={handlePlayheadMouseDown} > - {/* The red line spanning full height */} -
+ {/* The playhead line spanning full height */} +
- {/* Red dot indicator at the top (in ruler area) */} -
+ {/* Playhead dot indicator at the top (in ruler area) */} +
); } 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(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( null @@ -724,202 +698,168 @@ export function Timeline() { onMouseLeave={() => setIsInTimeline(false)} > {/* Toolbar */} -
- - {/* Play/Pause Button */} - - - - - - {isPlaying ? "Pause (Space)" : "Play (Space)"} - - -
- {/* Time Display */} -
- {currentTime.toFixed(1)}s / {duration.toFixed(1)}s -
- {/* Test Clip Button - for debugging */} - {tracks.length === 0 && ( - <> -
- - - - - Add a test clip to try playback - - - )} -
- - - - - Split element (Ctrl+S) - - - - - - Split and keep left (Ctrl+Q) - - - - - - Split and keep right (Ctrl+W) - - - - - - Separate audio (Ctrl+D) - - - - - - Duplicate element (Ctrl+D) - - - - - - Freeze frame (F) - - - - - - Delete element (Delete) - - -
- - {/* Snapping Controls */} - - - - - Toggle snapping (S) - - - - - - - Toggle grid snapping - - - - - - - Toggle element snapping - - - - - - - Toggle playhead snapping - - +
+
+ + {/* Play/Pause Button */} + + + + + + {isPlaying ? "Pause (Space)" : "Play (Space)"} + + +
+ {/* Time Display */} +
+ {currentTime.toFixed(1)}s / {duration.toFixed(1)}s +
+ {/* Test Clip Button - for debugging */} + {tracks.length === 0 && ( + <> +
+ + + + + + Add a test clip to try playback + + + + )} +
+ + + + + Split element (Ctrl+S) + + + + + + Split and keep left (Ctrl+Q) + + + + + + Split and keep right (Ctrl+W) + + + + + + Separate audio (Ctrl+D) + + + + + + Duplicate element (Ctrl+D) + + + + + + Freeze frame (F) + + + + + + Delete element (Delete) + + +
+
+ + + + + + Auto snapping + + +
{/* Timeline Container */} @@ -939,6 +879,17 @@ export function Timeline() { trackLabelsRef={trackLabelsRef} timelineRef={timelineRef} playheadRef={playheadRef} + isSnappingToPlayhead={ + showSnapIndicator && currentSnapPoint?.type === "playhead" + } + /> + {/* Timeline Header with Ruler */}
@@ -1138,17 +1089,6 @@ export function Timeline() { ))} )} - - {/* Snap Indicator */} -
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((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((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 - }, }; });