cleanup (this doesn't work)

This commit is contained in:
Maze Winther 2025-07-16 14:37:08 +02:00
parent 527d588897
commit 8bc26539eb
11 changed files with 388 additions and 577 deletions

View File

@ -37,7 +37,7 @@
"embla-carousel-react": "^8.5.1",
"framer-motion": "^11.13.1",
"input-otp": "^1.4.1",
"lucide-react": "^0.525.0",
"lucide-react": "^0.468.0",
"motion": "^12.18.1",
"next": "^15.4.1",
"next-themes": "^0.4.4",

View File

@ -15,7 +15,7 @@ import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import Link from "next/link";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { ChevronLeft, Loader2 } from "lucide-react";
import { ArrowLeft, Loader2 } from "lucide-react";
import { GoogleIcon } from "@/components/icons";
import { useLogin } from "@/hooks/auth/useLogin";
@ -41,7 +41,7 @@ const LoginPage = () => {
onClick={() => router.back()}
className="absolute top-6 left-6"
>
<ChevronLeft className="h-5 w-5" /> Back
<ArrowLeft className="h-5 w-5" /> Back
</Button>
<Card className="w-[400px] shadow-lg border-0">
<CardHeader className="text-center pb-4">
@ -119,7 +119,11 @@ const LoginPage = () => {
className="w-full h-11"
size="lg"
>
{isEmailLoading ? <Loader2 className="animate-spin" /> : "Sign in"}
{isEmailLoading ? (
<Loader2 className="animate-spin" />
) : (
"Sign in"
)}
</Button>
</div>
</div>
@ -137,6 +141,6 @@ const LoginPage = () => {
</Card>
</div>
);
}
};
export default memo(LoginPage);

View File

@ -15,7 +15,7 @@ import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import Link from "next/link";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { ChevronLeft, Loader2 } from "lucide-react";
import { ArrowLeft, Loader2 } from "lucide-react";
import { GoogleIcon } from "@/components/icons";
import { useSignUp } from "@/hooks/auth/useSignUp";
@ -43,7 +43,7 @@ const SignUpPage = () => {
onClick={() => router.back()}
className="absolute top-6 left-6"
>
<ChevronLeft className="h-5 w-5" /> Back
<ArrowLeft className="h-5 w-5" /> Back
</Button>
<Card className="w-[400px] shadow-lg border-0">
<CardHeader className="text-center pb-4">
@ -157,6 +157,6 @@ const SignUpPage = () => {
</Card>
</div>
);
}
};
export default memo(SignUpPage);

View File

