From 8545d95070890edfcf76b788ee023f9d510c302d Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Sat, 12 Jul 2025 12:48:31 +0200 Subject: refactor: add FPS and remove turbopack (yes, fuck you turbopack) --- apps/web/src/components/editor/preview-panel.tsx | 13 ++++++-- .../components/editor/properties-panel/index.tsx | 36 +++++++++++++++++++-- apps/web/src/components/editor/timeline-track.tsx | 37 ++++++++++++++++------ apps/web/src/components/editor/timeline.tsx | 17 +++++----- 4 files changed, 81 insertions(+), 22 deletions(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 5003e79b..894b6257 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -390,6 +390,7 @@ function PreviewToolbar({ hasAnyElements }: { hasAnyElements: boolean }) { const { isPlaying, toggle, currentTime } = usePlaybackStore(); const { setCanvasSize, setCanvasSizeToOriginal } = useEditorStore(); const { getTotalDuration } = useTimelineStore(); + const { activeProject } = useProjectStore(); const { currentPreset, isOriginal, @@ -420,11 +421,19 @@ function PreviewToolbar({ hasAnyElements }: { hasAnyElements: boolean }) { )} > - {formatTimeCode(currentTime, "HH:MM:SS:CS")} + {formatTimeCode( + currentTime, + "HH:MM:SS:FF", + activeProject?.fps || 30 + )} / - {formatTimeCode(getTotalDuration(), "HH:MM:SS:CS")} + {formatTimeCode( + getTotalDuration(), + "HH:MM:SS:FF", + activeProject?.fps || 30 + )}

diff --git a/apps/web/src/components/editor/properties-panel/index.tsx b/apps/web/src/components/editor/properties-panel/index.tsx index 0e6cb8a8..9e28850f 100644 --- a/apps/web/src/components/editor/properties-panel/index.tsx +++ b/apps/web/src/components/editor/properties-panel/index.tsx @@ -9,13 +9,28 @@ import { useMediaStore } from "@/stores/media-store"; import { AudioProperties } from "./audio-properties"; import { MediaProperties } from "./media-properties"; import { TextProperties } from "./text-properties"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../../ui/select"; +import { FPS_PRESETS } from "@/constants/timeline-constants"; export function PropertiesPanel() { - const { activeProject } = useProjectStore(); + const { activeProject, updateProjectFps } = useProjectStore(); const { getDisplayName, canvasSize } = useAspectRatio(); const { selectedElements, tracks } = useTimelineStore(); const { mediaItems } = useMediaStore(); + const handleFpsChange = (value: string) => { + const fps = parseFloat(value); + if (!isNaN(fps) && fps > 0) { + updateProjectFps(fps); + } + }; + const emptyView = (
{/* Media Properties */} @@ -26,7 +41,24 @@ export function PropertiesPanel() { label="Resolution:" value={`${canvasSize.width} × ${canvasSize.height}`} /> - +
+ + +
); diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index 7e2e93e0..c3a16b9a 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -17,7 +17,11 @@ import type { TimelineElement as TimelineElementType, DragData, } from "@/types/timeline"; -import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; +import { + snapTimeToFrame, + TIMELINE_CONSTANTS, +} from "@/constants/timeline-constants"; +import { useProjectStore } from "@/stores/project-store"; export function TimelineTrackContent({ track, @@ -80,7 +84,10 @@ export function TimelineTrackContent({ mouseX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel) ); const adjustedTime = Math.max(0, mouseTime - dragState.clickOffsetTime); - const snappedTime = Math.round(adjustedTime * 10) / 10; + // Use frame snapping if project has FPS, otherwise use decimal snapping + const projectStore = useProjectStore.getState(); + const projectFps = projectStore.activeProject?.fps || 30; + const snappedTime = snapTimeToFrame(adjustedTime, projectFps); updateDragTime(snappedTime); }; @@ -342,7 +349,9 @@ export function TimelineTrackContent({ if (dragData.type === "text") { // Text elements have default duration of 5 seconds const newElementDuration = 5; - const snappedTime = Math.round(dropTime * 10) / 10; + const projectStore = useProjectStore.getState(); + const projectFps = projectStore.activeProject?.fps || 30; + const snappedTime = snapTimeToFrame(dropTime, projectFps); const newElementEnd = snappedTime + newElementDuration; wouldOverlap = track.elements.some((existingElement) => { @@ -361,7 +370,9 @@ export function TimelineTrackContent({ ); if (mediaItem) { const newElementDuration = mediaItem.duration || 5; - const snappedTime = Math.round(dropTime * 10) / 10; + const projectStore = useProjectStore.getState(); + const projectFps = projectStore.activeProject?.fps || 30; + const snappedTime = snapTimeToFrame(dropTime, projectFps); const newElementEnd = snappedTime + newElementDuration; wouldOverlap = track.elements.some((existingElement) => { @@ -401,7 +412,9 @@ export function TimelineTrackContent({ movingElement.duration - movingElement.trimStart - movingElement.trimEnd; - const snappedTime = Math.round(dropTime * 10) / 10; + const projectStore = useProjectStore.getState(); + const projectFps = projectStore.activeProject?.fps || 30; + const snappedTime = snapTimeToFrame(dropTime, projectFps); const movingElementEnd = snappedTime + movingElementDuration; wouldOverlap = track.elements.some((existingElement) => { @@ -428,13 +441,17 @@ export function TimelineTrackContent({ if (wouldOverlap) { e.dataTransfer.dropEffect = "none"; setWouldOverlap(true); - setDropPosition(Math.round(dropTime * 10) / 10); + const projectStore = useProjectStore.getState(); + const projectFps = projectStore.activeProject?.fps || 30; + setDropPosition(snapTimeToFrame(dropTime, projectFps)); return; } e.dataTransfer.dropEffect = hasTimelineElement ? "move" : "copy"; setWouldOverlap(false); - setDropPosition(Math.round(dropTime * 10) / 10); + const projectStore = useProjectStore.getState(); + const projectFps = projectStore.activeProject?.fps || 30; + setDropPosition(snapTimeToFrame(dropTime, projectFps)); }; const handleTrackDragEnter = (e: React.DragEvent) => { @@ -502,7 +519,9 @@ export function TimelineTrackContent({ const mouseY = e.clientY - rect.top; // Get Y position relative to this track const newStartTime = mouseX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel); - const snappedTime = Math.round(newStartTime * 10) / 10; + const projectStore = useProjectStore.getState(); + const projectFps = projectStore.activeProject?.fps || 30; + const snappedTime = snapTimeToFrame(newStartTime, projectFps); // Calculate drop position relative to tracks const currentTrackIndex = tracks.findIndex((t) => t.id === track.id); @@ -548,7 +567,7 @@ export function TimelineTrackContent({ const adjustedStartTime = snappedTime - clickOffsetTime; const finalStartTime = Math.max( 0, - Math.round(adjustedStartTime * 10) / 10 + snapTimeToFrame(adjustedStartTime, projectFps) ); // Check for overlaps with existing elements (excluding the moving element itself) diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index 4fee16fc..a2b798e1 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -81,14 +81,8 @@ export function Timeline() { } = useTimelineStore(); const { mediaItems, addMediaItem } = useMediaStore(); const { activeProject } = useProjectStore(); - const { - currentTime, - duration, - seek, - setDuration, - isPlaying, - toggle, - } = usePlaybackStore(); + const { currentTime, duration, seek, setDuration, isPlaying, toggle } = + usePlaybackStore(); const [isDragOver, setIsDragOver] = useState(false); const [isProcessing, setIsProcessing] = useState(false); const [progress, setProgress] = useState(0); @@ -215,7 +209,7 @@ export function Timeline() { scrollLeft = tracksContent.scrollLeft; } - const time = Math.max( + const rawTime = Math.max( 0, Math.min( duration, @@ -224,6 +218,11 @@ export function Timeline() { ) ); + // Use frame snapping for timeline clicking + const projectFps = activeProject?.fps || 30; + const { snapTimeToFrame } = require("@/constants/timeline-constants"); + const time = snapTimeToFrame(rawTime, projectFps); + seek(time); }, [ -- cgit v1.3.1