diff options
| -rw-r--r-- | apps/web/src/components/editor/media-panel/index.tsx | 5 | ||||
| -rw-r--r-- | apps/web/src/components/editor/media-panel/views/audio.tsx | 18 | ||||
| -rw-r--r-- | apps/web/src/components/editor/media-panel/views/media.tsx | 8 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline.tsx | 3 | ||||
| -rw-r--r-- | apps/web/src/components/landing/hero.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/hooks/use-editor-actions.ts | 1 | ||||
| -rw-r--r-- | apps/web/src/hooks/use-timeline-element-resize.ts | 56 | ||||
| -rw-r--r-- | apps/web/src/stores/keybindings-store.ts | 11 | ||||
| -rw-r--r-- | apps/web/src/stores/timeline-store.ts | 37 |
9 files changed, 103 insertions, 38 deletions
diff --git a/apps/web/src/components/editor/media-panel/index.tsx b/apps/web/src/components/editor/media-panel/index.tsx index 9f7322c7..90ac84f0 100644 --- a/apps/web/src/components/editor/media-panel/index.tsx +++ b/apps/web/src/components/editor/media-panel/index.tsx @@ -4,15 +4,14 @@ import { TabBar } from "./tabbar"; import { MediaView } from "./views/media";
import { useMediaPanelStore, Tab } from "./store";
import { TextView } from "./views/text";
+import { AudioView } from "./views/audio";
export function MediaPanel() {
const { activeTab } = useMediaPanelStore();
const viewMap: Record<Tab, React.ReactNode> = {
media: <MediaView />,
- audio: (
- <div className="p-4 text-muted-foreground">Audio view coming soon...</div>
- ),
+ audio: <AudioView />,
text: <TextView />,
stickers: (
<div className="p-4 text-muted-foreground">
diff --git a/apps/web/src/components/editor/media-panel/views/audio.tsx b/apps/web/src/components/editor/media-panel/views/audio.tsx new file mode 100644 index 00000000..7ddf465c --- /dev/null +++ b/apps/web/src/components/editor/media-panel/views/audio.tsx @@ -0,0 +1,18 @@ +"use client";
+
+import { Input } from "@/components/ui/input";
+import { useState } from "react";
+
+export function AudioView() {
+ const [search, setSearch] = useState("");
+ return (
+ <div className="h-full flex flex-col gap-2 p-4">
+ <Input
+ placeholder="Search songs and artists"
+ value={search}
+ onChange={(e) => setSearch(e.target.value)}
+ />
+ <div className="flex flex-col gap-2"></div>
+ </div>
+ );
+}
diff --git a/apps/web/src/components/editor/media-panel/views/media.tsx b/apps/web/src/components/editor/media-panel/views/media.tsx index 631fe39a..0189720f 100644 --- a/apps/web/src/components/editor/media-panel/views/media.tsx +++ b/apps/web/src/components/editor/media-panel/views/media.tsx @@ -208,10 +208,10 @@ export function MediaView() { {/* Search and filter controls */}
<div className="flex gap-2">
<Select value={mediaFilter} onValueChange={setMediaFilter}>
- <SelectTrigger className="w-[80px] h-full text-xs">
+ <SelectTrigger className="w-[80px] h-9 text-xs">
<SelectValue />
</SelectTrigger>
- <SelectContent className="">
+ <SelectContent>
<SelectItem value="all">All</SelectItem>
<SelectItem value="video">Video</SelectItem>
<SelectItem value="audio">Audio</SelectItem>
@@ -221,7 +221,7 @@ export function MediaView() { <Input
type="text"
placeholder="Search media..."
- className="min-w-[60px] flex-1 h-full text-xs"
+ className="min-w-[60px] flex-1 h-9 text-xs"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
@@ -230,7 +230,7 @@ export function MediaView() { size="lg"
onClick={handleFileSelect}
disabled={isProcessing}
- className="flex-none bg-transparent min-w-[30px] whitespace-nowrap overflow-hidden px-2 justify-center items-center"
+ className="flex-none bg-transparent min-w-[30px] whitespace-nowrap overflow-hidden px-2 justify-center items-center h-9"
>
{isProcessing ? (
<Loader2 className="h-4 w-4 animate-spin" />
diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index 9e68fd4d..6e8c453c 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -433,7 +433,6 @@ export function Timeline() { // Action handlers for toolbar const handleSplitSelected = () => { if (selectedElements.length === 0) { - toast.error("No elements selected"); return; } let splitCount = 0; @@ -459,7 +458,6 @@ export function Timeline() { const handleDuplicateSelected = () => { if (selectedElements.length === 0) { - toast.error("No elements selected"); return; } const canDuplicate = selectedElements.length === 1; @@ -553,7 +551,6 @@ export function Timeline() { const handleDeleteSelected = () => { if (selectedElements.length === 0) { - toast.error("No elements selected"); return; } selectedElements.forEach(({ trackId, elementId }) => { diff --git a/apps/web/src/components/landing/hero.tsx b/apps/web/src/components/landing/hero.tsx index b905bbde..dba958ac 100644 --- a/apps/web/src/components/landing/hero.tsx +++ b/apps/web/src/components/landing/hero.tsx @@ -33,7 +33,7 @@ export function Hero() { className="mb-8 flex justify-center" > <SponsorButton - href="https://vercel.com/?utm_source=opencut" + href="https://vercel.com/home?utm_source=opencut" logo={VercelIcon} companyName="Vercel" /> diff --git a/apps/web/src/hooks/use-editor-actions.ts b/apps/web/src/hooks/use-editor-actions.ts index e0d9b9eb..4e8f6aef 100644 --- a/apps/web/src/hooks/use-editor-actions.ts +++ b/apps/web/src/hooks/use-editor-actions.ts @@ -102,7 +102,6 @@ export function useEditorActions() { useActionHandler("delete-selected", () => { if (selectedElements.length === 0) { - toast.error("No elements selected"); return; } selectedElements.forEach( diff --git a/apps/web/src/hooks/use-timeline-element-resize.ts b/apps/web/src/hooks/use-timeline-element-resize.ts index f16ca8e2..3afa637b 100644 --- a/apps/web/src/hooks/use-timeline-element-resize.ts +++ b/apps/web/src/hooks/use-timeline-element-resize.ts @@ -29,7 +29,12 @@ export function useTimelineElementResize({ }: UseTimelineElementResizeProps) { const [resizing, setResizing] = useState<ResizeState | null>(null); const { mediaItems } = useMediaStore(); - const { updateElementStartTime } = useTimelineStore(); + const { + updateElementStartTime, + updateElementTrim, + updateElementDuration, + pushHistory, + } = useTimelineStore(); // Set up document-level mouse listeners during resize (like proper drag behavior) useEffect(() => { @@ -61,6 +66,9 @@ export function useTimelineElementResize({ e.stopPropagation(); e.preventDefault(); + // Push history once at the start of the resize operation + pushHistory(); + setResizing({ elementId, side, @@ -112,13 +120,14 @@ export function useTimelineElementResize({ const trimDelta = newTrimStart - resizing.initialTrimStart; const newStartTime = element.startTime + trimDelta; - onUpdateTrim( + updateElementTrim( track.id, element.id, newTrimStart, - resizing.initialTrimEnd + resizing.initialTrimEnd, + false ); - updateElementStartTime(track.id, element.id, newStartTime); + updateElementStartTime(track.id, element.id, newStartTime, false); } else { // Trying to extend beyond trimStart = 0 if (canExtendElementDuration()) { @@ -128,22 +137,29 @@ export function useTimelineElementResize({ const newDuration = element.duration + extensionAmount; // Keep trimStart at 0 and extend the element - onUpdateTrim(track.id, element.id, 0, resizing.initialTrimEnd); - onUpdateDuration(track.id, element.id, newDuration); - updateElementStartTime(track.id, element.id, newStartTime); + updateElementTrim( + track.id, + element.id, + 0, + resizing.initialTrimEnd, + false + ); + updateElementDuration(track.id, element.id, newDuration, false); + updateElementStartTime(track.id, element.id, newStartTime, false); } else { // Video/Audio: can't extend beyond original content - limit to trimStart = 0 const newTrimStart = 0; const trimDelta = newTrimStart - resizing.initialTrimStart; const newStartTime = element.startTime + trimDelta; - onUpdateTrim( + updateElementTrim( track.id, element.id, newTrimStart, - resizing.initialTrimEnd + resizing.initialTrimEnd, + false ); - updateElementStartTime(track.id, element.id, newStartTime); + updateElementStartTime(track.id, element.id, newStartTime, false); } } } else { @@ -159,27 +175,35 @@ export function useTimelineElementResize({ const newTrimEnd = 0; // Reset trimEnd to 0 since we're extending // Update duration first, then trim - onUpdateDuration(track.id, element.id, newDuration); - onUpdateTrim( + updateElementDuration(track.id, element.id, newDuration, false); + updateElementTrim( track.id, element.id, resizing.initialTrimStart, - newTrimEnd + newTrimEnd, + false ); } else { // Can't extend - just set trimEnd to 0 (maximum possible extension) - onUpdateTrim(track.id, element.id, resizing.initialTrimStart, 0); + updateElementTrim( + track.id, + element.id, + resizing.initialTrimStart, + 0, + false + ); } } else { // Normal trimming within original duration const maxTrimEnd = element.duration - resizing.initialTrimStart - 0.1; // Leave at least 0.1s visible const newTrimEnd = Math.max(0, Math.min(maxTrimEnd, calculated)); - onUpdateTrim( + updateElementTrim( track.id, element.id, resizing.initialTrimStart, - newTrimEnd + newTrimEnd, + false ); } } diff --git a/apps/web/src/stores/keybindings-store.ts b/apps/web/src/stores/keybindings-store.ts index 60c71afd..3e525b86 100644 --- a/apps/web/src/stores/keybindings-store.ts +++ b/apps/web/src/stores/keybindings-store.ts @@ -195,6 +195,7 @@ function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null { function getPressedKey(ev: KeyboardEvent): string | null { // Sometimes the property code is not available on the KeyboardEvent object const key = (ev.key ?? "").toLowerCase(); + const code = ev.code ?? ""; // Check arrow keys if (key.startsWith("arrow")) { @@ -213,7 +214,15 @@ function getPressedKey(ev: KeyboardEvent): string | null { const isLetter = key.length === 1 && key >= "a" && key <= "z"; if (isLetter) return key; - // Check if number keys + // Check number keys using physical position for AZERTY support + if (code.startsWith("Digit")) { + const digit = code.slice(5); + if (digit.length === 1 && digit >= "0" && digit <= "9") { + return digit; + } + } + + // Fallback for other layouts const isDigit = key.length === 1 && key >= "0" && key <= "9"; if (isDigit) return key; diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts index 1fcfc60e..01ad1635 100644 --- a/apps/web/src/stores/timeline-store.ts +++ b/apps/web/src/stores/timeline-store.ts @@ -106,17 +106,20 @@ interface TimelineStore { trackId: string, elementId: string, trimStart: number, - trimEnd: number + trimEnd: number, + pushHistory?: boolean ) => void; updateElementDuration: ( trackId: string, elementId: string, - duration: number + duration: number, + pushHistory?: boolean ) => void; updateElementStartTime: ( trackId: string, elementId: string, - startTime: number + startTime: number, + pushHistory?: boolean ) => void; toggleTrackMute: (trackId: string) => void; @@ -687,8 +690,14 @@ export const useTimelineStore = create<TimelineStore>((set, get) => { updateTracksAndSave(newTracks); }, - updateElementTrim: (trackId, elementId, trimStart, trimEnd) => { - get().pushHistory(); + updateElementTrim: ( + trackId, + elementId, + trimStart, + trimEnd, + pushHistory = true + ) => { + if (pushHistory) get().pushHistory(); updateTracksAndSave( get()._tracks.map((track) => track.id === trackId @@ -705,8 +714,13 @@ export const useTimelineStore = create<TimelineStore>((set, get) => { ); }, - updateElementDuration: (trackId, elementId, duration) => { - get().pushHistory(); + updateElementDuration: ( + trackId, + elementId, + duration, + pushHistory = true + ) => { + if (pushHistory) get().pushHistory(); updateTracksAndSave( get()._tracks.map((track) => track.id === trackId @@ -721,8 +735,13 @@ export const useTimelineStore = create<TimelineStore>((set, get) => { ); }, - updateElementStartTime: (trackId, elementId, startTime) => { - get().pushHistory(); + updateElementStartTime: ( + trackId, + elementId, + startTime, + pushHistory = true + ) => { + if (pushHistory) get().pushHistory(); updateTracksAndSave( get()._tracks.map((track) => track.id === trackId |
