diff options
Diffstat (limited to 'apps/web')
| -rw-r--r-- | apps/web/src/app/editor/[project_id]/page.tsx | 340 | ||||
| -rw-r--r-- | apps/web/src/components/editor-header.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/components/panel-preset-selector.tsx | 91 | ||||
| -rw-r--r-- | apps/web/src/stores/panel-store.ts | 212 |
4 files changed, 572 insertions, 73 deletions
diff --git a/apps/web/src/app/editor/[project_id]/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx index cae8e779..4d5a27fc 100644 --- a/apps/web/src/app/editor/[project_id]/page.tsx +++ b/apps/web/src/app/editor/[project_id]/page.tsx @@ -30,6 +30,8 @@ export default function Editor() { setTimeline, propertiesPanel, setPropertiesPanel, + activePreset, + resetCounter, } = usePanelStore(); const { @@ -153,72 +155,302 @@ export default function Editor() { <div className="h-screen w-screen flex flex-col bg-background overflow-hidden"> <EditorHeader /> <div className="flex-1 min-h-0 min-w-0"> - <ResizablePanelGroup - direction="vertical" - className="h-full w-full gap-[0.18rem]" - > - <ResizablePanel - defaultSize={mainContent} - minSize={30} - maxSize={85} - onResize={setMainContent} - className="min-h-0" + {activePreset === "media" ? ( + <ResizablePanelGroup + key={`media-${activePreset}-${resetCounter}`} + direction="horizontal" + className="h-full w-full gap-[0.18rem] px-3 pb-3" > - {/* Main content area */} - <ResizablePanelGroup - direction="horizontal" - className="h-full w-full gap-[0.19rem] px-3" + <ResizablePanel + defaultSize={toolsPanel} + minSize={15} + maxSize={40} + onResize={setToolsPanel} + className="min-w-0 rounded-sm" > - {/* Tools Panel */} - <ResizablePanel - defaultSize={toolsPanel} - minSize={15} - maxSize={40} - onResize={setToolsPanel} - className="min-w-0 rounded-sm" + <MediaPanel /> + </ResizablePanel> + + <ResizableHandle withHandle /> + + <ResizablePanel + defaultSize={100 - toolsPanel} + minSize={60} + className="min-w-0 min-h-0" + > + <ResizablePanelGroup + direction="vertical" + className="h-full w-full gap-[0.18rem]" > - <MediaPanel /> - </ResizablePanel> + <ResizablePanel + defaultSize={mainContent} + minSize={30} + maxSize={85} + onResize={setMainContent} + className="min-h-0" + > + <ResizablePanelGroup + direction="horizontal" + className="h-full w-full gap-[0.19rem]" + > + <ResizablePanel + defaultSize={previewPanel} + minSize={30} + onResize={setPreviewPanel} + className="min-w-0 min-h-0 flex-1" + > + <PreviewPanel /> + </ResizablePanel> + + <ResizableHandle withHandle /> - <ResizableHandle withHandle /> + <ResizablePanel + defaultSize={propertiesPanel} + minSize={15} + maxSize={40} + onResize={setPropertiesPanel} + className="min-w-0" + > + <PropertiesPanel /> + </ResizablePanel> + </ResizablePanelGroup> + </ResizablePanel> - {/* Preview Area */} - <ResizablePanel - defaultSize={previewPanel} - minSize={30} - onResize={setPreviewPanel} - className="min-w-0 min-h-0 flex-1" + <ResizableHandle withHandle /> + + <ResizablePanel + defaultSize={timeline} + minSize={15} + maxSize={70} + onResize={setTimeline} + className="min-h-0" + > + <Timeline /> + </ResizablePanel> + </ResizablePanelGroup> + </ResizablePanel> + </ResizablePanelGroup> + ) : activePreset === "inspector" ? ( + <ResizablePanelGroup + key={`inspector-${activePreset}-${resetCounter}`} + direction="horizontal" + className="h-full w-full gap-[0.18rem] px-3 pb-3" + > + <ResizablePanel + defaultSize={100 - propertiesPanel} + minSize={30} + onResize={(size) => setPropertiesPanel(100 - size)} + className="min-w-0 min-h-0" + > + <ResizablePanelGroup + direction="vertical" + className="h-full w-full gap-[0.18rem]" > - <PreviewPanel /> - </ResizablePanel> + <ResizablePanel + defaultSize={mainContent} + minSize={30} + maxSize={85} + onResize={setMainContent} + className="min-h-0" + > + <ResizablePanelGroup + direction="horizontal" + className="h-full w-full gap-[0.19rem]" + > + <ResizablePanel + defaultSize={toolsPanel} + minSize={15} + maxSize={40} + onResize={setToolsPanel} + className="min-w-0 rounded-sm" + > + <MediaPanel /> + </ResizablePanel> - <ResizableHandle withHandle /> + <ResizableHandle withHandle /> - <ResizablePanel - defaultSize={propertiesPanel} - minSize={15} - maxSize={40} - onResize={setPropertiesPanel} - className="min-w-0 rounded-sm" + <ResizablePanel + defaultSize={previewPanel} + minSize={30} + onResize={setPreviewPanel} + className="min-w-0 min-h-0 flex-1" + > + <PreviewPanel /> + </ResizablePanel> + </ResizablePanelGroup> + </ResizablePanel> + + <ResizableHandle withHandle /> + + <ResizablePanel + defaultSize={timeline} + minSize={15} + maxSize={70} + onResize={setTimeline} + className="min-h-0" + > + <Timeline /> + </ResizablePanel> + </ResizablePanelGroup> + </ResizablePanel> + + <ResizableHandle withHandle /> + + <ResizablePanel + defaultSize={propertiesPanel} + minSize={15} + maxSize={40} + onResize={setPropertiesPanel} + className="min-w-0 min-h-0" + > + <PropertiesPanel /> + </ResizablePanel> + </ResizablePanelGroup> + ) : activePreset === "vertical-preview" ? ( + <ResizablePanelGroup + key={`vertical-preview-${activePreset}-${resetCounter}`} + direction="horizontal" + className="h-full w-full gap-[0.18rem] px-3 pb-3" + > + <ResizablePanel + defaultSize={100 - previewPanel} + minSize={30} + onResize={(size) => setPreviewPanel(100 - size)} + className="min-w-0 min-h-0" + > + <ResizablePanelGroup + direction="vertical" + className="h-full w-full gap-[0.18rem]" > - <PropertiesPanel /> - </ResizablePanel> - </ResizablePanelGroup> - </ResizablePanel> + <ResizablePanel + defaultSize={mainContent} + minSize={30} + maxSize={85} + onResize={setMainContent} + className="min-h-0" + > + <ResizablePanelGroup + direction="horizontal" + className="h-full w-full gap-[0.19rem]" + > + <ResizablePanel + defaultSize={toolsPanel} + minSize={15} + maxSize={40} + onResize={setToolsPanel} + className="min-w-0 rounded-sm" + > + <MediaPanel /> + </ResizablePanel> - <ResizableHandle withHandle /> + <ResizableHandle withHandle /> - {/* Timeline */} - <ResizablePanel - defaultSize={timeline} - minSize={15} - maxSize={70} - onResize={setTimeline} - className="min-h-0 px-3 pb-3" + <ResizablePanel + defaultSize={propertiesPanel} + minSize={15} + maxSize={40} + onResize={setPropertiesPanel} + className="min-w-0" + > + <PropertiesPanel /> + </ResizablePanel> + </ResizablePanelGroup> + </ResizablePanel> + + <ResizableHandle withHandle /> + + <ResizablePanel + defaultSize={timeline} + minSize={15} + maxSize={70} + onResize={setTimeline} + className="min-h-0" + > + <Timeline /> + </ResizablePanel> + </ResizablePanelGroup> + </ResizablePanel> + + <ResizableHandle withHandle /> + + <ResizablePanel + defaultSize={previewPanel} + minSize={30} + onResize={setPreviewPanel} + className="min-w-0 min-h-0" + > + <PreviewPanel /> + </ResizablePanel> + </ResizablePanelGroup> + ) : ( + <ResizablePanelGroup + key={`default-${activePreset}-${resetCounter}`} + direction="vertical" + className="h-full w-full gap-[0.18rem]" > - <Timeline /> - </ResizablePanel> - </ResizablePanelGroup> + <ResizablePanel + defaultSize={mainContent} + minSize={30} + maxSize={85} + onResize={setMainContent} + className="min-h-0" + > + {/* Main content area */} + <ResizablePanelGroup + direction="horizontal" + className="h-full w-full gap-[0.19rem] px-3" + > + {/* Tools Panel */} + <ResizablePanel + defaultSize={toolsPanel} + minSize={15} + maxSize={40} + onResize={setToolsPanel} + className="min-w-0 rounded-sm" + > + <MediaPanel /> + </ResizablePanel> + + <ResizableHandle withHandle /> + + {/* Preview Area */} + <ResizablePanel + defaultSize={previewPanel} + minSize={30} + onResize={setPreviewPanel} + className="min-w-0 min-h-0 flex-1" + > + <PreviewPanel /> + </ResizablePanel> + + <ResizableHandle withHandle /> + + <ResizablePanel + defaultSize={propertiesPanel} + minSize={15} + maxSize={40} + onResize={setPropertiesPanel} + className="min-w-0 rounded-sm" + > + <PropertiesPanel /> + </ResizablePanel> + </ResizablePanelGroup> + </ResizablePanel> + + <ResizableHandle withHandle /> + + {/* Timeline */} + <ResizablePanel + defaultSize={timeline} + minSize={15} + maxSize={70} + onResize={setTimeline} + className="min-h-0 px-3 pb-3" + > + <Timeline /> + </ResizablePanel> + </ResizablePanelGroup> + )} </div> <Onboarding /> </div> diff --git a/apps/web/src/components/editor-header.tsx b/apps/web/src/components/editor-header.tsx index 0caa6c3e..6db2543b 100644 --- a/apps/web/src/components/editor-header.tsx +++ b/apps/web/src/components/editor-header.tsx @@ -30,6 +30,7 @@ import { FaDiscord } from "react-icons/fa6"; import { useTheme } from "next-themes"; import { usePlaybackStore } from "@/stores/playback-store"; import { TransitionUpIcon } from "./icons"; +import { PanelPresetSelector } from "./panel-preset-selector"; export function EditorHeader() { const { getTotalDuration } = useTimelineStore(); @@ -141,6 +142,7 @@ export function EditorHeader() { const rightContent = ( <nav className="flex items-center gap-2"> + <PanelPresetSelector /> <KeyboardShortcutsHelp /> <ExportButton /> <Button diff --git a/apps/web/src/components/panel-preset-selector.tsx b/apps/web/src/components/panel-preset-selector.tsx new file mode 100644 index 00000000..c5351f76 --- /dev/null +++ b/apps/web/src/components/panel-preset-selector.tsx @@ -0,0 +1,91 @@ +"use client";
+
+import { Button } from "./ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "./ui/dropdown-menu";
+import { ChevronDown, RotateCcw, LayoutPanelTop } from "lucide-react";
+import { usePanelStore, type PanelPreset } from "@/stores/panel-store";
+
+const PRESET_LABELS: Record<PanelPreset, string> = {
+ default: "Default",
+ media: "Media",
+ inspector: "Inspector",
+ "vertical-preview": "Vertical Preview",
+};
+
+const PRESET_DESCRIPTIONS: Record<PanelPreset, string> = {
+ default: "Media, preview, and inspector on top row, timeline on bottom",
+ media: "Full height media on left, preview and inspector on top row",
+ inspector: "Full height inspector on right, media and preview on top row",
+ "vertical-preview": "Full height preview on right for vertical videos",
+};
+
+export function PanelPresetSelector() {
+ const { activePreset, setActivePreset, resetPreset } = usePanelStore();
+
+ const handlePresetChange = (preset: PanelPreset) => {
+ setActivePreset(preset);
+ };
+
+ const handleResetPreset = (preset: PanelPreset, event: React.MouseEvent) => {
+ event.stopPropagation();
+ resetPreset(preset);
+ };
+
+ return (
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button
+ variant="secondary"
+ size="sm"
+ className="h-8 px-2 flex items-center gap-1 text-xs"
+ title="Panel Presets"
+ >
+ <LayoutPanelTop className="h-4 w-4" />
+ <ChevronDown className="h-3 w-3" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="start" className="w-64">
+ <div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">
+ Panel Presets
+ </div>
+ <DropdownMenuSeparator />
+ {(Object.keys(PRESET_LABELS) as PanelPreset[]).map((preset) => (
+ <DropdownMenuItem
+ key={preset}
+ onClick={() => handlePresetChange(preset)}
+ className="flex items-start justify-between gap-2 py-2 cursor-pointer"
+ >
+ <div className="flex-1 min-w-0">
+ <div className="flex items-center gap-2">
+ <span className="font-medium text-sm">
+ {PRESET_LABELS[preset]}
+ </span>
+ {activePreset === preset && (
+ <div className="h-2 w-2 rounded-full bg-primary" />
+ )}
+ </div>
+ <p className="text-xs text-muted-foreground mt-0.5 leading-tight">
+ {PRESET_DESCRIPTIONS[preset]}
+ </p>
+ </div>
+ <Button
+ variant="secondary"
+ size="icon"
+ className="h-6 w-6 shrink-0 opacity-60 hover:opacity-100"
+ onClick={(e) => handleResetPreset(preset, e)}
+ title={`Reset ${PRESET_LABELS[preset]} preset`}
+ >
+ <RotateCcw className="h-3 w-3" />
+ </Button>
+ </DropdownMenuItem>
+ ))}
+ </DropdownMenuContent>
+ </DropdownMenu>
+ );
+}
diff --git a/apps/web/src/stores/panel-store.ts b/apps/web/src/stores/panel-store.ts index ebbce3c0..ae09c13b 100644 --- a/apps/web/src/stores/panel-store.ts +++ b/apps/web/src/stores/panel-store.ts @@ -1,48 +1,222 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -const DEFAULT_PANEL_SIZES = { - toolsPanel: 45, - previewPanel: 75, - propertiesPanel: 20, - mainContent: 70, - timeline: 30, -} as const; +export type PanelPreset = + | "default" + | "media" + | "inspector" + | "vertical-preview"; -interface PanelState { - // Panel sizes as percentages +interface PanelSizes { toolsPanel: number; previewPanel: number; propertiesPanel: number; mainContent: number; timeline: number; +} + +export const PRESET_CONFIGS: Record<PanelPreset, PanelSizes> = { + default: { + toolsPanel: 25, + previewPanel: 50, + propertiesPanel: 25, + mainContent: 70, + timeline: 30, + }, + media: { + toolsPanel: 30, + previewPanel: 45, + propertiesPanel: 25, + mainContent: 100, + timeline: 25, + }, + inspector: { + toolsPanel: 30, + previewPanel: 70, + propertiesPanel: 30, + mainContent: 75, + timeline: 25, + }, + "vertical-preview": { + toolsPanel: 30, + previewPanel: 40, + propertiesPanel: 30, + mainContent: 75, + timeline: 25, + }, +}; + +interface PanelState extends PanelSizes { + activePreset: PanelPreset; + presetCustomSizes: Record<PanelPreset, Partial<PanelSizes>>; + resetCounter: number; mediaViewMode: "grid" | "list"; - // Actions setToolsPanel: (size: number) => void; setPreviewPanel: (size: number) => void; setPropertiesPanel: (size: number) => void; setMainContent: (size: number) => void; setTimeline: (size: number) => void; setMediaViewMode: (mode: "grid" | "list") => void; + + setActivePreset: (preset: PanelPreset) => void; + resetPreset: (preset: PanelPreset) => void; + getCurrentPresetSizes: () => PanelSizes; } export const usePanelStore = create<PanelState>()( persist( - (set) => ({ - // Default sizes - optimized for responsiveness - ...DEFAULT_PANEL_SIZES, + (set, get) => ({ + ...PRESET_CONFIGS.default, + activePreset: "default" as PanelPreset, + presetCustomSizes: { + default: {}, + media: {}, + inspector: {}, + "vertical-preview": {}, + }, + resetCounter: 0, mediaViewMode: "grid" as const, - // Actions - setToolsPanel: (size) => set({ toolsPanel: size }), - setPreviewPanel: (size) => set({ previewPanel: size }), - setPropertiesPanel: (size) => set({ propertiesPanel: size }), - setMainContent: (size) => set({ mainContent: size }), - setTimeline: (size) => set({ timeline: size }), + setToolsPanel: (size) => { + const { activePreset, presetCustomSizes } = get(); + set({ + toolsPanel: size, + presetCustomSizes: { + ...presetCustomSizes, + [activePreset]: { + ...presetCustomSizes[activePreset], + toolsPanel: size, + }, + }, + }); + }, + setPreviewPanel: (size) => { + const { activePreset, presetCustomSizes } = get(); + set({ + previewPanel: size, + presetCustomSizes: { + ...presetCustomSizes, + [activePreset]: { + ...presetCustomSizes[activePreset], + previewPanel: size, + }, + }, + }); + }, + setPropertiesPanel: (size) => { + const { activePreset, presetCustomSizes } = get(); + set({ + propertiesPanel: size, + presetCustomSizes: { + ...presetCustomSizes, + [activePreset]: { + ...presetCustomSizes[activePreset], + propertiesPanel: size, + }, + }, + }); + }, + setMainContent: (size) => { + const { activePreset, presetCustomSizes } = get(); + set({ + mainContent: size, + presetCustomSizes: { + ...presetCustomSizes, + [activePreset]: { + ...presetCustomSizes[activePreset], + mainContent: size, + }, + }, + }); + }, + setTimeline: (size) => { + const { activePreset, presetCustomSizes } = get(); + set({ + timeline: size, + presetCustomSizes: { + ...presetCustomSizes, + [activePreset]: { + ...presetCustomSizes[activePreset], + timeline: size, + }, + }, + }); + }, setMediaViewMode: (mode) => set({ mediaViewMode: mode }), + + setActivePreset: (preset) => { + const { + activePreset: currentPreset, + presetCustomSizes, + toolsPanel, + previewPanel, + propertiesPanel, + mainContent, + timeline, + } = get(); + + const updatedPresetCustomSizes = { + ...presetCustomSizes, + [currentPreset]: { + toolsPanel, + previewPanel, + propertiesPanel, + mainContent, + timeline, + }, + }; + + const defaultSizes = PRESET_CONFIGS[preset]; + const customSizes = updatedPresetCustomSizes[preset] || {}; + const finalSizes = { ...defaultSizes, ...customSizes } as PanelSizes; + + set({ + activePreset: preset, + presetCustomSizes: updatedPresetCustomSizes, + ...finalSizes, + }); + }, + + resetPreset: (preset) => { + const { presetCustomSizes, activePreset, resetCounter } = get(); + const defaultSizes = PRESET_CONFIGS[preset]; + + const newPresetCustomSizes = { + ...presetCustomSizes, + [preset]: {}, + }; + + const updates: Partial<PanelState> = { + presetCustomSizes: newPresetCustomSizes, + resetCounter: resetCounter + 1, + }; + + if (preset === activePreset) { + Object.assign(updates, defaultSizes); + } + + set(updates); + }, + + getCurrentPresetSizes: () => { + const { + toolsPanel, + previewPanel, + propertiesPanel, + mainContent, + timeline, + } = get(); + return { + toolsPanel, + previewPanel, + propertiesPanel, + mainContent, + timeline, + }; + }, }), { name: "panel-sizes", |
