chore: implement unsafe changes

This commit is contained in:
Dominik Koch
2025-07-23 16:28:21 +02:00
parent f27f7ec7a3
commit cc70f7dd9e
61 changed files with 679 additions and 529 deletions
@@ -7,7 +7,6 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
export function DeleteProjectDialog({
isOpen,
+10 -10
View File
@@ -18,14 +18,12 @@ export function EditorHeader() {
const [newName, setNewName] = useState(activeProject?.name || "");
const inputRef = useRef<HTMLInputElement>(null);
const handleExport = () => {
// TODO: Implement export functionality
// NOTE: This is already being worked on
console.log("Export project");
};
const handleExport = () => {};
const handleNameClick = () => {
if (!activeProject) return;
if (!activeProject) {
return;
}
setNewName(activeProject.name);
setIsEditing(true);
};
@@ -34,8 +32,7 @@ export function EditorHeader() {
if (activeProject && newName.trim() && newName !== activeProject.name) {
try {
await renameProject(activeProject.id, newName.trim());
} catch (error) {
console.error("Failed to rename project:", error);
} catch (_error) {
setNewName(activeProject.name);
}
}
@@ -43,8 +40,11 @@ export function EditorHeader() {
};
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") handleNameSave();
else if (e.key === "Escape") setIsEditing(false);
if (e.key === "Enter") {
handleNameSave();
} else if (e.key === "Escape") {
setIsEditing(false);
}
};
const leftContent = (
@@ -22,14 +22,16 @@ const AudioWaveform: React.FC<AudioWaveformProps> = ({
let mounted = true;
const initWaveSurfer = async () => {
if (!(waveformRef.current && audioUrl)) return;
if (!(waveformRef.current && audioUrl)) {
return;
}
try {
// Clean up any existing instance
if (wavesurfer.current) {
try {
wavesurfer.current.destroy();
} catch (e) {
} catch (_e) {
// Silently ignore destroy errors
}
wavesurfer.current = null;
@@ -55,8 +57,7 @@ const AudioWaveform: React.FC<AudioWaveformProps> = ({
}
});
wavesurfer.current.on("error", (err) => {
console.error("WaveSurfer error:", err);
wavesurfer.current.on("error", (_err) => {
if (mounted) {
setError(true);
setIsLoading(false);
@@ -64,8 +65,7 @@ const AudioWaveform: React.FC<AudioWaveformProps> = ({
});
await wavesurfer.current.load(audioUrl);
} catch (err) {
console.error("Failed to initialize WaveSurfer:", err);
} catch (_err) {
if (mounted) {
setError(true);
setIsLoading(false);
@@ -80,7 +80,7 @@ const AudioWaveform: React.FC<AudioWaveformProps> = ({
if (wavesurfer.current) {
try {
wavesurfer.current.destroy();
} catch (e) {
} catch (_e) {
// Silently ignore destroy errors
}
wavesurfer.current = null;
@@ -1,5 +1,4 @@
import { Image, Plus, Upload } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Upload } from "lucide-react";
interface MediaDragOverlayProps {
isVisible: boolean;
@@ -16,10 +15,14 @@ export function MediaDragOverlay({
onClick,
isEmptyState = false,
}: MediaDragOverlayProps) {
if (!isVisible) return null;
if (!isVisible) {
return null;
}
const handleClick = (e: React.MouseEvent) => {
if (isProcessing || !onClick) return;
if (isProcessing || !onClick) {
return;
}
e.preventDefault();
e.stopPropagation();
onClick();
@@ -46,7 +46,9 @@ export function TabBar() {
// We're using useEffect because we need to sync with external DOM scroll events
useEffect(() => {
const container = scrollContainerRef.current;
if (!container) return;
if (!container) {
return;
}
checkScrollPosition();
container.addEventListener("scroll", checkScrollPosition);
@@ -58,7 +60,7 @@ export function TabBar() {
container.removeEventListener("scroll", checkScrollPosition);
resizeObserver.disconnect();
};
}, []);
}, [checkScrollPosition]);
return (
<div className="flex">
@@ -106,7 +108,9 @@ function ScrollButton({
onClick: () => void;
isVisible: boolean;
}) {
if (!isVisible) return null;
if (!isVisible) {
return null;
}
const Icon = direction === "left" ? ChevronLeft : ChevronRight;
@@ -36,7 +36,9 @@ export function MediaView() {
const [mediaFilter, setMediaFilter] = useState("all");
const processFiles = async (files: FileList | File[]) => {
if (!files || files.length === 0) return;
if (!files || files.length === 0) {
return;
}
if (!activeProject) {
toast.error("No active project");
return;
@@ -53,9 +55,7 @@ export function MediaView() {
for (const item of processedItems) {
await addMediaItem(activeProject.id, item);
}
} catch (error) {
// Show error toast if processing fails
console.error("Error processing files:", error);
} catch (_error) {
toast.error("Failed to process files");
} finally {
setIsProcessing(false);
@@ -72,7 +72,9 @@ export function MediaView() {
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// When files are selected via file picker, process them
if (e.target.files) processFiles(e.target.files);
if (e.target.files) {
processFiles(e.target.files);
}
e.target.value = ""; // Reset input
};
@@ -46,7 +46,9 @@ export function PreviewPanel() {
useEffect(() => {
const updatePreviewSize = () => {
if (!containerRef.current) return;
if (!containerRef.current) {
return;
}
let availableWidth, availableHeight;
@@ -190,7 +192,9 @@ export function PreviewPanel() {
const backgroundElement = blurBackgroundElements[0];
const { element, mediaItem } = backgroundElement;
if (!mediaItem) return null;
if (!mediaItem) {
return null;
}
const blurIntensity = activeProject.blurIntensity || 8;
@@ -459,7 +463,9 @@ function FullscreenToolbar({
const progress = totalDuration > 0 ? (currentTime / totalDuration) * 100 : 0;
const handleTimelineClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (!hasAnyElements) return;
if (!hasAnyElements) {
return;
}
const rect = e.currentTarget.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const percentage = Math.max(0, Math.min(1, clickX / rect.width));
@@ -468,7 +474,9 @@ function FullscreenToolbar({
};
const handleTimelineDrag = (e: React.MouseEvent<HTMLDivElement>) => {
if (!hasAnyElements) return;
if (!hasAnyElements) {
return;
}
e.preventDefault();
e.stopPropagation();
const rect = e.currentTarget.getBoundingClientRect();
@@ -26,7 +26,7 @@ export function PropertiesPanel() {
const handleFpsChange = (value: string) => {
const fps = Number.parseFloat(value);
if (!isNaN(fps) && fps > 0) {
if (!Number.isNaN(fps) && fps > 0) {
updateProjectFps(fps);
}
};
@@ -59,7 +59,7 @@ export function TextProperties({
className="!text-xs h-7 w-12 rounded-sm text-center [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
onChange={(e) =>
updateTextElement(trackId, element.id, {
fontSize: Number.parseInt(e.target.value),
fontSize: Number.parseInt(e.target.value, 10),
})
}
type="number"
@@ -18,7 +18,9 @@ export function SelectionBox({
const selectionBoxRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!(isActive && startPos && currentPos && containerRef.current)) return;
if (!(isActive && startPos && currentPos && containerRef.current)) {
return;
}
const container = containerRef.current;
const containerRect = container.getBoundingClientRect();
@@ -44,7 +46,9 @@ export function SelectionBox({
}
}, [startPos, currentPos, isActive, containerRef]);
if (!(isActive && startPos && currentPos)) return null;
if (!(isActive && startPos && currentPos)) {
return null;
}
return (
<div
+104 -91
View File
@@ -78,8 +78,8 @@ export function Timeline() {
const { currentTime, duration, seek, setDuration, isPlaying, toggle } =
usePlaybackStore();
const [isDragOver, setIsDragOver] = useState(false);
const [isProcessing, setIsProcessing] = useState(false);
const [progress, setProgress] = useState(0);
const [_isProcessing, setIsProcessing] = useState(false);
const [_progress, setProgress] = useState(0);
const dragCounterRef = useRef(0);
const timelineRef = useRef<HTMLDivElement>(null);
const rulerRef = useRef<HTMLDivElement>(null);
@@ -142,7 +142,6 @@ export function Timeline() {
containerRef: tracksContainerRef,
playheadRef,
onSelectionComplete: (elements) => {
console.log(JSON.stringify({ onSelectionComplete: elements.length }));
setSelectedElements(elements);
},
});
@@ -194,12 +193,6 @@ export function Timeline() {
// Only process as click if we tracked a mouse down on timeline background
if (!isMouseDown) {
console.log(
JSON.stringify({
ignoredClickWithoutMouseDown: true,
timeStamp: e.timeStamp,
})
);
return;
}
@@ -209,15 +202,6 @@ export function Timeline() {
const deltaTime = e.timeStamp - downTime;
if (deltaX > 5 || deltaY > 5 || deltaTime > 500) {
console.log(
JSON.stringify({
ignoredDragNotClick: true,
deltaX,
deltaY,
deltaTime,
timeStamp: e.timeStamp,
})
);
return;
}
@@ -241,9 +225,6 @@ export function Timeline() {
clearSelectedElements();
return;
}
// Clear selected elements when clicking empty timeline area
console.log(JSON.stringify({ clearingSelectedElements: true }));
clearSelectedElements();
// Determine if we're clicking in ruler or tracks area
@@ -259,7 +240,9 @@ export function Timeline() {
const rulerContent = rulerScrollRef.current?.querySelector(
"[data-radix-scroll-area-viewport]"
) as HTMLElement;
if (!rulerContent) return;
if (!rulerContent) {
return;
}
const rect = rulerContent.getBoundingClientRect();
mouseX = e.clientX - rect.left;
scrollLeft = rulerContent.scrollLeft;
@@ -268,7 +251,9 @@ export function Timeline() {
const tracksContent = tracksScrollRef.current?.querySelector(
"[data-radix-scroll-area-viewport]"
) as HTMLElement;
if (!tracksContent) return;
if (!tracksContent) {
return;
}
const rect = tracksContent.getBoundingClientRect();
mouseX = e.clientX - rect.left;
scrollLeft = tracksContent.scrollLeft;
@@ -293,11 +278,10 @@ export function Timeline() {
duration,
zoomLevel,
seek,
rulerScrollRef,
tracksScrollRef,
clearSelectedElements,
isSelecting,
justFinishedSelecting,
activeProject?.fps,
]
);
@@ -305,7 +289,7 @@ export function Timeline() {
useEffect(() => {
const totalDuration = getTotalDuration();
setDuration(Math.max(totalDuration, 10)); // Minimum 10 seconds for empty timeline
}, [tracks, setDuration, getTotalDuration]);
}, [setDuration, getTotalDuration]);
// Old marquee system removed - using new SelectionBox component instead
@@ -374,8 +358,7 @@ export function Timeline() {
useTimelineStore.getState().addMediaToNewTrack(mediaItem);
}
} catch (error) {
console.error("Error parsing dropped item data:", error);
} catch (_error) {
toast.error("Failed to add item to timeline");
}
} else if (e.dataTransfer.files?.length > 0) {
@@ -403,9 +386,7 @@ export function Timeline() {
useTimelineStore.getState().addMediaToNewTrack(addedItem);
}
}
} catch (error) {
// Show error if file processing fails
console.error("Error processing external files:", error);
} catch (_error) {
toast.error("Failed to process dropped files");
} finally {
setIsProcessing(false);
@@ -433,12 +414,16 @@ export function Timeline() {
"[data-radix-scroll-area-viewport]"
) as HTMLElement;
if (!(rulerViewport && tracksViewport)) return;
if (!(rulerViewport && tracksViewport)) {
return;
}
// Horizontal scroll synchronization between ruler and tracks
const handleRulerScroll = () => {
const now = Date.now();
if (isUpdatingRef.current || now - lastRulerSync.current < 16) return;
if (isUpdatingRef.current || now - lastRulerSync.current < 16) {
return;
}
lastRulerSync.current = now;
isUpdatingRef.current = true;
tracksViewport.scrollLeft = rulerViewport.scrollLeft;
@@ -446,7 +431,9 @@ export function Timeline() {
};
const handleTracksScroll = () => {
const now = Date.now();
if (isUpdatingRef.current || now - lastTracksSync.current < 16) return;
if (isUpdatingRef.current || now - lastTracksSync.current < 16) {
return;
}
lastTracksSync.current = now;
isUpdatingRef.current = true;
rulerViewport.scrollLeft = tracksViewport.scrollLeft;
@@ -460,8 +447,9 @@ export function Timeline() {
if (trackLabelsViewport) {
const handleTrackLabelsScroll = () => {
const now = Date.now();
if (isUpdatingRef.current || now - lastVerticalSync.current < 16)
if (isUpdatingRef.current || now - lastVerticalSync.current < 16) {
return;
}
lastVerticalSync.current = now;
isUpdatingRef.current = true;
tracksViewport.scrollTop = trackLabelsViewport.scrollTop;
@@ -469,8 +457,9 @@ export function Timeline() {
};
const handleTracksVerticalScroll = () => {
const now = Date.now();
if (isUpdatingRef.current || now - lastVerticalSync.current < 16)
if (isUpdatingRef.current || now - lastVerticalSync.current < 16) {
return;
}
lastVerticalSync.current = now;
isUpdatingRef.current = true;
trackLabelsViewport.scrollTop = tracksViewport.scrollTop;
@@ -579,12 +568,24 @@ export function Timeline() {
const getTimeInterval = (zoom: number) => {
const pixelsPerSecond =
TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoom;
if (pixelsPerSecond >= 200) return 0.1; // Every 0.1s when very zoomed in
if (pixelsPerSecond >= 100) return 0.5; // Every 0.5s when zoomed in
if (pixelsPerSecond >= 50) return 1; // Every 1s at normal zoom
if (pixelsPerSecond >= 25) return 2; // Every 2s when zoomed out
if (pixelsPerSecond >= 12) return 5; // Every 5s when more zoomed out
if (pixelsPerSecond >= 6) return 10; // Every 10s when very zoomed out
if (pixelsPerSecond >= 200) {
return 0.1; // Every 0.1s when very zoomed in
}
if (pixelsPerSecond >= 100) {
return 0.5; // Every 0.5s when zoomed in
}
if (pixelsPerSecond >= 50) {
return 1; // Every 1s at normal zoom
}
if (pixelsPerSecond >= 25) {
return 2; // Every 2s when zoomed out
}
if (pixelsPerSecond >= 12) {
return 5; // Every 5s when more zoomed out
}
if (pixelsPerSecond >= 6) {
return 10; // Every 10s when very zoomed out
}
return 30; // Every 30s when extremely zoomed out
};
@@ -593,7 +594,9 @@ export function Timeline() {
return Array.from({ length: markerCount }, (_, i) => {
const time = i * interval;
if (time > duration) return null;
if (time > duration) {
return null;
}
const isMainMarker =
time % (interval >= 1 ? Math.max(1, interval) : 1) === 0;
@@ -717,47 +720,43 @@ export function Timeline() {
{tracks.length === 0 ? (
<div />
) : (
<>
{tracks.map((track, index) => (
<ContextMenu key={track.id}>
<ContextMenuTrigger asChild>
<div
className="absolute right-0 left-0 border-muted/30 border-b py-[0.05rem]"
onClick={(e) => {
// If clicking empty area (not on a element), deselect all elements
if (
!(e.target as HTMLElement).closest(
".timeline-element"
)
) {
clearSelectedElements();
}
}}
style={{
top: `${getCumulativeHeightBefore(tracks, index)}px`,
height: `${getTrackHeight(track.type)}px`,
}}
>
<TimelineTrackContent
onSnapPointChange={handleSnapPointChange}
track={track}
zoomLevel={zoomLevel}
/>
</div>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
onClick={() => toggleTrackMute(track.id)}
>
{track.muted ? "Unmute Track" : "Mute Track"}
</ContextMenuItem>
<ContextMenuItem>
Track settings (soon)
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
))}
</>
tracks.map((track, index) => (
<ContextMenu key={track.id}>
<ContextMenuTrigger asChild>
<div
className="absolute right-0 left-0 border-muted/30 border-b py-[0.05rem]"
onClick={(e) => {
// If clicking empty area (not on a element), deselect all elements
if (
!(e.target as HTMLElement).closest(
".timeline-element"
)
) {
clearSelectedElements();
}
}}
style={{
top: `${getCumulativeHeightBefore(tracks, index)}px`,
height: `${getTrackHeight(track.type)}px`,
}}
>
<TimelineTrackContent
onSnapPointChange={handleSnapPointChange}
track={track}
zoomLevel={zoomLevel}
/>
</div>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
onClick={() => toggleTrackMute(track.id)}
>
{track.muted ? "Unmute Track" : "Mute Track"}
</ContextMenuItem>
<ContextMenuItem>Track settings (soon)</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
))
)}
</div>
</ScrollArea>
@@ -812,7 +811,9 @@ function TimelineToolbar({
// Action handlers
const handleSplitSelected = () => {
if (selectedElements.length === 0) return;
if (selectedElements.length === 0) {
return;
}
let splitCount = 0;
selectedElements.forEach(({ trackId, elementId }) => {
const track = tracks.find((t) => t.id === trackId);
@@ -824,7 +825,9 @@ function TimelineToolbar({
(element.duration - element.trimStart - element.trimEnd);
if (currentTime > effectiveStart && currentTime < effectiveEnd) {
const newElementId = splitElement(trackId, elementId, currentTime);
if (newElementId) splitCount++;
if (newElementId) {
splitCount++;
}
}
}
});
@@ -834,9 +837,13 @@ function TimelineToolbar({
};
const handleDuplicateSelected = () => {
if (selectedElements.length === 0) return;
if (selectedElements.length === 0) {
return;
}
const canDuplicate = selectedElements.length === 1;
if (!canDuplicate) return;
if (!canDuplicate) {
return;
}
selectedElements.forEach(({ trackId, elementId }) => {
const track = tracks.find((t) => t.id === trackId);
@@ -868,7 +875,9 @@ function TimelineToolbar({
const { trackId, elementId } = selectedElements[0];
const track = tracks.find((t) => t.id === trackId);
const element = track?.elements.find((c) => c.id === elementId);
if (!element) return;
if (!element) {
return;
}
const effectiveStart = element.startTime;
const effectiveEnd =
element.startTime +
@@ -888,7 +897,9 @@ function TimelineToolbar({
const { trackId, elementId } = selectedElements[0];
const track = tracks.find((t) => t.id === trackId);
const element = track?.elements.find((c) => c.id === elementId);
if (!element) return;
if (!element) {
return;
}
const effectiveStart = element.startTime;
const effectiveEnd =
element.startTime +
@@ -915,7 +926,9 @@ function TimelineToolbar({
};
const handleDeleteSelected = () => {
if (selectedElements.length === 0) return;
if (selectedElements.length === 0) {
return;
}
selectedElements.forEach(({ trackId, elementId }) => {
if (rippleEditingEnabled) {
removeElementFromTrackWithRipple(trackId, elementId);
@@ -1,17 +1,6 @@
"use client";
import {
ChevronLeft,
ChevronRight,
Copy,
MoreVertical,
Music,
RefreshCw,
Scissors,
SplitSquareHorizontal,
Trash2,
Type,
} from "lucide-react";
import { Copy, RefreshCw, Scissors, Trash2 } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import {
@@ -23,8 +12,7 @@ import { useTimelineElementResize } from "@/hooks/use-timeline-element-resize";
import { useMediaStore } from "@/stores/media-store";
import { usePlaybackStore } from "@/stores/playback-store";
import { useTimelineStore } from "@/stores/timeline-store";
import { type TimelineElementProps, TrackType } from "@/types/timeline";
import { Button } from "../../ui/button";
import type { TimelineElementProps } from "@/types/timeline";
import {
ContextMenu,
ContextMenuContent,
@@ -32,16 +20,6 @@ import {
ContextMenuSeparator,
ContextMenuTrigger,
} from "../../ui/context-menu";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "../../ui/dropdown-menu";
import AudioWaveform from "../audio-waveform";
export function TimelineElement({
@@ -69,7 +47,7 @@ export function TimelineElement({
} = useTimelineStore();
const { currentTime } = usePlaybackStore();
const [elementMenuOpen, setElementMenuOpen] = useState(false);
const [_elementMenuOpen, _setElementMenuOpen] = useState(false);
const {
resizing,
@@ -122,7 +100,7 @@ export function TimelineElement({
const { id, ...elementWithoutId } = element;
addElementToTrack(track.id, {
...elementWithoutId,
name: element.name + " (copy)",
name: `${element.name} (copy)`,
startTime:
element.startTime +
(element.duration - element.trimStart - element.trimEnd) +
@@ -150,7 +128,9 @@ export function TimelineElement({
input.accept = "video/*,audio/*,image/*";
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (!file) return;
if (!file) {
return;
}
try {
const success = await replaceElementMedia(track.id, element.id, file);
@@ -159,11 +139,8 @@ export function TimelineElement({
} else {
toast.error("Failed to replace clip");
}
} catch (error) {
} catch (_error) {
toast.error("Failed to replace clip");
console.log(
JSON.stringify({ error: "Failed to replace clip", details: error })
);
}
};
input.click();
@@ -234,8 +211,8 @@ export function TimelineElement({
);
}
const VIDEO_TILE_PADDING = 16;
const OVERLAY_SPACE_MULTIPLIER = 1.5;
const _VIDEO_TILE_PADDING = 16;
const _OVERLAY_SPACE_MULTIPLIER = 1.5;
if (mediaItem.type === "video" && mediaItem.thumbnailUrl) {
const trackHeight = getTrackHeight(track.type);
@@ -329,10 +306,8 @@ export function TimelineElement({
)} ${isSelected ? "border-foreground border-t-[0.5px] border-b-[0.5px]" : ""} ${
isBeingDragged ? "z-50" : "z-10"
}`}
onClick={(e) => onElementClick && onElementClick(e, element)}
onContextMenu={(e) =>
onElementMouseDown && onElementMouseDown(e, element)
}
onClick={(e) => onElementClick?.(e, element)}
onContextMenu={(e) => onElementMouseDown?.(e, element)}
onMouseDown={handleElementMouseDown}
>
<div className="absolute inset-0 flex h-full items-center">
@@ -1,10 +1,7 @@
"use client";
import { useRef } from "react";
import {
getTotalTracksHeight,
TIMELINE_CONSTANTS,
} from "@/constants/timeline-constants";
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
import { useTimelinePlayhead } from "@/hooks/use-timeline-playhead";
import type { TimelineTrack } from "@/types/timeline";
@@ -20,9 +20,7 @@ import type {
} from "@/types/timeline";
import {
canElementGoOnTrack,
ensureMainTrack,
getMainTrack,
sortTracksByOrder,
type TimelineTrack,
} from "@/types/timeline";
import { TimelineElement } from "./timeline-element";
@@ -114,7 +112,7 @@ export function TimelineTrackContent({
const timelineRef = useRef<HTMLDivElement>(null);
const [isDropping, setIsDropping] = useState(false);
const [dropPosition, setDropPosition] = useState<number | null>(null);
const [_dropPosition, setDropPosition] = useState<number | null>(null);
const [wouldOverlap, setWouldOverlap] = useState(false);
const dragCounterRef = useRef(0);
const [mouseDownLocation, setMouseDownLocation] = useState<{
@@ -124,10 +122,14 @@ export function TimelineTrackContent({
// Set up mouse event listeners for drag
useEffect(() => {
if (!dragState.isDragging) return;
if (!dragState.isDragging) {
return;
}
const handleMouseMove = (e: MouseEvent) => {
if (!timelineRef.current) return;
if (!timelineRef.current) {
return;
}
// On first mouse move during drag, ensure the element is selected
if (dragState.elementId && dragState.trackId) {
@@ -218,7 +220,9 @@ export function TimelineTrackContent({
};
const handleMouseUp = (e: MouseEvent) => {
if (!(dragState.elementId && dragState.trackId)) return;
if (!(dragState.elementId && dragState.trackId)) {
return;
}
// If this track initiated the drag, we should handle the mouse up regardless of where it occurs
const isTrackThatStartedDrag = dragState.trackId === track.id;
@@ -249,7 +253,9 @@ export function TimelineTrackContent({
const isMouseOverThisTrack =
e.clientY >= timelineRect.top && e.clientY <= timelineRect.bottom;
if (!(isMouseOverThisTrack || isTrackThatStartedDrag)) return;
if (!(isMouseOverThisTrack || isTrackThatStartedDrag)) {
return;
}
const finalTime = dragState.currentTime;
@@ -394,6 +400,12 @@ export function TimelineTrackContent({
selectedElements,
selectElement,
onSnapPointChange,
currentTime,
rippleEditingEnabled,
snapElementEdge,
snappingEnabled,
track.elements.some,
updateElementStartTimeWithRipple,
]);
const handleElementMouseDown = (
@@ -488,7 +500,9 @@ export function TimelineTrackContent({
"application/x-media-item"
);
if (!(hasTimelineElement || hasMediaItem)) return;
if (!(hasTimelineElement || hasMediaItem)) {
return;
}
// Calculate drop position for overlap checking
const trackContainer = e.currentTarget.querySelector(
@@ -557,7 +571,7 @@ export function TimelineTrackContent({
}
}
}
} catch (error) {
} catch (_error) {
// Continue with default behavior
}
} else if (hasTimelineElement) {
@@ -588,8 +602,12 @@ export function TimelineTrackContent({
const movingElementEnd = snappedTime + movingElementDuration;
wouldOverlap = track.elements.some((existingElement) => {
if (fromTrackId === track.id && existingElement.id === elementId)
if (
fromTrackId === track.id &&
existingElement.id === elementId
) {
return false;
}
const existingStart = existingElement.startTime;
const existingEnd =
@@ -603,7 +621,7 @@ export function TimelineTrackContent({
});
}
}
} catch (error) {
} catch (_error) {
// Continue with default behavior
}
}
@@ -632,7 +650,9 @@ export function TimelineTrackContent({
"application/x-media-item"
);
if (!(hasTimelineElement || hasMediaItem)) return;
if (!(hasTimelineElement || hasMediaItem)) {
return;
}
dragCounterRef.current++;
setIsDropping(true);
@@ -648,7 +668,9 @@ export function TimelineTrackContent({
"application/x-media-item"
);
if (!(hasTimelineElement || hasMediaItem)) return;
if (!(hasTimelineElement || hasMediaItem)) {
return;
}
dragCounterRef.current--;
@@ -663,16 +685,6 @@ export function TimelineTrackContent({
e.preventDefault();
e.stopPropagation();
// Debug logging
console.log(
JSON.stringify({
message: "Drop event started in timeline track",
dataTransferTypes: Array.from(e.dataTransfer.types),
trackId: track.id,
trackType: track.type,
})
);
// Reset all drag states
dragCounterRef.current = 0;
setIsDropping(false);
@@ -685,12 +697,16 @@ export function TimelineTrackContent({
"application/x-media-item"
);
if (!(hasTimelineElement || hasMediaItem)) return;
if (!(hasTimelineElement || hasMediaItem)) {
return;
}
const trackContainer = e.currentTarget.querySelector(
".track-elements-container"
) as HTMLElement;
if (!trackContainer) return;
if (!trackContainer) {
return;
}
const rect = trackContainer.getBoundingClientRect();
const mouseX = Math.max(0, e.clientX - rect.left);
@@ -699,7 +715,7 @@ export function TimelineTrackContent({
mouseX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel);
const projectStore = useProjectStore.getState();
const projectFps = projectStore.activeProject?.fps || 30;
const snappedTime = snapTimeToFrame(newStartTime, projectFps);
const _snappedTime = snapTimeToFrame(newStartTime, projectFps);
// Calculate drop position relative to tracks
const currentTrackIndex = tracks.findIndex((t) => t.id === track.id);
@@ -720,7 +736,9 @@ export function TimelineTrackContent({
const timelineElementData = e.dataTransfer.getData(
"application/x-timeline-element"
);
if (!timelineElementData) return;
if (!timelineElementData) {
return;
}
const {
elementId,
@@ -759,8 +777,9 @@ export function TimelineTrackContent({
const hasOverlap = track.elements.some((existingElement) => {
// Skip the element being moved if it's on the same track
if (fromTrackId === track.id && existingElement.id === elementId)
if (fromTrackId === track.id && existingElement.id === elementId) {
return false;
}
const existingStart = existingElement.startTime;
const existingEnd =
@@ -813,7 +832,9 @@ export function TimelineTrackContent({
const mediaItemData = e.dataTransfer.getData(
"application/x-media-item"
);
if (!mediaItemData) return;
if (!mediaItemData) {
return;
}
const dragData: DragData = JSON.parse(mediaItemData);
@@ -850,7 +871,9 @@ export function TimelineTrackContent({
const newTargetTrack = updatedTracks.find(
(t) => t.id === targetTrackId
);
if (!newTargetTrack) return;
if (!newTargetTrack) {
return;
}
targetTrack = newTargetTrack;
}
@@ -940,7 +963,9 @@ export function TimelineTrackContent({
const newTargetTrack = updatedTracks.find(
(t) => t.id === targetTrackId
);
if (!newTargetTrack) return;
if (!newTargetTrack) {
return;
}
targetTrack = newTargetTrack;
} else if (
mainTrack.elements.length === 0 &&
@@ -970,7 +995,9 @@ export function TimelineTrackContent({
const newTargetTrack = updatedTracks.find(
(t) => t.id === targetTrackId
);
if (!newTargetTrack) return;
if (!newTargetTrack) {
return;
}
targetTrack = newTargetTrack;
}
} else if (isAudio) {
@@ -999,12 +1026,16 @@ export function TimelineTrackContent({
const newTargetTrack = updatedTracks.find(
(t) => t.id === targetTrackId
);
if (!newTargetTrack) return;
if (!newTargetTrack) {
return;
}
targetTrack = newTargetTrack;
}
}
if (!targetTrack) return;
if (!targetTrack) {
return;
}
// Check for overlaps with existing elements in target track
const newElementDuration = mediaItem.duration || 5;
@@ -1046,8 +1077,7 @@ export function TimelineTrackContent({
});
}
}
} catch (error) {
console.error("Error handling drop:", error);
} catch (_error) {
toast.error("Failed to add media to track");
}
};
@@ -1087,66 +1117,64 @@ export function TimelineTrackContent({
: ""}
</div>
) : (
<>
{track.elements.map((element) => {
const isSelected = selectedElements.some(
(c) => c.trackId === track.id && c.elementId === element.id
);
track.elements.map((element) => {
const isSelected = selectedElements.some(
(c) => c.trackId === track.id && c.elementId === element.id
);
const handleElementSplit = () => {
const { currentTime } = usePlaybackStore();
const { splitElement } = useTimelineStore();
const splitTime = currentTime;
const effectiveStart = element.startTime;
const effectiveEnd =
element.startTime +
(element.duration - element.trimStart - element.trimEnd);
const _handleElementSplit = () => {
const { currentTime } = usePlaybackStore();
const { splitElement } = useTimelineStore();
const splitTime = currentTime;
const effectiveStart = element.startTime;
const effectiveEnd =
element.startTime +
(element.duration - element.trimStart - element.trimEnd);
if (splitTime > effectiveStart && splitTime < effectiveEnd) {
const secondElementId = splitElement(
track.id,
element.id,
splitTime
);
if (!secondElementId) {
toast.error("Failed to split element");
}
} else {
toast.error("Playhead must be within element to split");
if (splitTime > effectiveStart && splitTime < effectiveEnd) {
const secondElementId = splitElement(
track.id,
element.id,
splitTime
);
if (!secondElementId) {
toast.error("Failed to split element");
}
};
} else {
toast.error("Playhead must be within element to split");
}
};
const handleElementDuplicate = () => {
const { addElementToTrack } = useTimelineStore.getState();
const { id, ...elementWithoutId } = element;
addElementToTrack(track.id, {
...elementWithoutId,
name: element.name + " (copy)",
startTime:
element.startTime +
(element.duration - element.trimStart - element.trimEnd) +
0.1,
});
};
const _handleElementDuplicate = () => {
const { addElementToTrack } = useTimelineStore.getState();
const { id, ...elementWithoutId } = element;
addElementToTrack(track.id, {
...elementWithoutId,
name: `${element.name} (copy)`,
startTime:
element.startTime +
(element.duration - element.trimStart - element.trimEnd) +
0.1,
});
};
const handleElementDelete = () => {
const { removeElementFromTrack } = useTimelineStore.getState();
removeElementFromTrack(track.id, element.id);
};
const _handleElementDelete = () => {
const { removeElementFromTrack } = useTimelineStore.getState();
removeElementFromTrack(track.id, element.id);
};
return (
<TimelineElement
element={element}
isSelected={isSelected}
key={element.id}
onElementClick={handleElementClick}
onElementMouseDown={handleElementMouseDown}
track={track}
zoomLevel={zoomLevel}
/>
);
})}
</>
return (
<TimelineElement
element={element}
isSelected={isSelected}
key={element.id}
onElementClick={handleElementClick}
onElementMouseDown={handleElementMouseDown}
track={track}
zoomLevel={zoomLevel}
/>
);
})
)}
</div>
</div>
+2 -4
View File
@@ -9,16 +9,14 @@ import { RiDiscordFill, RiTwitterXLine } from "react-icons/ri";
import { getStars } from "@/lib/fetch-github-stars";
export function Footer() {
const [star, setStar] = useState<string>();
const [_star, setStar] = useState<string>();
useEffect(() => {
const fetchStars = async () => {
try {
const data = await getStars();
setStar(data);
} catch (err) {
console.error("Failed to fetch GitHub stars", err);
}
} catch (_err) {}
};
fetchStars();
@@ -32,7 +32,9 @@ const modifier: {
};
function getKeyWithModifier(key: string) {
if (key === "Ctrl") return getPlatformSpecialKey();
if (key === "Ctrl") {
return getPlatformSpecialKey();
}
return modifier[key] || key;
}
@@ -50,8 +52,9 @@ const ShortcutItem = ({
if (
key.includes("Cmd") &&
shortcut.keys.includes(key.replace("Cmd", "Ctrl"))
)
) {
return false;
}
return true;
});
@@ -152,7 +155,9 @@ export const KeyboardShortcutsHelp = () => {
const categories = Array.from(new Set(shortcuts.map((s) => s.category)));
useEffect(() => {
if (!(recordingKey && recordingShortcut)) return;
if (!(recordingKey && recordingShortcut)) {
return;
}
const handleKeyDown = (e: KeyboardEvent) => {
e.preventDefault();
@@ -186,7 +191,7 @@ export const KeyboardShortcutsHelp = () => {
}
};
const handleClickOutside = (e: MouseEvent) => {
const handleClickOutside = (_e: MouseEvent) => {
setRecordingKey(null);
setRecordingShortcut(null);
};
@@ -40,7 +40,9 @@ export function Handlebars({
const measureRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!measureRef.current) return;
if (!measureRef.current) {
return;
}
const measureContent = () => {
if (measureRef.current) {
@@ -56,7 +58,7 @@ export function Handlebars({
const timer = setTimeout(measureContent, 50);
return () => clearTimeout(timer);
}, [children, rightHandleX]);
}, [rightHandleX]);
useEffect(() => {
leftHandleX.set(leftHandle);
@@ -70,7 +72,7 @@ export function Handlebars({
onRangeChange?.(leftHandle, rightHandle);
}, [leftHandle, rightHandle, onRangeChange]);
const handleLeftDrag = (event: any, info: PanInfo) => {
const handleLeftDrag = (_event: any, info: PanInfo) => {
const newLeft = Math.max(
0,
Math.min(leftHandle + info.offset.x, rightHandle - minWidth)
@@ -78,7 +80,7 @@ export function Handlebars({
setLeftHandle(newLeft);
};
const handleRightDrag = (event: any, info: PanInfo) => {
const handleRightDrag = (_event: any, info: PanInfo) => {
const newRight = Math.max(
leftHandle + minWidth,
Math.min(contentWidth, rightHandle + info.offset.x)
+1 -1
View File
@@ -77,7 +77,7 @@ function Title({ title }: { title: string }) {
return <h2 className="font-bold text-lg md:text-xl">{title}</h2>;
}
function Subtitle({ subtitle }: { subtitle: string }) {
function _Subtitle({ subtitle }: { subtitle: string }) {
return <h3 className="font-medium text-lg">{subtitle}</h3>;
}
@@ -3,7 +3,6 @@
import { createContext, useContext, useEffect, useState } from "react";
import { toast } from "sonner";
import { storageService } from "@/lib/storage/storage-service";
import { useMediaStore } from "@/stores/media-store";
import { useProjectStore } from "@/stores/project-store";
interface StorageContextType {
@@ -61,7 +60,6 @@ export function StorageProvider({ children }: StorageProviderProps) {
error: null,
});
} catch (error) {
console.error("Failed to initialize storage:", error);
setStatus({
isInitialized: false,
isLoading: false,
+9 -3
View File
@@ -33,7 +33,9 @@ export function AudioPlayer({
// Sync playback events
useEffect(() => {
const audio = audioRef.current;
if (!(audio && isInClipRange)) return;
if (!(audio && isInClipRange)) {
return;
}
const handleSeekEvent = (e: CustomEvent) => {
// Always update audio time, even if outside clip range
@@ -94,7 +96,9 @@ export function AudioPlayer({
// Sync playback state
useEffect(() => {
const audio = audioRef.current;
if (!audio) return;
if (!audio) {
return;
}
if (isPlaying && isInClipRange && !trackMuted) {
audio.play().catch(() => {});
@@ -106,7 +110,9 @@ export function AudioPlayer({
// Sync volume and speed
useEffect(() => {
const audio = audioRef.current;
if (!audio) return;
if (!audio) {
return;
}
audio.volume = volume;
audio.muted = muted || trackMuted;
-5
View File
@@ -2,11 +2,6 @@
import * as React from "react";
import * as RechartsPrimitive from "recharts";
import {
NameType,
Payload,
ValueType,
} from "recharts/types/component/DefaultTooltipContent";
import { cn } from "../../lib/utils";
@@ -52,7 +52,9 @@ export function DraggableMediaItem({
"data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
useEffect(() => {
if (!isDragging) return;
if (!isDragging) {
return;
}
const handleDragOver = (e: DragEvent) => {
setDragPosition({ x: e.clientX, y: e.clientY });
@@ -8,7 +8,6 @@ const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
aria-label="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
role="navigation"
{...props}
/>
);
+2 -2
View File
@@ -94,7 +94,7 @@ const SidebarProvider = React.forwardRef<
return isMobile
? setOpenMobile((open) => !open)
: setOpen((open) => !open);
}, [isMobile, setOpen, setOpenMobile]);
}, [isMobile, setOpen]);
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
@@ -126,7 +126,7 @@ const SidebarProvider = React.forwardRef<
setOpenMobile,
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
[state, open, setOpen, isMobile, openMobile, toggleSidebar]
);
return (
+9 -3
View File
@@ -33,7 +33,9 @@ export function VideoPlayer({
// Sync playback events
useEffect(() => {
const video = videoRef.current;
if (!(video && isInClipRange)) return;
if (!(video && isInClipRange)) {
return;
}
const handleSeekEvent = (e: CustomEvent) => {
// Always update video time, even if outside clip range
@@ -94,7 +96,9 @@ export function VideoPlayer({
// Sync playback state
useEffect(() => {
const video = videoRef.current;
if (!video) return;
if (!video) {
return;
}
if (isPlaying && isInClipRange) {
video.play().catch(() => {});
@@ -106,7 +110,9 @@ export function VideoPlayer({
// Sync volume and speed
useEffect(() => {
const video = videoRef.current;
if (!video) return;
if (!video) {
return;
}
video.volume = volume;
video.muted = muted;