@ -3,6 +3,7 @@
import { useEffect } from "react";
import { Loader2 } from "lucide-react";
import { useEditorStore } from "@/stores/editor-store";
import { useKeyboardShortcuts } from "@/hooks/use-keyboard-shortcuts";
interface EditorProviderProps {
children: React.ReactNode;
@ -11,6 +12,11 @@ interface EditorProviderProps {
export function EditorProvider({ children }: EditorProviderProps) {
const { isInitializing, isPanelsReady, initializeApp } = useEditorStore();
useKeyboardShortcuts({
enabled: !isInitializing && isPanelsReady,
context: "editor",
});
useEffect(() => {
initializeApp();
}, [initializeApp]);

View File

@ -1,6 +1,5 @@
"use client";
// @ts-ignore - IDE TypeScript configuration issue (React modules compile fine in Docker)
import { ScrollArea } from "../ui/scroll-area";
import { Button } from "../ui/button";
import {
@ -16,7 +15,6 @@ import {
Video,
Music,
TypeIcon,
Magnet,
Lock,
LockOpen,
} from "lucide-react";
@ -39,9 +37,6 @@ import { useProjectStore } from "@/stores/project-store";
import { useTimelineZoom } from "@/hooks/use-timeline-zoom";
import { processMediaFiles } from "@/lib/media-processing";
import { toast } from "sonner";
// @ts-ignore - React type definitions
import * as React from "react";
// @ts-ignore - React type definitions
import { useState, useRef, useEffect, useCallback } from "react";
import { TimelineTrackContent } from "./timeline-track";
import {
@ -65,9 +60,7 @@ export function Timeline() {
// Timeline shows all tracks (video, audio, effects) and their elements.
// You can drag media here to add it to your project.
// elements can be trimmed, deleted, and moved.
// Note: Some parameter types are inferred as 'any' due to store interface definitions
// This doesn't affect runtime safety as the stores provide proper type checking
const {
tracks,
addTrack,
@ -265,259 +258,6 @@ export function Timeline() {
setDuration(Math.max(totalDuration, 10)); // Minimum 10 seconds for empty timeline
}, [tracks, setDuration, getTotalDuration]);
// Keyboard event for deleting selected elements
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Don't trigger when typing in input fields or textareas
if (
e.target instanceof HTMLInputElement ||
e.target instanceof HTMLTextAreaElement
) {
return;
}
// Only trigger when timeline is focused or mouse is over timeline
if (
!isInTimeline &&
!timelineRef.current?.contains(document.activeElement)
) {
return;
}
if (
(e.key === "Delete" || e.key === "Backspace") &&
selectedElements.length > 0
) {
selectedElements.forEach(({ trackId, elementId }: { trackId: string; elementId: string }) => {
removeElementFromTrack(trackId, elementId);
});
clearSelectedElements();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [
selectedElements,
removeElementFromTrack,
clearSelectedElements,
isInTimeline,
]);
// Keyboard event for undo (Cmd+Z)
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === "z" && !e.shiftKey) {
e.preventDefault();
undo();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [undo]);
// Keyboard event for redo (Cmd+Shift+Z or Cmd+Y)
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === "z" && e.shiftKey) {
e.preventDefault();
redo();
} else if ((e.metaKey || e.ctrlKey) && e.key === "y") {
e.preventDefault();
redo();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [redo]);
// Core video editor keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Don't interfere with typing in inputs
const activeElement = document.activeElement as HTMLElement;
const isInputFocused = activeElement && (
activeElement.tagName === 'INPUT' ||
activeElement.tagName === 'TEXTAREA' ||
activeElement.contentEditable === 'true'
);
if (isInputFocused) return;
const { key, metaKey, ctrlKey, shiftKey } = e;
const isModified = metaKey || ctrlKey || shiftKey;
switch (key) {
case ' ': // Spacebar - Play/Pause
if (!isModified) {
e.preventDefault();
toggle();
toast.info(isPlaying ? 'Paused' : 'Playing', { duration: 1000 });
}
break;
case 'j':
case 'J':
if (!isModified) {
e.preventDefault();
// J - Rewind 1 second
seek(Math.max(0, currentTime - 1));
toast.info('Rewind 1s', { duration: 1000 });
}
break;
case 'k':
case 'K':
if (!isModified) {
e.preventDefault();
// K - Pause/Play toggle
toggle();
toast.info(isPlaying ? 'Paused' : 'Playing', { duration: 1000 });
}
break;
case 'l':
case 'L':
if (!isModified) {
e.preventDefault();
// L - Fast forward 1 second
seek(Math.min(duration, currentTime + 1));
toast.info('Forward 1s', { duration: 1000 });
}
break;
case 'ArrowLeft':
if (shiftKey) {
e.preventDefault();
// Shift+Left - Jump back 5 seconds
seek(Math.max(0, currentTime - 5));
} else if (!isModified) {
e.preventDefault();
// Left - Frame step backward (1 frame)
const projectFps = activeProject?.fps || 30;
seek(Math.max(0, currentTime - (1/projectFps)));
}
break;
case 'ArrowRight':
if (shiftKey) {
e.preventDefault();
// Shift+Right - Jump forward 5 seconds
seek(Math.min(duration, currentTime + 5));
} else if (!isModified) {
e.preventDefault();
// Right - Frame step forward (1 frame)
const projectFps = activeProject?.fps || 30;
seek(Math.min(duration, currentTime + (1/projectFps)));
}
break;
case 'Home':
if (!isModified) {
e.preventDefault();
seek(0);
toast.info('Start of timeline', { duration: 1000 });
}
break;
case 'End':
if (!isModified) {
e.preventDefault();
seek(duration);
toast.info('End of timeline', { duration: 1000 });
}
break;
case 's':
case 'S':
if (!isModified && selectedElements.length === 1) {
e.preventDefault();
// S - Split element at playhead
const { trackId, elementId } = selectedElements[0];
const track = tracks.find((t: any) => t.id === trackId);
const element = track?.elements.find((el: any) => el.id === elementId);
if (element) {
const effectiveStart = element.startTime;
const effectiveEnd = element.startTime + (element.duration - element.trimStart - element.trimEnd);
if (currentTime > effectiveStart && currentTime < effectiveEnd) {
splitElement(trackId, elementId, currentTime);
toast.success('Element split at playhead');
} else {
toast.error('Playhead must be within selected element');
}
}
}
break;
case 'n':
case 'N':
if (!isModified) {
e.preventDefault();
toggleSnapping();
toast.info(`Snapping ${snappingEnabled ? 'disabled' : 'enabled'}`, { duration: 1000 });
}
break;
}
// Multi-key shortcuts
if ((metaKey || ctrlKey) && !shiftKey) {
switch (key.toLowerCase()) {
case 'a':
e.preventDefault();
// Cmd/Ctrl+A - Select all elements
const allElements = tracks.flatMap((track: any) =>
track.elements.map((element: any) => ({
trackId: track.id,
elementId: element.id
}))
);
setSelectedElements(allElements);
toast.info(`Selected ${allElements.length} elements`, { duration: 1000 });
break;
case 'd':
if (selectedElements.length === 1) {
e.preventDefault();
// Cmd/Ctrl+D - Duplicate selected element
const { trackId, elementId } = selectedElements[0];
const track = tracks.find((t: any) => t.id === trackId);
const element = track?.elements.find((el: any) => el.id === elementId);
if (element) {
const newStartTime = element.startTime + (element.duration - element.trimStart - element.trimEnd) + 0.1;
const { id, ...elementWithoutId } = element;
addElementToTrack(trackId, {
...elementWithoutId,
startTime: newStartTime,
});
toast.success('Element duplicated');
}
}
break;
}
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [
isPlaying,
currentTime,
duration,
selectedElements,
tracks,
toggle,
seek,
splitElement,
snappingEnabled,
toggleSnapping,
setSelectedElements,
addElementToTrack
]);
// Old marquee system removed - using new SelectionBox component instead
const handleDragEnter = (e: React.DragEvent) => {
@ -575,7 +315,9 @@ export function Timeline() {
useTimelineStore.getState().addTextToNewTrack(dragData);
} else {
// Handle media items
const mediaItem = mediaItems.find((item: any) => item.id === dragData.id);
const mediaItem = mediaItems.find(
(item: any) => item.id === dragData.id
);
if (!mediaItem) {
toast.error("Media item not found");
return;

View File

@ -11,120 +11,8 @@ import {
DialogTrigger,
} from "./ui/dialog";
import { Badge } from "./ui/badge";
import { Keyboard, Play, Pause, SkipBack, SkipForward } from "lucide-react";
interface Shortcut {
keys: string[];
description: string;
category: string;
icon?: React.ReactNode;
}
const shortcuts: Shortcut[] = [
// Playback Controls
{
keys: ["Space"],
description: "Play/Pause",
category: "Playback",
icon: <Play className="w-3 h-3" />
},
{
keys: ["J"],
description: "Rewind 1 second",
category: "Playback",
icon: <SkipBack className="w-3 h-3" />
},
{
keys: ["K"],
description: "Play/Pause (alternative)",
category: "Playback",
icon: <Pause className="w-3 h-3" />
},
{
keys: ["L"],
description: "Fast forward 1 second",
category: "Playback",
icon: <SkipForward className="w-3 h-3" />
},
// Navigation
{
keys: ["←"],
description: "Frame step backward",
category: "Navigation"
},
{
keys: ["→"],
description: "Frame step forward",
category: "Navigation"
},
{
keys: ["Shift", "←"],
description: "Jump back 5 seconds",
category: "Navigation"
},
{
keys: ["Shift", "→"],
description: "Jump forward 5 seconds",
category: "Navigation"
},
{
keys: ["Home"],
description: "Go to timeline start",
category: "Navigation"
},
{
keys: ["End"],
description: "Go to timeline end",
category: "Navigation"
},
// Editing
{
keys: ["S"],
description: "Split element at playhead",
category: "Editing"
},
{
keys: ["Delete", "Backspace"],
description: "Delete selected elements",
category: "Editing"
},
{
keys: ["N"],
description: "Toggle snapping",
category: "Editing"
},
// Selection & Organization
{
keys: ["Cmd", "A"],
description: "Select all elements",
category: "Selection"
},
{
keys: ["Cmd", "D"],
description: "Duplicate selected element",
category: "Selection"
},
// History
{
keys: ["Cmd", "Z"],
description: "Undo",
category: "History"
},
{
keys: ["Cmd", "Shift", "Z"],
description: "Redo",
category: "History"
},
{
keys: ["Cmd", "Y"],
description: "Redo (alternative)",
category: "History"
}
];
import { Keyboard } from "lucide-react";
import { useKeyboardShortcuts } from "@/hooks/use-keyboard-shortcuts";
const KeyBadge = ({ keyName }: { keyName: string }) => {
// Replace common key names with symbols
@ -142,7 +30,7 @@ const KeyBadge = ({ keyName }: { keyName: string }) => {
);
};
const ShortcutItem = ({ shortcut }: { shortcut: Shortcut }) => (
const ShortcutItem = ({ shortcut }: { shortcut: any }) => (
<div className="flex items-center justify-between py-2 px-3 rounded-lg hover:bg-muted/50">
<div className="flex items-center gap-3">
{shortcut.icon && (
@ -151,7 +39,7 @@ const ShortcutItem = ({ shortcut }: { shortcut: Shortcut }) => (
<span className="text-sm">{shortcut.description}</span>
</div>
<div className="flex items-center gap-1">
{shortcut.keys.map((key, index) => (
{shortcut.keys.map((key: string, index: number) => (
<div key={index} className="flex items-center gap-1">
<KeyBadge keyName={key} />
{index < shortcut.keys.length - 1 && (
@ -166,7 +54,10 @@ const ShortcutItem = ({ shortcut }: { shortcut: Shortcut }) => (
export const KeyboardShortcutsHelp = () => {
const [open, setOpen] = useState(false);
const categories = Array.from(new Set(shortcuts.map(s => s.category)));
// Get shortcuts from centralized hook (disabled so it doesn't add event listeners)
const { shortcuts } = useKeyboardShortcuts({ enabled: false });
const categories = Array.from(new Set(shortcuts.map((s) => s.category)));
return (
<Dialog open={open} onOpenChange={setOpen}>
@ -187,16 +78,16 @@ export const KeyboardShortcutsHelp = () => {
Most shortcuts work when the timeline is focused.
</DialogDescription>
</DialogHeader>
<div className="space-y-6">
{categories.map(category => (
{categories.map((category) => (
<div key={category}>
<h3 className="font-semibold text-sm text-muted-foreground uppercase tracking-wide mb-3">
{category}
</h3>
<div className="space-y-1">
{shortcuts
.filter(shortcut => shortcut.category === category)
.filter((shortcut) => shortcut.category === category)
.map((shortcut, index) => (
<ShortcutItem key={index} shortcut={shortcut} />
))}
@ -208,7 +99,9 @@ export const KeyboardShortcutsHelp = () => {
<div className="mt-6 p-4 bg-muted/30 rounded-lg">
<h4 className="font-medium text-sm mb-2">Tips:</h4>
<ul className="text-sm text-muted-foreground space-y-1">
<li> Shortcuts work when the editor is focused (not typing in inputs)</li>
<li>
Shortcuts work when the editor is focused (not typing in inputs)
</li>
<li> J/K/L are industry-standard video editing shortcuts</li>
<li> Use arrow keys for frame-perfect positioning</li>
<li> Hold Shift with arrow keys for larger jumps</li>
@ -217,4 +110,4 @@ export const KeyboardShortcutsHelp = () => {
</DialogContent>
</Dialog>
);
};
};

View File

@ -0,0 +1,349 @@
"use client";
import { useEffect, useCallback } from "react";
import { useTimelineStore } from "@/stores/timeline-store";
import { usePlaybackStore } from "@/stores/playback-store";
import { useProjectStore } from "@/stores/project-store";
import { toast } from "sonner";
export interface KeyboardShortcut {
id: string;
keys: string[];
description: string;
category: string;
action: () => void;
enabled?: boolean;
requiresSelection?: boolean;
icon?: React.ReactNode;
}
interface UseKeyboardShortcutsOptions {
enabled?: boolean;
context?: "global" | "timeline" | "editor";
}
export const useKeyboardShortcuts = (
options: UseKeyboardShortcutsOptions = {}
) => {
const { enabled = true, context = "editor" } = options;
const {
tracks,
selectedElements,
clearSelectedElements,
setSelectedElements,
removeElementFromTrack,
splitElement,
addElementToTrack,
snappingEnabled,
toggleSnapping,
undo,
redo,
} = useTimelineStore();
const { currentTime, duration, isPlaying, toggle, seek } = usePlaybackStore();
const { activeProject } = useProjectStore();
// Check if user is typing in an input field
const isInputFocused = useCallback(() => {
const activeElement = document.activeElement as HTMLElement;
return (
activeElement &&
(activeElement.tagName === "INPUT" ||
activeElement.tagName === "TEXTAREA" ||
activeElement.contentEditable === "true")
);
}, []);
// Define all shortcuts in one place
const shortcuts: KeyboardShortcut[] = [
// Playback Controls
{
id: "play-pause",
keys: ["Space"],
description: "Play/Pause",
category: "Playback",
action: () => {
toggle();
toast.info(isPlaying ? "Paused" : "Playing", { duration: 1000 });
},
},
{
id: "rewind",
keys: ["j", "J"],
description: "Rewind 1 second",
category: "Playback",
action: () => {
seek(Math.max(0, currentTime - 1));
toast.info("Rewind 1s", { duration: 1000 });
},
},
{
id: "play-pause-alt",
keys: ["k", "K"],
description: "Play/Pause (alternative)",
category: "Playback",
action: () => {
toggle();
toast.info(isPlaying ? "Paused" : "Playing", { duration: 1000 });
},
},
{
id: "fast-forward",
keys: ["l", "L"],
description: "Fast forward 1 second",
category: "Playback",
action: () => {
seek(Math.min(duration, currentTime + 1));
toast.info("Forward 1s", { duration: 1000 });
},
},
// Navigation
{
id: "frame-backward",
keys: ["ArrowLeft"],
description: "Frame step backward",
category: "Navigation",
action: () => {
const projectFps = activeProject?.fps || 30;
seek(Math.max(0, currentTime - 1 / projectFps));
},
},
{
id: "frame-forward",
keys: ["ArrowRight"],
description: "Frame step forward",
category: "Navigation",
action: () => {
const projectFps = activeProject?.fps || 30;
seek(Math.min(duration, currentTime + 1 / projectFps));
},
},
{
id: "jump-backward",
keys: ["Shift+ArrowLeft"],
description: "Jump back 5 seconds",
category: "Navigation",
action: () => {
seek(Math.max(0, currentTime - 5));
},
},
{
id: "jump-forward",
keys: ["Shift+ArrowRight"],
description: "Jump forward 5 seconds",
category: "Navigation",
action: () => {
seek(Math.min(duration, currentTime + 5));
},
},
{
id: "goto-start",
keys: ["Home"],
description: "Go to timeline start",
category: "Navigation",
action: () => {
seek(0);
toast.info("Start of timeline", { duration: 1000 });
},
},
{
id: "goto-end",
keys: ["End"],
description: "Go to timeline end",
category: "Navigation",
action: () => {
seek(duration);
toast.info("End of timeline", { duration: 1000 });
},
},
// Editing
{
id: "split-element",
keys: ["s", "S"],
description: "Split element at playhead",
category: "Editing",
requiresSelection: true,
action: () => {
if (selectedElements.length !== 1) {
toast.error("Select exactly one element to split");
return;
}
const { trackId, elementId } = selectedElements[0];
const track = tracks.find((t: any) => t.id === trackId);
const element = track?.elements.find((el: any) => el.id === elementId);
if (element) {
const effectiveStart = element.startTime;
const effectiveEnd =
element.startTime +
(element.duration - element.trimStart - element.trimEnd);
if (currentTime > effectiveStart && currentTime < effectiveEnd) {
splitElement(trackId, elementId, currentTime);
toast.success("Element split at playhead");
} else {
toast.error("Playhead must be within selected element");
}
}
},
},
{
id: "delete-elements",
keys: ["Delete", "Backspace"],
description: "Delete selected elements",
category: "Editing",
requiresSelection: true,
action: () => {
if (selectedElements.length === 0) {
toast.error("No elements selected");
return;
}
selectedElements.forEach(
({ trackId, elementId }: { trackId: string; elementId: string }) => {
removeElementFromTrack(trackId, elementId);
}
);
clearSelectedElements();
},
},
{
id: "toggle-snapping",
keys: ["n", "N"],
description: "Toggle snapping",
category: "Editing",
action: () => {
toggleSnapping();
toast.info(`Snapping ${snappingEnabled ? "disabled" : "enabled"}`, {
duration: 1000,
});
},
},
// Selection & Organization
{
id: "select-all",
keys: ["Cmd+a", "Ctrl+a"],
description: "Select all elements",
category: "Selection",
action: () => {
const allElements = tracks.flatMap((track: any) =>
track.elements.map((element: any) => ({
trackId: track.id,
elementId: element.id,
}))
);
setSelectedElements(allElements);
toast.info(`Selected ${allElements.length} elements`, {
duration: 1000,
});
},
},
{
id: "duplicate-element",
keys: ["Cmd+d", "Ctrl+d"],
description: "Duplicate selected element",
category: "Selection",
requiresSelection: true,
action: () => {
if (selectedElements.length !== 1) {
toast.error("Select exactly one element to duplicate");
return;
}
const { trackId, elementId } = selectedElements[0];
const track = tracks.find((t: any) => t.id === trackId);
const element = track?.elements.find((el: any) => el.id === elementId);
if (element) {
const newStartTime =
element.startTime +
(element.duration - element.trimStart - element.trimEnd) +
0.1;
const { id, ...elementWithoutId } = element;
addElementToTrack(trackId, {
...elementWithoutId,
startTime: newStartTime,
});
toast.success("Element duplicated");
}
},
},
// History
{
id: "undo",
keys: ["Cmd+z", "Ctrl+z"],
description: "Undo",
category: "History",
action: () => {
undo();
},
},
{
id: "redo",
keys: ["Cmd+Shift+z", "Ctrl+Shift+z", "Cmd+y", "Ctrl+y"],
description: "Redo",
category: "History",
action: () => {
redo();
},
},
];
// Parse keyboard event to match against shortcuts
const parseKeyboardEvent = useCallback((e: KeyboardEvent): string => {
const parts: string[] = [];
if (e.metaKey || e.ctrlKey) parts.push(e.metaKey ? "Cmd" : "Ctrl");
if (e.shiftKey) parts.push("Shift");
if (e.altKey) parts.push("Alt");
parts.push(e.key);
return parts.join("+");
}, []);
// Handle keyboard events
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (!enabled || isInputFocused()) return;
const keyCombo = parseKeyboardEvent(e);
const shortcut = shortcuts.find((s) =>
s.keys.some((key) => key === keyCombo || key === e.key)
);
if (shortcut) {
// Check if shortcut requires selection
if (shortcut.requiresSelection && selectedElements.length === 0) {
return;
}
e.preventDefault();
shortcut.action();
}
},
[enabled, shortcuts, selectedElements, parseKeyboardEvent, isInputFocused]
);
// Set up event listener
useEffect(() => {
if (!enabled) return;
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [enabled, handleKeyDown]);
// Return shortcuts for help component
return {
shortcuts: shortcuts.filter((s) => s.enabled !== false),
enabled,
};
};

View File

@ -106,68 +106,4 @@ export const usePlaybackControls = () => {
separateAudio(trackId, elementId);
}, [selectedElements, tracks, separateAudio]);
const handleKeyPress = useCallback(
(e: KeyboardEvent) => {
if (
e.target instanceof HTMLInputElement ||
e.target instanceof HTMLTextAreaElement
) {
return;
}
switch (e.key) {
case " ":
e.preventDefault();
if (isPlaying) {
pause();
} else {
play();
}
break;
case "s":
if (e.ctrlKey || e.metaKey) {
e.preventDefault();
handleSplitSelectedElement();
}
break;
case "q":
if (e.ctrlKey || e.metaKey) {
e.preventDefault();
handleSplitAndKeepLeftCallback();
}
break;
case "w":
if (e.ctrlKey || e.metaKey) {
e.preventDefault();
handleSplitAndKeepRightCallback();
}
break;
case "d":
if (e.ctrlKey || e.metaKey) {
e.preventDefault();
handleSeparateAudioCallback();
}
break;
}
},
[
isPlaying,
play,
pause,
handleSplitSelectedElement,
handleSplitAndKeepLeftCallback,
handleSplitAndKeepRightCallback,
handleSeparateAudioCallback,
]
);
useEffect(() => {
document.addEventListener("keydown", handleKeyPress);
return () => document.removeEventListener("keydown", handleKeyPress);
}, [handleKeyPress]);
};

View File

@ -1,19 +0,0 @@
/// <reference types="react" />
/// <reference types="react-dom" />
// Global React types
declare global {
namespace JSX {
interface IntrinsicElements {
[elemName: string]: any;
}
}
}
// React module declarations
declare module 'react' {
export * from '@types/react';
}
// Ensure this file is treated as a module
export {};

View File

@ -1,100 +0,0 @@
// Type declarations for external modules without @types packages
declare module 'lucide-react' {
import { FC, SVGProps } from 'react';
export interface IconProps extends SVGProps<SVGSVGElement> {
size?: string | number;
strokeWidth?: string | number;
}
export const Scissors: FC<IconProps>;
export const ArrowLeftToLine: FC<IconProps>;
export const ArrowRightToLine: FC<IconProps>;
export const Trash2: FC<IconProps>;
export const Snowflake: FC<IconProps>;
export const Copy: FC<IconProps>;
export const SplitSquareHorizontal: FC<IconProps>;
export const Pause: FC<IconProps>;
export const Play: FC<IconProps>;
export const Video: FC<IconProps>;
export const Music: FC<IconProps>;
export const TypeIcon: FC<IconProps>;
export const Magnet: FC<IconProps>;
export const Lock: FC<IconProps>;
export const ChevronLeft: FC<IconProps>;
export const ChevronRight: FC<IconProps>;
export const ChevronDown: FC<IconProps>;
export const ChevronUp: FC<IconProps>;
export const Download: FC<IconProps>;
export const Keyboard: FC<IconProps>;
export const SkipBack: FC<IconProps>;
export const SkipForward: FC<IconProps>;
export const Loader2: FC<IconProps>;
export const ExternalLink: FC<IconProps>;
export const ArrowRight: FC<IconProps>;
export const ArrowLeft: FC<IconProps>;
export const Expand: FC<IconProps>;
export const Plus: FC<IconProps>;
export const Upload: FC<IconProps>;
export const Image: FC<IconProps>;
export const MoreVertical: FC<IconProps>;
export const MoreHorizontal: FC<IconProps>;
export const Eye: FC<IconProps>;
export const EyeOff: FC<IconProps>;
export const Check: FC<IconProps>;
export const Circle: FC<IconProps>;
export const Search: FC<IconProps>;
export const X: FC<IconProps>;
export const PanelLeft: FC<IconProps>;
export const ChevronsUpDown: FC<IconProps>;
export const CheckIcon: FC<IconProps>;
export const Minus: FC<IconProps>;
export const RefreshCw: FC<IconProps>;
export const PipetteIcon: FC<IconProps>;
export const Type: FC<IconProps>;
export const Calendar: FC<IconProps>;
export const CaptionsIcon: FC<IconProps>;
export const ArrowLeftRightIcon: FC<IconProps>;
export const SparklesIcon: FC<IconProps>;
export const StickerIcon: FC<IconProps>;
export const MusicIcon: FC<IconProps>;
export const VideoIcon: FC<IconProps>;
export const BlendIcon: FC<IconProps>;
export const SlidersHorizontalIcon: FC<IconProps>;
export type LucideIcon = FC<IconProps>;
// Add other icons as needed
}
declare module 'sonner' {
export interface ToastOptions {
duration?: number;
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center';
style?: React.CSSProperties;
className?: string;
description?: string;
action?: {
label: string;
onClick: () => void;
};
cancel?: {
label: string;
onClick?: () => void;
};
id?: string | number;
onDismiss?: (toast: any) => void;
onAutoClose?: (toast: any) => void;
}
export interface Toast {
success: (message: string, options?: ToastOptions) => void;
error: (message: string, options?: ToastOptions) => void;
info: (message: string, options?: ToastOptions) => void;
warning: (message: string, options?: ToastOptions) => void;
loading: (message: string, options?: ToastOptions) => void;
custom: (jsx: React.ReactNode, options?: ToastOptions) => void;
}
export const toast: Toast;
export const Toaster: React.FC<any>;
}

View File

@ -38,7 +38,7 @@
"embla-carousel-react": "^8.5.1",
"framer-motion": "^11.13.1",
"input-otp": "^1.4.1",
"lucide-react": "^0.525.0",
"lucide-react": "^0.468.0",
"motion": "^12.18.1",
"next": "^15.4.1",
"next-themes": "^0.4.4",
@ -734,7 +734,7 @@
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"lucide-react": ["lucide-react@0.525.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ=="],
"lucide-react": ["lucide-react@0.468.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA=="],
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],