2025-08-02 01:41:46 +02:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from "@/components/ui/select";
|
2025-08-05 08:46:17 +02:00
|
|
|
import { BaseView } from "./base-view";
|
2025-08-02 01:41:46 +02:00
|
|
|
import {
|
|
|
|
|
PropertyItem,
|
|
|
|
|
PropertyItemLabel,
|
|
|
|
|
PropertyItemValue,
|
2025-08-02 12:21:24 +02:00
|
|
|
PropertyGroup,
|
2025-08-02 01:41:46 +02:00
|
|
|
} 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";
|
2025-08-02 16:09:20 +02:00
|
|
|
import { colors } from "@/data/colors/solid";
|
|
|
|
|
import { patternCraftGradients } from "@/data/colors/pattern-craft";
|
2025-08-02 01:41:46 +02:00
|
|
|
import { PipetteIcon } from "lucide-react";
|
|
|
|
|
import { useMemo, memo, useCallback } from "react";
|
2025-08-02 16:09:20 +02:00
|
|
|
import { syntaxUIGradients } from "@/data/colors/syntax-ui";
|
2025-08-02 01:41:46 +02:00
|
|
|
|
|
|
|
|
export function SettingsView() {
|
|
|
|
|
return <ProjectSettingsTabs />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ProjectSettingsTabs() {
|
|
|
|
|
return (
|
2025-08-05 08:46:17 +02:00
|
|
|
<BaseView
|
|
|
|
|
defaultTab="project-info"
|
|
|
|
|
tabs={[
|
|
|
|
|
{
|
|
|
|
|
value: "project-info",
|
|
|
|
|
label: "Project info",
|
|
|
|
|
content: <ProjectInfoView />,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
value: "background",
|
|
|
|
|
label: "Background",
|
|
|
|
|
content: <BackgroundView />,
|
|
|
|
|
},
|
|
|
|
|
]}
|
|
|
|
|
/>
|
2025-08-02 01:41:46 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ProjectInfoView() {
|
2025-08-04 20:45:48 +02:00
|
|
|
const { activeProject, updateProjectFps, updateCanvasSize } =
|
|
|
|
|
useProjectStore();
|
|
|
|
|
const { canvasPresets } = useEditorStore();
|
2025-08-02 01:41:46 +02:00
|
|
|
const { getDisplayName } = useAspectRatio();
|
|
|
|
|
|
|
|
|
|
const handleAspectRatioChange = (value: string) => {
|
|
|
|
|
const preset = canvasPresets.find((p) => p.name === value);
|
|
|
|
|
if (preset) {
|
2025-08-04 20:45:48 +02:00
|
|
|
updateCanvasSize(
|
|
|
|
|
{ width: preset.width, height: preset.height },
|
|
|
|
|
"preset"
|
|
|
|
|
);
|
2025-08-02 01:41:46 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleFpsChange = (value: string) => {
|
|
|
|
|
const fps = parseFloat(value);
|
|
|
|
|
updateProjectFps(fps);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
<PropertyItem direction="column">
|
|
|
|
|
<PropertyItemLabel>Name</PropertyItemLabel>
|
|
|
|
|
<PropertyItemValue>
|
|
|
|
|
{activeProject?.name || "Untitled project"}
|
|
|
|
|
</PropertyItemValue>
|
|
|
|
|
</PropertyItem>
|
|
|
|
|
|
|
|
|
|
<PropertyItem direction="column">
|
|
|
|
|
<PropertyItemLabel>Aspect ratio</PropertyItemLabel>
|
|
|
|
|
<PropertyItemValue>
|
|
|
|
|
<Select
|
|
|
|
|
value={getDisplayName()}
|
|
|
|
|
onValueChange={handleAspectRatioChange}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger className="bg-panel-accent">
|
|
|
|
|
<SelectValue placeholder="Select an aspect ratio" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
{canvasPresets.map((preset) => (
|
|
|
|
|
<SelectItem key={preset.name} value={preset.name}>
|
|
|
|
|
{preset.name}
|
|
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</PropertyItemValue>
|
|
|
|
|
</PropertyItem>
|
|
|
|
|
|
|
|
|
|
<PropertyItem direction="column">
|
|
|
|
|
<PropertyItemLabel>Frame rate</PropertyItemLabel>
|
|
|
|
|
<PropertyItemValue>
|
|
|
|
|
<Select
|
|
|
|
|
value={(activeProject?.fps || 30).toString()}
|
|
|
|
|
onValueChange={handleFpsChange}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger className="bg-panel-accent">
|
|
|
|
|
<SelectValue placeholder="Select a frame rate" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
{FPS_PRESETS.map((preset) => (
|
|
|
|
|
<SelectItem key={preset.value} value={preset.value}>
|
|
|
|
|
{preset.label}
|
|
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</PropertyItemValue>
|
|
|
|
|
</PropertyItem>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const BlurPreview = memo(
|
|
|
|
|
({
|
|
|
|
|
blur,
|
|
|
|
|
isSelected,
|
|
|
|
|
onSelect,
|
|
|
|
|
}: {
|
|
|
|
|
blur: { label: string; value: number };
|
|
|
|
|
isSelected: boolean;
|
|
|
|
|
onSelect: () => void;
|
|
|
|
|
}) => (
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
2025-08-02 16:09:20 +02:00
|
|
|
"w-full aspect-square rounded-sm cursor-pointer border border-foreground/15 hover:border-primary relative overflow-hidden",
|
|
|
|
|
isSelected && "border-2 border-primary"
|
2025-08-02 01:41:46 +02:00
|
|
|
)}
|
|
|
|
|
onClick={onSelect}
|
|
|
|
|
>
|
|
|
|
|
<Image
|
|
|
|
|
src="https://images.unsplash.com/photo-1501785888041-af3ef285b470?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
|
|
|
|
alt={`Blur preview ${blur.label}`}
|
|
|
|
|
fill
|
|
|
|
|
className="object-cover"
|
|
|
|
|
style={{ filter: `blur(${blur.value}px)` }}
|
2025-08-02 12:21:24 +02:00
|
|
|
loading="eager"
|
2025-08-02 01:41:46 +02:00
|
|
|
/>
|
|
|
|
|
<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>
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
BlurPreview.displayName = "BlurPreview";
|
|
|
|
|
|
2025-08-02 16:09:20 +02:00
|
|
|
const BackgroundPreviews = memo(
|
|
|
|
|
({
|
|
|
|
|
backgrounds,
|
|
|
|
|
currentBackgroundColor,
|
|
|
|
|
isColorBackground,
|
|
|
|
|
handleColorSelect,
|
|
|
|
|
useBackgroundColor = false,
|
|
|
|
|
}: {
|
|
|
|
|
backgrounds: string[];
|
|
|
|
|
currentBackgroundColor: string;
|
|
|
|
|
isColorBackground: boolean;
|
|
|
|
|
handleColorSelect: (bg: string) => void;
|
|
|
|
|
useBackgroundColor?: boolean;
|
|
|
|
|
}) => {
|
|
|
|
|
return useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
backgrounds.map((bg) => (
|
|
|
|
|
<div
|
|
|
|
|
key={bg}
|
|
|
|
|
className={cn(
|
|
|
|
|
"w-full aspect-square rounded-sm cursor-pointer border border-foreground/15 hover:border-primary",
|
|
|
|
|
isColorBackground &&
|
|
|
|
|
bg === currentBackgroundColor &&
|
|
|
|
|
"border-2 border-primary"
|
|
|
|
|
)}
|
|
|
|
|
style={
|
|
|
|
|
useBackgroundColor
|
|
|
|
|
? { backgroundColor: bg }
|
|
|
|
|
: {
|
|
|
|
|
background: bg,
|
|
|
|
|
backgroundSize: "cover",
|
|
|
|
|
backgroundPosition: "center",
|
|
|
|
|
backgroundRepeat: "no-repeat",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
onClick={() => handleColorSelect(bg)}
|
|
|
|
|
/>
|
|
|
|
|
)),
|
|
|
|
|
[
|
|
|
|
|
backgrounds,
|
|
|
|
|
isColorBackground,
|
|
|
|
|
currentBackgroundColor,
|
|
|
|
|
handleColorSelect,
|
|
|
|
|
useBackgroundColor,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
BackgroundPreviews.displayName = "BackgroundPreviews";
|
|
|
|
|
|
2025-08-02 01:41:46 +02:00
|
|
|
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) => (
|
|
|
|
|
<BlurPreview
|
|
|
|
|
key={blur.value}
|
|
|
|
|
blur={blur}
|
|
|
|
|
isSelected={isBlurBackground && currentBlurIntensity === blur.value}
|
|
|
|
|
onSelect={() => handleBlurSelect(blur.value)}
|
|
|
|
|
/>
|
|
|
|
|
)),
|
|
|
|
|
[blurLevels, isBlurBackground, currentBlurIntensity, handleBlurSelect]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
2025-08-05 08:51:27 +02:00
|
|
|
<div className="flex flex-col gap-4">
|
2025-08-02 16:09:20 +02:00
|
|
|
<PropertyGroup title="Blur" defaultExpanded={false}>
|
2025-08-02 12:21:24 +02:00
|
|
|
<div className="grid grid-cols-4 gap-2 w-full">{blurPreviews}</div>
|
|
|
|
|
</PropertyGroup>
|
|
|
|
|
|
2025-08-02 16:09:20 +02:00
|
|
|
<PropertyGroup title="Colors" defaultExpanded={false}>
|
2025-08-02 12:21:24 +02:00
|
|
|
<div className="grid grid-cols-4 gap-2 w-full">
|
|
|
|
|
<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" />
|
2025-08-02 01:41:46 +02:00
|
|
|
</div>
|
2025-08-02 16:09:20 +02:00
|
|
|
<BackgroundPreviews
|
|
|
|
|
backgrounds={colors}
|
|
|
|
|
currentBackgroundColor={currentBackgroundColor}
|
|
|
|
|
isColorBackground={isColorBackground}
|
|
|
|
|
handleColorSelect={handleColorSelect}
|
|
|
|
|
useBackgroundColor={true}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</PropertyGroup>
|
|
|
|
|
|
|
|
|
|
<PropertyGroup title="Pattern Craft" defaultExpanded={false}>
|
|
|
|
|
<div className="grid grid-cols-4 gap-2 w-full">
|
|
|
|
|
<BackgroundPreviews
|
|
|
|
|
backgrounds={patternCraftGradients}
|
|
|
|
|
currentBackgroundColor={currentBackgroundColor}
|
|
|
|
|
isColorBackground={isColorBackground}
|
|
|
|
|
handleColorSelect={handleColorSelect}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</PropertyGroup>
|
|
|
|
|
|
|
|
|
|
<PropertyGroup title="Syntax UI" defaultExpanded={false}>
|
|
|
|
|
<div className="grid grid-cols-4 gap-2 w-full">
|
|
|
|
|
<BackgroundPreviews
|
|
|
|
|
backgrounds={syntaxUIGradients}
|
|
|
|
|
currentBackgroundColor={currentBackgroundColor}
|
|
|
|
|
isColorBackground={isColorBackground}
|
|
|
|
|
handleColorSelect={handleColorSelect}
|
|
|
|
|
/>
|
2025-08-02 12:21:24 +02:00
|
|
|
</div>
|
|
|
|
|
</PropertyGroup>
|
2025-08-02 01:41:46 +02:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|