From 4d67e366ad23e1d3b23b6bc33d4b4a4eefa6e7c5 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Fri, 11 Jul 2025 03:52:39 +0200 Subject: [PATCH] feat: background settings (color, blur) --- .../src/components/background-settings.tsx | 184 +++++++++++++ .../src/components/editor/preview-panel.tsx | 100 ++++++- apps/web/src/components/icons.tsx | 61 +++++ apps/web/src/data/colors.ts | 243 ++++++++++++++++++ apps/web/src/lib/storage/storage-service.ts | 6 + apps/web/src/stores/project-store.ts | 59 +++++ apps/web/src/types/project.ts | 3 + 7 files changed, 654 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/components/background-settings.tsx create mode 100644 apps/web/src/data/colors.ts diff --git a/apps/web/src/components/background-settings.tsx b/apps/web/src/components/background-settings.tsx new file mode 100644 index 0000000..bcefac1 --- /dev/null +++ b/apps/web/src/components/background-settings.tsx @@ -0,0 +1,184 @@ +import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; +import { Button } from "./ui/button"; +import { BackgroundIcon } from "./icons"; +import { cn } from "@/lib/utils"; +import Image from "next/image"; +import { colors } from "@/data/colors"; +import { useProjectStore } from "@/stores/project-store"; +import { PipetteIcon } from "lucide-react"; + +type BackgroundTab = "color" | "blur"; + +export function BackgroundSettings() { + const { activeProject, updateBackgroundType } = useProjectStore(); + + // ✅ Good: derive activeTab from activeProject during rendering + const activeTab = activeProject?.backgroundType || "color"; + + const handleColorSelect = (color: string) => { + updateBackgroundType("color", { backgroundColor: color }); + }; + + const handleBlurSelect = (blurIntensity: number) => { + updateBackgroundType("blur", { blurIntensity }); + }; + + const tabs = [ + { + label: "Color", + value: "color", + }, + { + label: "Blur", + value: "blur", + }, + ]; + + return ( + + + + + +
+

Background

+
+ {tabs.map((tab) => ( + { + // Switch to the background type when clicking tabs + if (tab.value === "color") { + updateBackgroundType("color", { + backgroundColor: + activeProject?.backgroundColor || "#000000", + }); + } else { + updateBackgroundType("blur", { + blurIntensity: activeProject?.blurIntensity || 8, + }); + } + }} + className={cn( + "text-muted-foreground cursor-pointer", + activeTab === tab.value && "text-foreground" + )} + > + {tab.label} + + ))} +
+
+ {activeTab === "color" ? ( + + ) : ( + + )} +
+
+ ); +} + +function ColorView({ + selectedColor, + onColorSelect, +}: { + selectedColor: string; + onColorSelect: (color: string) => void; +}) { + return ( +
+
+
+
+ +
+ {colors.map((color) => ( + onColorSelect(color)} + /> + ))} +
+
+ ); +} + +function ColorItem({ + color, + isSelected, + onClick, +}: { + color: string; + isSelected: boolean; + onClick: () => void; +}) { + return ( +
+ ); +} + +function BlurView({ + selectedBlur, + onBlurSelect, +}: { + selectedBlur: number; + onBlurSelect: (blurIntensity: number) => void; +}) { + const blurLevels = [ + { label: "Light", value: 4 }, + { label: "Medium", value: 8 }, + { label: "Heavy", value: 18 }, + ]; + const blurImage = + "https://images.unsplash.com/photo-1501785888041-af3ef285b470?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"; + + return ( +
+ {blurLevels.map((blur) => ( +
onBlurSelect(blur.value)} + > + {`Blur +
+ + {blur.label} + +
+
+ ))} +
+ ); +} diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 1cc9d97..2f76af5 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -21,6 +21,8 @@ import { useState, useRef, useEffect } from "react"; import { cn } from "@/lib/utils"; import { formatTimeCode } from "@/lib/time"; import { FONT_CLASS_MAP } from "@/lib/font-config"; +import { BackgroundSettings } from "../background-settings"; +import { useProjectStore } from "@/stores/project-store"; interface ActiveElement { element: TimelineElement; @@ -39,6 +41,7 @@ export function PreviewPanel() { width: 0, height: 0, }); + const { activeProject } = useProjectStore(); // Calculate optimal preview size that fits in container while maintaining aspect ratio useEffect(() => { @@ -136,6 +139,85 @@ export function PreviewPanel() { // Check if there are any elements in the timeline at all const hasAnyElements = tracks.some((track) => track.elements.length > 0); + // Get media elements for blur background (video/image only) + const getBlurBackgroundElements = (): ActiveElement[] => { + return activeElements.filter( + ({ element, mediaItem }) => + element.type === "media" && + mediaItem && + (mediaItem.type === "video" || mediaItem.type === "image") && + element.mediaId !== "test" // Exclude test elements + ); + }; + + const blurBackgroundElements = getBlurBackgroundElements(); + + // Render blur background layer + const renderBlurBackground = () => { + if ( + !activeProject?.backgroundType || + activeProject.backgroundType !== "blur" || + blurBackgroundElements.length === 0 + ) { + return null; + } + + // Use the first media element for background (could be enhanced to use primary/focused element) + const backgroundElement = blurBackgroundElements[0]; + const { element, mediaItem } = backgroundElement; + + if (!mediaItem) return null; + + const blurIntensity = activeProject.blurIntensity || 8; + + if (mediaItem.type === "video") { + return ( +
+ +
+ ); + } + + if (mediaItem.type === "image") { + return ( +
+ {mediaItem.name} +
+ ); + } + + return null; + }; + // Render an element const renderElement = (elementData: ActiveElement, index: number) => { const { element, mediaItem } = elementData; @@ -265,12 +347,17 @@ export function PreviewPanel() { {hasAnyElements ? (
+ {renderBlurBackground()} {activeElements.length === 0 ? (
No elements at current time @@ -280,6 +367,14 @@ export function PreviewPanel() { renderElement(elementData, index) ) )} + {/* Show message when blur is selected but no media available */} + {activeProject?.backgroundType === "blur" && + blurBackgroundElements.length === 0 && + activeElements.length > 0 && ( +
+ Add a video or image to use blur background +
+ )}
) : null} @@ -346,7 +441,8 @@ function PreviewToolbar({ hasAnyElements }: { hasAnyElements: boolean }) { )} -
+
+