feat: background settings (color, blur)
This commit is contained in:
184
apps/web/src/components/background-settings.tsx
Normal file
184
apps/web/src/components/background-settings.tsx
Normal file
@ -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 (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="text"
|
||||
size="icon"
|
||||
className="!size-5 border border-muted-foreground"
|
||||
>
|
||||
<BackgroundIcon className="!size-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="flex flex-col items-start w-[20rem] h-[16rem] overflow-hidden p-0">
|
||||
<div className="flex items-center justify-between w-full gap-2 z-10 bg-popover p-3">
|
||||
<h2 className="text-sm">Background</h2>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
{tabs.map((tab) => (
|
||||
<span
|
||||
key={tab.value}
|
||||
onClick={() => {
|
||||
// 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}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{activeTab === "color" ? (
|
||||
<ColorView
|
||||
selectedColor={activeProject?.backgroundColor || "#000000"}
|
||||
onColorSelect={handleColorSelect}
|
||||
/>
|
||||
) : (
|
||||
<BlurView
|
||||
selectedBlur={activeProject?.blurIntensity || 8}
|
||||
onBlurSelect={handleBlurSelect}
|
||||
/>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
function ColorView({
|
||||
selectedColor,
|
||||
onColorSelect,
|
||||
}: {
|
||||
selectedColor: string;
|
||||
onColorSelect: (color: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
<div className="absolute top-8 left-0 w-[calc(100%-1rem)] h-12 bg-gradient-to-b from-popover to-transparent pointer-events-none"></div>
|
||||
<div className="grid grid-cols-4 gap-2 w-full h-full p-3 pt-0 overflow-auto">
|
||||
<div className="w-full aspect-square rounded-sm cursor-pointer border border-foreground/15 hover:border-primary flex items-center justify-center">
|
||||
<PipetteIcon className="size-4" />
|
||||
</div>
|
||||
{colors.map((color) => (
|
||||
<ColorItem
|
||||
key={color}
|
||||
color={color}
|
||||
isSelected={color === selectedColor}
|
||||
onClick={() => onColorSelect(color)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ColorItem({
|
||||
color,
|
||||
isSelected,
|
||||
onClick,
|
||||
}: {
|
||||
color: string;
|
||||
isSelected: boolean;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"w-full aspect-square rounded-sm cursor-pointer hover:border-2 hover:border-primary",
|
||||
isSelected && "border-2 border-primary"
|
||||
)}
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="grid grid-cols-3 gap-2 w-full p-3 pt-0">
|
||||
{blurLevels.map((blur) => (
|
||||
<div
|
||||
key={blur.value}
|
||||
className={cn(
|
||||
"w-full aspect-square rounded-sm cursor-pointer hover:border-2 hover:border-primary relative overflow-hidden",
|
||||
selectedBlur === blur.value && "border-2 border-primary"
|
||||
)}
|
||||
onClick={() => onBlurSelect(blur.value)}
|
||||
>
|
||||
<Image
|
||||
src={blurImage}
|
||||
alt={`Blur preview ${blur.label}`}
|
||||
fill
|
||||
className="object-cover"
|
||||
style={{ filter: `blur(${blur.value}px)` }}
|
||||
/>
|
||||
<div className="absolute bottom-1 left-1 right-1 text-center">
|
||||
<span className="text-xs text-white bg-black/50 px-1 rounded">
|
||||
{blur.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<div
|
||||
key={`blur-${element.id}`}
|
||||
className="absolute inset-0 overflow-hidden"
|
||||
style={{
|
||||
filter: `blur(${blurIntensity}px)`,
|
||||
transform: "scale(1.1)", // Slightly zoom to avoid blur edge artifacts
|
||||
transformOrigin: "center",
|
||||
}}
|
||||
>
|
||||
<VideoPlayer
|
||||
src={mediaItem.url!}
|
||||
poster={mediaItem.thumbnailUrl}
|
||||
clipStartTime={element.startTime}
|
||||
trimStart={element.trimStart}
|
||||
trimEnd={element.trimEnd}
|
||||
clipDuration={element.duration}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (mediaItem.type === "image") {
|
||||
return (
|
||||
<div
|
||||
key={`blur-${element.id}`}
|
||||
className="absolute inset-0 overflow-hidden"
|
||||
style={{
|
||||
filter: `blur(${blurIntensity}px)`,
|
||||
transform: "scale(1.1)", // Slightly zoom to avoid blur edge artifacts
|
||||
transformOrigin: "center",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={mediaItem.url!}
|
||||
alt={mediaItem.name}
|
||||
className="w-full h-full object-cover"
|
||||
draggable={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Render an element
|
||||
const renderElement = (elementData: ActiveElement, index: number) => {
|
||||
const { element, mediaItem } = elementData;
|
||||
@ -265,12 +347,17 @@ export function PreviewPanel() {
|
||||
{hasAnyElements ? (
|
||||
<div
|
||||
ref={previewRef}
|
||||
className="relative overflow-hidden rounded-sm bg-black border"
|
||||
className="relative overflow-hidden rounded-sm border"
|
||||
style={{
|
||||
width: previewDimensions.width,
|
||||
height: previewDimensions.height,
|
||||
backgroundColor:
|
||||
activeProject?.backgroundType === "blur"
|
||||
? "transparent"
|
||||
: activeProject?.backgroundColor || "#000000",
|
||||
}}
|
||||
>
|
||||
{renderBlurBackground()}
|
||||
{activeElements.length === 0 ? (
|
||||
<div className="absolute inset-0 flex items-center justify-center text-muted-foreground">
|
||||
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 && (
|
||||
<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>
|
||||
) : null}
|
||||
|
||||
@ -346,7 +441,8 @@ function PreviewToolbar({ hasAnyElements }: { hasAnyElements: boolean }) {
|
||||
<Play className="h-3 w-3" />
|
||||
)}
|
||||
</Button>
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<BackgroundSettings />
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
|
@ -37,3 +37,64 @@ export function GithubIcon({ className }: { className?: string }) {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function BackgroundIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="353"
|
||||
height="353"
|
||||
viewBox="0 0 353 353"
|
||||
fill="none"
|
||||
className={className}
|
||||
>
|
||||
<g clipPath="url(#clip0_1_3)">
|
||||
<rect
|
||||
x="-241.816"
|
||||
y="233.387"
|
||||
width="592.187"
|
||||
height="17.765"
|
||||
transform="rotate(-37 -241.816 233.387)"
|
||||
fill="white"
|
||||
/>
|
||||
<rect
|
||||
x="-189.907"
|
||||
y="306.804"
|
||||
width="592.187"
|
||||
height="17.765"
|
||||
transform="rotate(-37 -189.907 306.804)"
|
||||
fill="white"
|
||||
/>
|
||||
<rect
|
||||
x="-146.928"
|
||||
y="389.501"
|
||||
width="592.187"
|
||||
height="17.765"
|
||||
transform="rotate(-37 -146.928 389.501)"
|
||||
fill="white"
|
||||
/>
|
||||
<rect
|
||||
x="-103.144"
|
||||
y="477.904"
|
||||
width="592.187"
|
||||
height="17.765"
|
||||
transform="rotate(-37 -103.144 477.904)"
|
||||
fill="white"
|
||||
/>
|
||||
<rect
|
||||
x="-57.169"
|
||||
y="570.714"
|
||||
width="592.187"
|
||||
height="17.765"
|
||||
transform="rotate(-37 -57.169 570.714)"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_3">
|
||||
<rect width="353" height="353" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
243
apps/web/src/data/colors.ts
Normal file
243
apps/web/src/data/colors.ts
Normal file
@ -0,0 +1,243 @@
|
||||
export const colors = [
|
||||
"#fef2f2",
|
||||
"#ffe2e2",
|
||||
"#ffc9c9",
|
||||
"#ffa2a2",
|
||||
"#ff6467",
|
||||
"#fb2c36",
|
||||
"#e7000b",
|
||||
"#c10007",
|
||||
"#9f0712",
|
||||
"#82181a",
|
||||
"#460809",
|
||||
"#fff7ed",
|
||||
"#ffedd4",
|
||||
"#ffd6a7",
|
||||
"#ffb86a",
|
||||
"#ff8904",
|
||||
"#ff6900",
|
||||
"#f54900",
|
||||
"#ca3500",
|
||||
"#9f2d00",
|
||||
"#7e2a0c",
|
||||
"#441306",
|
||||
"#fffbeb",
|
||||
"#fef3c6",
|
||||
"#fee685",
|
||||
"#ffd230",
|
||||
"#ffb900",
|
||||
"#fe9a00",
|
||||
"#e17100",
|
||||
"#bb4d00",
|
||||
"#973c00",
|
||||
"#7b3306",
|
||||
"#461901",
|
||||
"#fefce8",
|
||||
"#fef9c2",
|
||||
"#fff085",
|
||||
"#ffdf20",
|
||||
"#fdc700",
|
||||
"#f0b100",
|
||||
"#d08700",
|
||||
"#a65f00",
|
||||
"#894b00",
|
||||
"#733e0a",
|
||||
"#432004",
|
||||
"#f7fee7",
|
||||
"#ecfcca",
|
||||
"#d8f999",
|
||||
"#bbf451",
|
||||
"#9ae600",
|
||||
"#7ccf00",
|
||||
"#5ea500",
|
||||
"#497d00",
|
||||
"#3c6300",
|
||||
"#35530e",
|
||||
"#192e03",
|
||||
"#f0fdf4",
|
||||
"#dcfce7",
|
||||
"#b9f8cf",
|
||||
"#7bf1a8",
|
||||
"#05df72",
|
||||
"#00c950",
|
||||
"#00a63e",
|
||||
"#008236",
|
||||
"#016630",
|
||||
"#0d542b",
|
||||
"#032e15",
|
||||
"#ecfdf5",
|
||||
"#d0fae5",
|
||||
"#a4f4cf",
|
||||
"#5ee9b5",
|
||||
"#00d492",
|
||||
"#00bc7d",
|
||||
"#009966",
|
||||
"#007a55",
|
||||
"#006045",
|
||||
"#004f3b",
|
||||
"#002c22",
|
||||
"#f0fdfa",
|
||||
"#cbfbf1",
|
||||
"#96f7e4",
|
||||
"#46ecd5",
|
||||
"#00d5be",
|
||||
"#00bba7",
|
||||
"#009689",
|
||||
"#00786f",
|
||||
"#005f5a",
|
||||
"#0b4f4a",
|
||||
"#022f2e",
|
||||
"#ecfeff",
|
||||
"#cefafe",
|
||||
"#a2f4fd",
|
||||
"#53eafd",
|
||||
"#00d3f2",
|
||||
"#00b8db",
|
||||
"#0092b8",
|
||||
"#007595",
|
||||
"#005f78",
|
||||
"#104e64",
|
||||
"#053345",
|
||||
"#f0f9ff",
|
||||
"#dff2fe",
|
||||
"#b8e6fe",
|
||||
"#74d4ff",
|
||||
"#00bcff",
|
||||
"#00a6f4",
|
||||
"#0084d1",
|
||||
"#0069a8",
|
||||
"#00598a",
|
||||
"#024a70",
|
||||
"#052f4a",
|
||||
"#eff6ff",
|
||||
"#dbeafe",
|
||||
"#bedbff",
|
||||
"#8ec5ff",
|
||||
"#51a2ff",
|
||||
"#2b7fff",
|
||||
"#155dfc",
|
||||
"#1447e6",
|
||||
"#193cb8",
|
||||
"#1c398e",
|
||||
"#162456",
|
||||
"#eef2ff",
|
||||
"#e0e7ff",
|
||||
"#c6d2ff",
|
||||
"#a3b3ff",
|
||||
"#7c86ff",
|
||||
"#615fff",
|
||||
"#4f39f6",
|
||||
"#432dd7",
|
||||
"#372aac",
|
||||
"#312c85",
|
||||
"#1e1a4d",
|
||||
"#f5f3ff",
|
||||
"#ede9fe",
|
||||
"#ddd6ff",
|
||||
"#c4b4ff",
|
||||
"#a684ff",
|
||||
"#8e51ff",
|
||||
"#7f22fe",
|
||||
"#7008e7",
|
||||
"#5d0ec0",
|
||||
"#4d179a",
|
||||
"#2f0d68",
|
||||
"#faf5ff",
|
||||
"#f3e8ff",
|
||||
"#e9d4ff",
|
||||
"#dab2ff",
|
||||
"#c27aff",
|
||||
"#ad46ff",
|
||||
"#9810fa",
|
||||
"#8200db",
|
||||
"#6e11b0",
|
||||
"#59168b",
|
||||
"#3c0366",
|
||||
"#fdf4ff",
|
||||
"#fae8ff",
|
||||
"#f6cfff",
|
||||
"#f4a8ff",
|
||||
"#ed6aff",
|
||||
"#e12afb",
|
||||
"#c800de",
|
||||
"#a800b7",
|
||||
"#8a0194",
|
||||
"#721378",
|
||||
"#4b004f",
|
||||
"#fdf2f8",
|
||||
"#fce7f3",
|
||||
"#fccee8",
|
||||
"#fda5d5",
|
||||
"#fb64b6",
|
||||
"#f6339a",
|
||||
"#e60076",
|
||||
"#c6005c",
|
||||
"#a3004c",
|
||||
"#861043",
|
||||
"#510424",
|
||||
"#fff1f2",
|
||||
"#ffe4e6",
|
||||
"#ffccd3",
|
||||
"#ffa1ad",
|
||||
"#ff637e",
|
||||
"#ff2056",
|
||||
"#ec003f",
|
||||
"#c70036",
|
||||
"#a50036",
|
||||
"#8b0836",
|
||||
"#4d0218",
|
||||
"#f8fafc",
|
||||
"#f1f5f9",
|
||||
"#e2e8f0",
|
||||
"#cad5e2",
|
||||
"#90a1b9",
|
||||
"#62748e",
|
||||
"#45556c",
|
||||
"#314158",
|
||||
"#1d293d",
|
||||
"#0f172b",
|
||||
"#020618",
|
||||
"#f9fafb",
|
||||
"#f3f4f6",
|
||||
"#e5e7eb",
|
||||
"#d1d5dc",
|
||||
"#99a1af",
|
||||
"#6a7282",
|
||||
"#4a5565",
|
||||
"#364153",
|
||||
"#1e2939",
|
||||
"#101828",
|
||||
"#030712",
|
||||
"#fafafa",
|
||||
"#f4f4f5",
|
||||
"#e4e4e7",
|
||||
"#d4d4d8",
|
||||
"#9f9fa9",
|
||||
"#71717b",
|
||||
"#52525c",
|
||||
"#3f3f46",
|
||||
"#27272a",
|
||||
"#18181b",
|
||||
"#09090b",
|
||||
"#f5f5f5",
|
||||
"#e5e5e5",
|
||||
"#d4d4d4",
|
||||
"#a1a1a1",
|
||||
"#737373",
|
||||
"#525252",
|
||||
"#404040",
|
||||
"#262626",
|
||||
"#171717",
|
||||
"#0a0a0a",
|
||||
"#fafaf9",
|
||||
"#f5f5f4",
|
||||
"#e7e5e4",
|
||||
"#d6d3d1",
|
||||
"#a6a09b",
|
||||
"#79716b",
|
||||
"#57534d",
|
||||
"#44403b",
|
||||
"#292524",
|
||||
"#1c1917",
|
||||
"#0c0a09",
|
||||
];
|
@ -60,6 +60,9 @@ class StorageService {
|
||||
thumbnail: project.thumbnail,
|
||||
createdAt: project.createdAt.toISOString(),
|
||||
updatedAt: project.updatedAt.toISOString(),
|
||||
backgroundColor: project.backgroundColor,
|
||||
backgroundType: project.backgroundType,
|
||||
blurIntensity: project.blurIntensity,
|
||||
};
|
||||
|
||||
await this.projectsAdapter.set(project.id, serializedProject);
|
||||
@ -77,6 +80,9 @@ class StorageService {
|
||||
thumbnail: serializedProject.thumbnail,
|
||||
createdAt: new Date(serializedProject.createdAt),
|
||||
updatedAt: new Date(serializedProject.updatedAt),
|
||||
backgroundColor: serializedProject.backgroundColor,
|
||||
backgroundType: serializedProject.backgroundType,
|
||||
blurIntensity: serializedProject.blurIntensity,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,11 @@ interface ProjectStore {
|
||||
closeProject: () => void;
|
||||
renameProject: (projectId: string, name: string) => Promise<void>;
|
||||
duplicateProject: (projectId: string) => Promise<string>;
|
||||
updateProjectBackground: (backgroundColor: string) => Promise<void>;
|
||||
updateBackgroundType: (
|
||||
type: "color" | "blur",
|
||||
options?: { backgroundColor?: string; blurIntensity?: number }
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useProjectStore = create<ProjectStore>((set, get) => ({
|
||||
@ -35,6 +40,9 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
|
||||
thumbnail: "",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
backgroundColor: "#000000",
|
||||
backgroundType: "color",
|
||||
blurIntensity: 8,
|
||||
};
|
||||
|
||||
set({ activeProject: newProject });
|
||||
@ -234,4 +242,55 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
updateProjectBackground: async (backgroundColor: string) => {
|
||||
const { activeProject } = get();
|
||||
if (!activeProject) return;
|
||||
|
||||
const updatedProject = {
|
||||
...activeProject,
|
||||
backgroundColor,
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
try {
|
||||
await storageService.saveProject(updatedProject);
|
||||
set({ activeProject: updatedProject });
|
||||
await get().loadAllProjects(); // Refresh the list
|
||||
} catch (error) {
|
||||
console.error("Failed to update project background:", error);
|
||||
toast.error("Failed to update background", {
|
||||
description: "Please try again",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
updateBackgroundType: async (
|
||||
type: "color" | "blur",
|
||||
options?: { backgroundColor?: string; blurIntensity?: number }
|
||||
) => {
|
||||
const { activeProject } = get();
|
||||
if (!activeProject) return;
|
||||
|
||||
const updatedProject = {
|
||||
...activeProject,
|
||||
backgroundType: type,
|
||||
...(options?.backgroundColor && {
|
||||
backgroundColor: options.backgroundColor,
|
||||
}),
|
||||
...(options?.blurIntensity && { blurIntensity: options.blurIntensity }),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
try {
|
||||
await storageService.saveProject(updatedProject);
|
||||
set({ activeProject: updatedProject });
|
||||
await get().loadAllProjects(); // Refresh the list
|
||||
} catch (error) {
|
||||
console.error("Failed to update background type:", error);
|
||||
toast.error("Failed to update background", {
|
||||
description: "Please try again",
|
||||
});
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
@ -5,4 +5,7 @@ export interface TProject {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
mediaItems?: string[];
|
||||
backgroundColor?: string;
|
||||
backgroundType?: "color" | "blur";
|
||||
blurIntensity?: number; // in pixels (4, 8, 18)
|
||||
}
|
||||
|
Reference in New Issue
Block a user