diff options
Diffstat (limited to 'apps/web/src/components/editor')
| -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/preview-panel.tsx | 514 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline/index.tsx (renamed from apps/web/src/components/editor/timeline.tsx) | 15 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline/timeline-element.tsx (renamed from apps/web/src/components/editor/timeline-element.tsx) | 8 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline/timeline-playhead.tsx (renamed from apps/web/src/components/editor/timeline-playhead.tsx) | 0 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline/timeline-track.tsx (renamed from apps/web/src/components/editor/timeline-track.tsx) | 0 |
8 files changed, 290 insertions, 278 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/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index af603e97..2cba1374 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -44,40 +44,30 @@ export function PreviewPanel() { const [isExpanded, setIsExpanded] = useState(false); const { activeProject } = useProjectStore(); - // Calculate optimal preview size that fits in container while maintaining aspect ratio useEffect(() => { const updatePreviewSize = () => { if (!containerRef.current) return; - let container; let availableWidth, availableHeight; if (isExpanded) { - // Use full viewport when expanded, accounting for controls and margin - const controlsHeight = 80; // Approximate height of controls area - const marginSpace = 24; // m-3 = 12px on each side = 24px total + const controlsHeight = 80; + const marginSpace = 24; availableWidth = window.innerWidth - marginSpace; availableHeight = window.innerHeight - controlsHeight - marginSpace; } else { - container = containerRef.current.getBoundingClientRect(); + const container = containerRef.current.getBoundingClientRect(); const computedStyle = getComputedStyle(containerRef.current); - - // Get padding values const paddingTop = parseFloat(computedStyle.paddingTop); const paddingBottom = parseFloat(computedStyle.paddingBottom); const paddingLeft = parseFloat(computedStyle.paddingLeft); const paddingRight = parseFloat(computedStyle.paddingRight); - - // Get gap value (gap-4 = 1rem = 16px) const gap = parseFloat(computedStyle.gap) || 16; - - // Get toolbar height if it exists const toolbar = containerRef.current.querySelector("[data-toolbar]"); const toolbarHeight = toolbar ? toolbar.getBoundingClientRect().height : 0; - // Calculate available space after accounting for padding, gap, and toolbar availableWidth = container.width - paddingLeft - paddingRight; availableHeight = container.height - @@ -89,16 +79,13 @@ export function PreviewPanel() { const targetRatio = canvasSize.width / canvasSize.height; const containerRatio = availableWidth / availableHeight; - let width, height; if (containerRatio > targetRatio) { - // Container is wider - constrain by height - height = availableHeight * (isExpanded ? 0.95 : 1); // Use more space when expanded + height = availableHeight * (isExpanded ? 0.95 : 1); width = height * targetRatio; } else { - // Container is taller - constrain by width - width = availableWidth * (isExpanded ? 0.95 : 1); // Use more space when expanded + width = availableWidth * (isExpanded ? 0.95 : 1); height = width / targetRatio; } @@ -106,13 +93,10 @@ export function PreviewPanel() { }; updatePreviewSize(); - const resizeObserver = new ResizeObserver(updatePreviewSize); if (containerRef.current) { resizeObserver.observe(containerRef.current); } - - // Listen for window resize when expanded if (isExpanded) { window.addEventListener("resize", updatePreviewSize); } @@ -125,7 +109,6 @@ export function PreviewPanel() { }; }, [canvasSize.width, canvasSize.height, isExpanded]); - // Handle escape key to exit expanded mode useEffect(() => { const handleEscapeKey = (event: KeyboardEvent) => { if (event.key === "Escape" && isExpanded) { @@ -135,7 +118,6 @@ export function PreviewPanel() { if (isExpanded) { document.addEventListener("keydown", handleEscapeKey); - // Prevent body scroll when expanded document.body.style.overflow = "hidden"; } else { document.body.style.overflow = ""; @@ -151,73 +133,7 @@ export function PreviewPanel() { setIsExpanded((prev) => !prev); }, []); - // Check if there are any elements in the timeline at all const hasAnyElements = tracks.some((track) => track.elements.length > 0); - - // Handle keyboard shortcuts for timeline navigation - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - // Only handle shortcuts when preview panel is focused or no input is focused - const activeElement = document.activeElement; - const isInputFocused = - activeElement?.tagName === "INPUT" || - activeElement?.tagName === "TEXTAREA" || - activeElement?.contentEditable === "true"; - - if (isInputFocused) return; - - switch (event.key) { - case " ": - event.preventDefault(); - if (hasAnyElements) { - toggle(); - } - break; - case "ArrowLeft": - event.preventDefault(); - if (hasAnyElements) { - const frameTime = 1 / (activeProject?.fps || 30); - const totalDuration = getTotalDuration(); - const newTime = Math.max( - 0, - currentTime - (event.shiftKey ? frameTime : 1), - ); - setCurrentTime(newTime); - } - break; - case "ArrowRight": - event.preventDefault(); - if (hasAnyElements) { - const frameTime = 1 / (activeProject?.fps || 30); - const totalDuration = getTotalDuration(); - const newTime = Math.min( - totalDuration, - currentTime + (event.shiftKey ? frameTime : 1), - ); - setCurrentTime(newTime); - } - break; - case "Home": - event.preventDefault(); - if (hasAnyElements) { - setCurrentTime(0); - } - break; - case "End": - event.preventDefault(); - if (hasAnyElements) { - const totalDuration = getTotalDuration(); - setCurrentTime(totalDuration); - } - break; - } - }; - - document.addEventListener("keydown", handleKeyDown); - return () => document.removeEventListener("keydown", handleKeyDown); - }, [hasAnyElements, toggle, currentTime, setCurrentTime, activeProject?.fps]); - - // Get active elements at current time const getActiveElements = (): ActiveElement[] => { const activeElements: ActiveElement[] = []; @@ -230,16 +146,13 @@ export function PreviewPanel() { if (currentTime >= elementStart && currentTime < elementEnd) { let mediaItem = null; - - // Only get media item for media elements if (element.type === "media") { mediaItem = element.mediaId === "test" - ? null // Test elements don't have a real media item + ? null : mediaItems.find((item) => item.id === element.mediaId) || null; } - activeElements.push({ element, track, mediaItem }); } }); @@ -257,7 +170,7 @@ export function PreviewPanel() { element.type === "media" && mediaItem && (mediaItem.type === "video" || mediaItem.type === "image") && - element.mediaId !== "test", // Exclude test elements + element.mediaId !== "test" // Exclude test elements ); }; @@ -476,10 +389,9 @@ export function PreviewPanel() { </div> ) : ( activeElements.map((elementData, index) => - renderElement(elementData, index), + renderElement(elementData, index) ) )} - {/* Show message when blur is selected but no media available */} {activeProject?.backgroundType === "blur" && blurBackgroundElements.length === 0 && activeElements.length > 0 && ( @@ -504,62 +416,29 @@ export function PreviewPanel() { </div> </div> - {/* Expanded/Fullscreen Mode */} {isExpanded && ( - <div className="fixed inset-0 z-[9999] flex flex-col"> - <div className="flex-1 flex items-center justify-center bg-background"> - <div - className="relative overflow-hidden border border-border m-3" - style={{ - width: previewDimensions.width, - height: previewDimensions.height, - backgroundColor: - activeProject?.backgroundType === "blur" - ? "#1a1a1a" - : activeProject?.backgroundColor || "#1a1a1a", - }} - > - {renderBlurBackground()} - {activeElements.length === 0 ? ( - <div className="absolute inset-0 flex items-center justify-center text-white/60"> - No elements at current time - </div> - ) : ( - activeElements.map((elementData, index) => - renderElement(elementData, index), - ) - )} - {/* Show message when blur is selected but no media available */} - {activeProject?.backgroundType === "blur" && - blurBackgroundElements.length === 0 && - activeElements.length > 0 && ( - <div className="absolute bottom-2 left-2 right-2 bg-black/70 text-white text-xs p-2 rounded"> - Add a video or image to use blur background - </div> - )} - </div> - </div> - <div className="p-4 bg-black"> - <PreviewToolbar - hasAnyElements={hasAnyElements} - onToggleExpanded={toggleExpanded} - isExpanded={isExpanded} - currentTime={currentTime} - setCurrentTime={setCurrentTime} - toggle={toggle} - getTotalDuration={getTotalDuration} - /> - </div> - </div> + <FullscreenPreview + previewDimensions={previewDimensions} + activeProject={activeProject} + renderBlurBackground={renderBlurBackground} + activeElements={activeElements} + renderElement={renderElement} + blurBackgroundElements={blurBackgroundElements} + hasAnyElements={hasAnyElements} + toggleExpanded={toggleExpanded} + currentTime={currentTime} + setCurrentTime={setCurrentTime} + toggle={toggle} + getTotalDuration={getTotalDuration} + /> )} </> ); } -function PreviewToolbar({ +function FullscreenToolbar({ hasAnyElements, onToggleExpanded, - isExpanded, currentTime, setCurrentTime, toggle, @@ -567,38 +446,20 @@ function PreviewToolbar({ }: { hasAnyElements: boolean; onToggleExpanded: () => void; - isExpanded: boolean; currentTime: number; setCurrentTime: (time: number) => void; toggle: () => void; getTotalDuration: () => number; }) { const { isPlaying } = usePlaybackStore(); - const { setCanvasSize, setCanvasSizeToOriginal } = useEditorStore(); const { activeProject } = useProjectStore(); - const { - currentPreset, - isOriginal, - getOriginalAspectRatio, - getDisplayName, - canvasPresets, - } = useAspectRatio(); - - const handlePresetSelect = (preset: { width: number; height: number }) => { - setCanvasSize({ width: preset.width, height: preset.height }); - }; - - const handleOriginalSelect = () => { - const aspectRatio = getOriginalAspectRatio(); - setCanvasSizeToOriginal(aspectRatio); - }; + const [isDragging, setIsDragging] = useState(false); const totalDuration = getTotalDuration(); const progress = totalDuration > 0 ? (currentTime / totalDuration) * 100 : 0; const handleTimelineClick = (e: React.MouseEvent<HTMLDivElement>) => { 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)); @@ -608,16 +469,12 @@ function PreviewToolbar({ const handleTimelineDrag = (e: React.MouseEvent<HTMLDivElement>) => { if (!hasAnyElements) return; - e.preventDefault(); e.stopPropagation(); - const rect = e.currentTarget.getBoundingClientRect(); - let isDragging = true; + setIsDragging(true); const handleMouseMove = (moveEvent: MouseEvent) => { - if (!isDragging) return; - moveEvent.preventDefault(); const dragX = moveEvent.clientX - rect.left; const percentage = Math.max(0, Math.min(1, dragX / rect.width)); @@ -625,48 +482,35 @@ function PreviewToolbar({ setCurrentTime(Math.max(0, Math.min(newTime, totalDuration))); }; - const handleMouseUp = (moveEvent: MouseEvent) => { - isDragging = false; + const handleMouseUp = () => { + setIsDragging(false); document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); document.body.style.userSelect = ""; }; - // Prevent text selection during drag document.body.style.userSelect = "none"; - document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); - - // Also handle the initial click position handleMouseMove(e.nativeEvent); }; const skipBackward = () => { - const newTime = Math.max(0, currentTime - 1); // Skip 1 second back + const newTime = Math.max(0, currentTime - 1); setCurrentTime(newTime); }; const skipForward = () => { - const newTime = Math.min(totalDuration, currentTime + 1); // Skip 1 second forward + const newTime = Math.min(totalDuration, currentTime + 1); setCurrentTime(newTime); }; return ( <div data-toolbar - className={cn( - "flex items-center gap-2 p-1 pt-2 w-full", - isExpanded && "text-white", - )} + className="flex items-center gap-2 p-1 pt-2 w-full text-white" > - {/* Time Display */} - <div - className={cn( - "flex items-center gap-1 text-[0.70rem] tabular-nums", - isExpanded ? "text-white/90" : "text-muted-foreground", - )} - > + <div className="flex items-center gap-1 text-[0.70rem] tabular-nums text-white/90"> <span className="text-primary"> {formatTimeCode(currentTime, "HH:MM:SS:FF", activeProject?.fps || 30)} </span> @@ -675,22 +519,18 @@ function PreviewToolbar({ {formatTimeCode( totalDuration, "HH:MM:SS:FF", - activeProject?.fps || 30, + activeProject?.fps || 30 )} </span> </div> - {/* Transport Controls */} <div className="flex items-center gap-1"> <Button variant="text" size="icon" onClick={skipBackward} disabled={!hasAnyElements} - className={cn( - "h-auto p-0", - isExpanded && "text-white hover:text-white/80", - )} + className="h-auto p-0 text-white hover:text-white/80" title="Skip backward 1s" > <SkipBack className="h-3 w-3" /> @@ -700,10 +540,7 @@ function PreviewToolbar({ size="icon" onClick={toggle} disabled={!hasAnyElements} - className={cn( - "h-auto p-0", - isExpanded && "text-white hover:text-white/80", - )} + className="h-auto p-0 text-white hover:text-white/80" > {isPlaying ? ( <Pause className="h-3 w-3" /> @@ -716,23 +553,18 @@ function PreviewToolbar({ size="icon" onClick={skipForward} disabled={!hasAnyElements} - className={cn( - "h-auto p-0", - isExpanded && "text-white hover:text-white/80", - )} + className="h-auto p-0 text-white hover:text-white/80" title="Skip forward 1s" > <SkipForward className="h-3 w-3" /> </Button> </div> - {/* Timeline Scrubber */} <div className="flex-1 flex items-center gap-2"> <div className={cn( - "relative h-1 rounded-full cursor-pointer flex-1", - isExpanded ? "bg-white/20" : "bg-muted", - !hasAnyElements && "opacity-50 cursor-not-allowed", + "relative h-1 rounded-full cursor-pointer flex-1 bg-white/20", + !hasAnyElements && "opacity-50 cursor-not-allowed" )} onClick={hasAnyElements ? handleTimelineClick : undefined} onMouseDown={hasAnyElements ? handleTimelineDrag : undefined} @@ -740,73 +572,239 @@ function PreviewToolbar({ > <div className={cn( - "absolute top-0 left-0 h-full rounded-full transition-all duration-100", - isExpanded ? "bg-white" : "bg-primary", + "absolute top-0 left-0 h-full rounded-full bg-white", + !isDragging && "duration-100" )} style={{ width: `${progress}%` }} /> <div - className={cn( - "absolute top-1/2 w-3 h-3 rounded-full -translate-y-1/2 -translate-x-1/2 shadow-sm", - isExpanded - ? "bg-white border border-black/20" - : "bg-primary border border-background", - )} + className="absolute top-1/2 w-3 h-3 rounded-full -translate-y-1/2 -translate-x-1/2 shadow-sm bg-white border border-black/20" style={{ left: `${progress}%` }} /> </div> </div> - {/* Aspect Ratio and Fullscreen Controls */} - <div className="flex items-center gap-3"> - {!isExpanded && ( - <> - <BackgroundSettings /> - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button - size="sm" - className="!bg-panel-accent text-foreground/85 text-[0.70rem] h-4 rounded-none border border-muted-foreground px-0.5 py-0 font-light" - disabled={!hasAnyElements} - > - {getDisplayName()} - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent align="end"> - <DropdownMenuItem - onClick={handleOriginalSelect} - className={cn("text-xs", isOriginal && "font-semibold")} - > - Original - </DropdownMenuItem> - <DropdownMenuSeparator /> - {canvasPresets.map((preset) => ( - <DropdownMenuItem - key={preset.name} - onClick={() => handlePresetSelect(preset)} - className={cn( - "text-xs", - currentPreset?.name === preset.name && "font-semibold", - )} - > - {preset.name} - </DropdownMenuItem> - ))} - </DropdownMenuContent> - </DropdownMenu> - </> + <Button + variant="text" + size="icon" + className="!size-4 text-white/80 hover:text-white" + onClick={onToggleExpanded} + title="Exit fullscreen (Esc)" + > + <Expand className="!size-4" /> + </Button> + </div> + ); +} + +function FullscreenPreview({ + previewDimensions, + activeProject, + renderBlurBackground, + activeElements, + renderElement, + blurBackgroundElements, + hasAnyElements, + toggleExpanded, + currentTime, + setCurrentTime, + toggle, + getTotalDuration, +}: { + previewDimensions: { width: number; height: number }; + activeProject: any; + renderBlurBackground: () => React.ReactNode; + activeElements: ActiveElement[]; + renderElement: (elementData: ActiveElement, index: number) => React.ReactNode; + blurBackgroundElements: ActiveElement[]; + hasAnyElements: boolean; + toggleExpanded: () => void; + currentTime: number; + setCurrentTime: (time: number) => void; + toggle: () => void; + getTotalDuration: () => number; +}) { + return ( + <div className="fixed inset-0 z-[9999] flex flex-col"> + <div className="flex-1 flex items-center justify-center bg-background"> + <div + className="relative overflow-hidden border border-border m-3" + style={{ + width: previewDimensions.width, + height: previewDimensions.height, + backgroundColor: + activeProject?.backgroundType === "blur" + ? "#1a1a1a" + : activeProject?.backgroundColor || "#1a1a1a", + }} + > + {renderBlurBackground()} + {activeElements.length === 0 ? ( + <div className="absolute inset-0 flex items-center justify-center text-white/60"> + No elements at current time + </div> + ) : ( + activeElements.map((elementData, index) => + renderElement(elementData, index) + ) + )} + {activeProject?.backgroundType === "blur" && + blurBackgroundElements.length === 0 && + activeElements.length > 0 && ( + <div className="absolute bottom-2 left-2 right-2 bg-black/70 text-white text-xs p-2 rounded"> + Add a video or image to use blur background + </div> + )} + </div> + </div> + <div className="p-4 bg-black"> + <FullscreenToolbar + hasAnyElements={hasAnyElements} + onToggleExpanded={toggleExpanded} + currentTime={currentTime} + setCurrentTime={setCurrentTime} + toggle={toggle} + getTotalDuration={getTotalDuration} + /> + </div> + </div> + ); +} + +function PreviewToolbar({ + hasAnyElements, + onToggleExpanded, + isExpanded, + currentTime, + setCurrentTime, + toggle, + getTotalDuration, +}: { + hasAnyElements: boolean; + onToggleExpanded: () => void; + isExpanded: boolean; + currentTime: number; + setCurrentTime: (time: number) => void; + toggle: () => void; + getTotalDuration: () => number; +}) { + const { isPlaying } = usePlaybackStore(); + const { setCanvasSize, setCanvasSizeToOriginal } = useEditorStore(); + const { activeProject } = useProjectStore(); + const { + currentPreset, + isOriginal, + getOriginalAspectRatio, + getDisplayName, + canvasPresets, + } = useAspectRatio(); + + const handlePresetSelect = (preset: { width: number; height: number }) => { + setCanvasSize({ width: preset.width, height: preset.height }); + }; + + const handleOriginalSelect = () => { + const aspectRatio = getOriginalAspectRatio(); + setCanvasSizeToOriginal(aspectRatio); + }; + + if (isExpanded) { + return ( + <FullscreenToolbar + {...{ + hasAnyElements, + onToggleExpanded, + currentTime, + setCurrentTime, + toggle, + getTotalDuration, + }} + /> + ); + } + + return ( + <div + data-toolbar + className="flex items-end justify-between gap-2 p-1 pt-2 w-full" + > + <div> + <p + className={cn( + "text-[0.75rem] text-muted-foreground flex items-center gap-1", + !hasAnyElements && "opacity-50" + )} + > + <span className="text-primary tabular-nums"> + {formatTimeCode( + currentTime, + "HH:MM:SS:FF", + activeProject?.fps || 30 + )} + </span> + <span className="opacity-50">/</span> + <span className="tabular-nums"> + {formatTimeCode( + getTotalDuration(), + "HH:MM:SS:FF", + activeProject?.fps || 30 + )} + </span> + </p> + </div> + <Button + variant="text" + size="icon" + onClick={toggle} + disabled={!hasAnyElements} + className="h-auto p-0" + > + {isPlaying ? ( + <Pause className="h-3 w-3" /> + ) : ( + <Play className="h-3 w-3" /> )} + </Button> + <div className="flex items-center gap-3"> + <BackgroundSettings /> + <DropdownMenu> + <DropdownMenuTrigger asChild> + <Button + size="sm" + className="!bg-panel-accent text-foreground/85 text-[0.70rem] h-4 rounded-none border border-muted-foreground px-0.5 py-0 font-light" + disabled={!hasAnyElements} + > + {getDisplayName()} + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + <DropdownMenuItem + onClick={handleOriginalSelect} + className={cn("text-xs", isOriginal && "font-semibold")} + > + Original + </DropdownMenuItem> + <DropdownMenuSeparator /> + {canvasPresets.map((preset) => ( + <DropdownMenuItem + key={preset.name} + onClick={() => handlePresetSelect(preset)} + className={cn( + "text-xs", + currentPreset?.name === preset.name && "font-semibold" + )} + > + {preset.name} + </DropdownMenuItem> + ))} + </DropdownMenuContent> + </DropdownMenu> <Button variant="text" size="icon" - className={cn( - "!size-4", - isExpanded - ? "text-white/80 hover:text-white" - : "text-muted-foreground", - )} + className="!size-4 text-muted-foreground" onClick={onToggleExpanded} - title={isExpanded ? "Exit fullscreen (Esc)" : "Enter fullscreen"} + title="Enter fullscreen" > <Expand className="!size-4" /> </Button> diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline/index.tsx index 9e68fd4d..f9a6cb8c 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline/index.tsx @@ -1,7 +1,7 @@ "use client"; -import { ScrollArea } from "../ui/scroll-area"; -import { Button } from "../ui/button"; +import { ScrollArea } from "../../ui/scroll-area"; +import { Button } from "../../ui/button"; import { Scissors, ArrowLeftToLine, @@ -24,13 +24,13 @@ import { TooltipContent, TooltipTrigger, TooltipProvider, -} from "../ui/tooltip"; +} from "../../ui/tooltip"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, -} from "../ui/context-menu"; +} from "../../ui/context-menu"; import { useTimelineStore } from "@/stores/timeline-store"; import { useMediaStore } from "@/stores/media-store"; import { usePlaybackStore } from "@/stores/playback-store"; @@ -44,9 +44,9 @@ import { TimelinePlayhead, useTimelinePlayheadRuler, } from "./timeline-playhead"; -import { SelectionBox } from "./selection-box"; +import { SelectionBox } from "../selection-box"; import { useSelectionBox } from "@/hooks/use-selection-box"; -import { SnapIndicator } from "./snap-indicator"; +import { SnapIndicator } from "../snap-indicator"; import { SnapPoint } from "@/hooks/use-timeline-snapping"; import type { DragData, TimelineTrack } from "@/types/timeline"; import { @@ -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/editor/timeline-element.tsx b/apps/web/src/components/editor/timeline/timeline-element.tsx index eb9f1c01..7cc72b10 100644 --- a/apps/web/src/components/editor/timeline-element.tsx +++ b/apps/web/src/components/editor/timeline/timeline-element.tsx @@ -1,7 +1,7 @@ "use client"; import { useState } from "react"; -import { Button } from "../ui/button"; +import { Button } from "../../ui/button"; import { MoreVertical, Scissors, @@ -17,7 +17,7 @@ import { import { useMediaStore } from "@/stores/media-store"; import { useTimelineStore } from "@/stores/timeline-store"; import { usePlaybackStore } from "@/stores/playback-store"; -import AudioWaveform from "./audio-waveform"; +import AudioWaveform from "../audio-waveform"; import { toast } from "sonner"; import { TimelineElementProps, TrackType } from "@/types/timeline"; import { useTimelineElementResize } from "@/hooks/use-timeline-element-resize"; @@ -35,14 +35,14 @@ import { DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, -} from "../ui/dropdown-menu"; +} from "../../ui/dropdown-menu"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, -} from "../ui/context-menu"; +} from "../../ui/context-menu"; export function TimelineElement({ element, diff --git a/apps/web/src/components/editor/timeline-playhead.tsx b/apps/web/src/components/editor/timeline/timeline-playhead.tsx index 7a357067..7a357067 100644 --- a/apps/web/src/components/editor/timeline-playhead.tsx +++ b/apps/web/src/components/editor/timeline/timeline-playhead.tsx diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline/timeline-track.tsx index 82ca0b98..82ca0b98 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline/timeline-track.tsx |
