From dd09bf2949b63d17b3e7311a688d7d84c5d6dc52 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Sat, 2 Aug 2025 01:41:46 +0200 Subject: [PATCH] feat: add project settings --- .../components/editor/media-panel/index.tsx | 2 + .../components/editor/media-panel/store.ts | 8 +- .../editor/media-panel/views/settings.tsx | 246 ++++++++++++++++++ .../editor/properties-panel/property-item.tsx | 10 +- apps/web/src/components/icons.tsx | 22 ++ apps/web/src/components/ui/select.tsx | 4 +- apps/web/src/components/ui/tabs.tsx | 4 +- 7 files changed, 288 insertions(+), 8 deletions(-) create mode 100644 apps/web/src/components/editor/media-panel/views/settings.tsx diff --git a/apps/web/src/components/editor/media-panel/index.tsx b/apps/web/src/components/editor/media-panel/index.tsx index a5cd6686..4d80b642 100644 --- a/apps/web/src/components/editor/media-panel/index.tsx +++ b/apps/web/src/components/editor/media-panel/index.tsx @@ -6,6 +6,7 @@ import { useMediaPanelStore, Tab } from "./store"; import { TextView } from "./views/text"; import { AudioView } from "./views/audio"; import { Separator } from "@/components/ui/separator"; +import { SettingsView } from "./views/settings"; export function MediaPanel() { const { activeTab } = useMediaPanelStore(); @@ -44,6 +45,7 @@ export function MediaPanel() { Adjustment view coming soon... ), + settings: , }; return ( diff --git a/apps/web/src/components/editor/media-panel/store.ts b/apps/web/src/components/editor/media-panel/store.ts index c661f51e..577417a9 100644 --- a/apps/web/src/components/editor/media-panel/store.ts +++ b/apps/web/src/components/editor/media-panel/store.ts @@ -9,6 +9,7 @@ import { SlidersHorizontalIcon, LucideIcon, TypeIcon, + SettingsIcon, } from "lucide-react"; import { create } from "zustand"; @@ -21,7 +22,8 @@ export type Tab = | "transitions" | "captions" | "filters" - | "adjustment"; + | "adjustment" + | "settings"; export const tabs: { [key in Tab]: { icon: LucideIcon; label: string } } = { media: { @@ -60,6 +62,10 @@ export const tabs: { [key in Tab]: { icon: LucideIcon; label: string } } = { icon: SlidersHorizontalIcon, label: "Adjustment", }, + settings: { + icon: SettingsIcon, + label: "Settings", + }, }; interface MediaPanelStore { diff --git a/apps/web/src/components/editor/media-panel/views/settings.tsx b/apps/web/src/components/editor/media-panel/views/settings.tsx new file mode 100644 index 00000000..54387dd7 --- /dev/null +++ b/apps/web/src/components/editor/media-panel/views/settings.tsx @@ -0,0 +1,246 @@ +"use client"; + +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Separator } from "@/components/ui/separator"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + PropertyItem, + PropertyItemLabel, + PropertyItemValue, +} from "../../properties-panel/property-item"; +import { FPS_PRESETS } from "@/constants/timeline-constants"; +import { useProjectStore } from "@/stores/project-store"; +import { useEditorStore } from "@/stores/editor-store"; +import { useAspectRatio } from "@/hooks/use-aspect-ratio"; +import Image from "next/image"; +import { cn } from "@/lib/utils"; +import { colors } from "@/data/colors"; +import { PipetteIcon } from "lucide-react"; +import { useMemo, memo, useCallback } from "react"; + +export function SettingsView() { + return ; +} + +function ProjectSettingsTabs() { + return ( +
+ +
+ + Project info + Background + +
+ + + + + + + + + +
+
+ ); +} + +function ProjectInfoView() { + const { activeProject, updateProjectFps } = useProjectStore(); + const { canvasSize, canvasPresets, setCanvasSize } = useEditorStore(); + const { getDisplayName } = useAspectRatio(); + + const handleAspectRatioChange = (value: string) => { + const preset = canvasPresets.find((p) => p.name === value); + if (preset) { + setCanvasSize({ width: preset.width, height: preset.height }); + } + }; + + const handleFpsChange = (value: string) => { + const fps = parseFloat(value); + updateProjectFps(fps); + }; + + return ( +
+ + Name + + {activeProject?.name || "Untitled project"} + + + + + Aspect ratio + + + + + + + Frame rate + + + + +
+ ); +} + +const BlurPreview = memo( + ({ + blur, + isSelected, + onSelect, + }: { + blur: { label: string; value: number }; + isSelected: boolean; + onSelect: () => void; + }) => ( +
+ {`Blur +
+ + {blur.label} + +
+
+ ) +); + +BlurPreview.displayName = "BlurPreview"; + +function BackgroundView() { + const { activeProject, updateBackgroundType } = useProjectStore(); + + const blurLevels = useMemo( + () => [ + { label: "Light", value: 4 }, + { label: "Medium", value: 8 }, + { label: "Heavy", value: 18 }, + ], + [] + ); + + const handleBlurSelect = useCallback( + async (blurIntensity: number) => { + await updateBackgroundType("blur", { blurIntensity }); + }, + [updateBackgroundType] + ); + + const handleColorSelect = useCallback( + async (color: string) => { + await updateBackgroundType("color", { backgroundColor: color }); + }, + [updateBackgroundType] + ); + + const currentBlurIntensity = activeProject?.blurIntensity || 8; + const isBlurBackground = activeProject?.backgroundType === "blur"; + const currentBackgroundColor = activeProject?.backgroundColor || "#000000"; + const isColorBackground = activeProject?.backgroundType === "color"; + + const blurPreviews = useMemo( + () => + blurLevels.map((blur) => ( + handleBlurSelect(blur.value)} + /> + )), + [blurLevels, isBlurBackground, currentBlurIntensity, handleBlurSelect] + ); + + const colorPreviews = useMemo( + () => + colors.map((color) => ( +
handleColorSelect(color)} + /> + )), + [isColorBackground, currentBackgroundColor, handleColorSelect] + ); + + return ( +
+ + Blur + +
{blurPreviews}
+
+
+ + Color + +
+
+ +
+ {colorPreviews} +
+
+
+
+ ); +} diff --git a/apps/web/src/components/editor/properties-panel/property-item.tsx b/apps/web/src/components/editor/properties-panel/property-item.tsx index 804ffb39..86a8d378 100644 --- a/apps/web/src/components/editor/properties-panel/property-item.tsx +++ b/apps/web/src/components/editor/properties-panel/property-item.tsx @@ -17,7 +17,7 @@ export function PropertyItem({ "flex gap-2", direction === "row" ? "items-center justify-between gap-6" - : "flex-col gap-1", + : "flex-col gap-1.5", className )} > @@ -33,7 +33,11 @@ export function PropertyItemLabel({ children: React.ReactNode; className?: string; }) { - return ; + return ( + + ); } export function PropertyItemValue({ @@ -43,5 +47,5 @@ export function PropertyItemValue({ children: React.ReactNode; className?: string; }) { - return
{children}
; + return
{children}
; } diff --git a/apps/web/src/components/icons.tsx b/apps/web/src/components/icons.tsx index 43f953e8..065a123d 100644 --- a/apps/web/src/components/icons.tsx +++ b/apps/web/src/components/icons.tsx @@ -163,3 +163,25 @@ export function DataBuddyIcon({ ); } + +export function SquareSlashIcon({ + className, + size = 24, +}: { + className?: string; + size?: number; +}) { + return ( + + + + + ); +} diff --git a/apps/web/src/components/ui/select.tsx b/apps/web/src/components/ui/select.tsx index ce4f340d..53ca27cb 100644 --- a/apps/web/src/components/ui/select.tsx +++ b/apps/web/src/components/ui/select.tsx @@ -35,14 +35,14 @@ const SelectTrigger = React.forwardRef< span]:line-clamp-1", + "flex gap-1 h-7 cursor-pointer w-auto items-center justify-between whitespace-nowrap rounded-md bg-accent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className )} {...props} > {children} - + )); diff --git a/apps/web/src/components/ui/tabs.tsx b/apps/web/src/components/ui/tabs.tsx index e5384daf..ea628959 100644 --- a/apps/web/src/components/ui/tabs.tsx +++ b/apps/web/src/components/ui/tabs.tsx @@ -14,7 +14,7 @@ const TabsList = React.forwardRef<