Files
Pien-Studio/apps/web/components/editor/editor-sidebar.tsx
T

335 lines
11 KiB
TypeScript
Raw Normal View History

2026-05-10 03:06:54 +07:00
import React from "react";
2026-05-31 03:03:49 +07:00
import type {
FaceBlurMethod,
Layer,
LayerEffect,
Project,
} from "@pien-studio/types";
2026-05-10 03:06:54 +07:00
import { FacePanel } from "./face-panel";
import { HistoryPanel } from "./history-panel";
import { LayersPanel } from "./layers-panel";
2026-05-31 03:03:49 +07:00
import type {
FaceDetectionOverlay,
FacePreview,
} from "../../hooks/use-face-detection";
2026-05-10 03:06:54 +07:00
type EditorSidebarProps = {
isDark: boolean;
2026-05-21 00:02:12 +07:00
tool: string;
fillColor?: string;
fillTolerance?: number;
onSetFillColor?: (color: string) => void;
onSetFillTolerance?: (tolerance: number) => void;
onCreateFillLayer?: () => void;
brushColor?: string;
brushSize?: number;
brushOpacity?: number;
brushHardness?: number;
onSetBrushColor?: (color: string) => void;
onSetBrushSize?: (size: number) => void;
onSetBrushOpacity?: (opacity: number) => void;
onSetBrushHardness?: (hardness: number) => void;
onAddLayer?: () => void;
2026-05-10 03:06:54 +07:00
layers: Layer[];
selectedLayerId: string | null;
selectedLayer: Layer | null;
history: { past: Project[]; future: Project[] };
canUndo: boolean;
canRedo: boolean;
faceDetections: FaceDetectionOverlay[];
facePreviews: FacePreview[];
faceStatus: "idle" | "detecting" | "unsupported";
blurMethod: FaceBlurMethod;
blurAmount: number;
censorColor: string;
selectedFaceIndices: number[];
onSelectLayer: (layerId: string | null) => void;
2026-05-21 00:02:12 +07:00
onSetLayerVisible: (layerId: string, visible: boolean) => void;
2026-05-31 03:03:49 +07:00
onSetEffectEnabled: (
layerId: string,
kind: LayerEffect["kind"],
enabled: boolean,
) => void;
2026-05-10 03:06:54 +07:00
onMoveLayerOrder: (direction: "up" | "down") => void;
onRemoveSelectedLayer: () => void;
onUndo: () => void;
onRedo: () => void;
onJumpToPast: (idx: number) => void;
onJumpToFuture: (idx: number) => void;
onSetBlurMethod: (method: FaceBlurMethod) => void;
onSetBlurAmount: (amount: number) => void;
onSetCensorColor: (color: string) => void;
onToggleFaceIndex: (index: number) => void;
onBlur: (indices: number[]) => void;
onClearBlur: () => void;
};
export function EditorSidebar({
isDark,
tool,
2026-05-21 00:02:12 +07:00
fillColor,
fillTolerance,
onSetFillColor,
onSetFillTolerance,
onCreateFillLayer,
brushColor,
brushSize,
brushOpacity,
brushHardness,
onSetBrushColor,
onSetBrushSize,
onSetBrushOpacity,
onSetBrushHardness,
onAddLayer,
2026-05-10 03:06:54 +07:00
layers,
selectedLayerId,
selectedLayer,
history,
canUndo,
canRedo,
faceDetections,
facePreviews,
faceStatus,
blurMethod,
blurAmount,
censorColor,
selectedFaceIndices,
onSelectLayer,
2026-05-21 00:02:12 +07:00
onSetLayerVisible,
onSetEffectEnabled,
2026-05-10 03:06:54 +07:00
onMoveLayerOrder,
onRemoveSelectedLayer,
onUndo,
onRedo,
onJumpToPast,
onJumpToFuture,
onSetBlurMethod,
onSetBlurAmount,
onSetCensorColor,
onToggleFaceIndex,
onBlur,
onClearBlur,
}: EditorSidebarProps) {
return (
2026-05-31 03:03:49 +07:00
<aside
className={`border-l p-3 ${isDark ? "border-white/10 bg-[#24262a]" : "border-black/10 bg-[#eceff3]"}`}
>
2026-05-10 03:06:54 +07:00
<div className="space-y-3">
<LayersPanel
layers={layers}
selectedLayerId={selectedLayerId}
isDark={isDark}
onSelectLayer={(layerId) => onSelectLayer(layerId)}
2026-05-21 00:02:12 +07:00
onSetLayerVisible={onSetLayerVisible}
onSetEffectEnabled={onSetEffectEnabled}
2026-05-10 03:06:54 +07:00
onMoveLayerOrder={onMoveLayerOrder}
onRemoveSelectedLayer={onRemoveSelectedLayer}
2026-05-21 00:02:12 +07:00
onAddLayer={onAddLayer}
2026-05-10 03:06:54 +07:00
/>
2026-05-21 00:02:12 +07:00
{tool === "fill" && onSetFillColor && onSetFillTolerance ? (
2026-05-31 03:03:49 +07:00
<div
className={`rounded-lg border p-3 ${isDark ? "border-white/10 bg-[#2d3036]" : "border-black/10 bg-white"}`}
>
<p
className={`mb-2 text-xs font-semibold uppercase tracking-wider ${isDark ? "text-[#8b9ab1]" : "text-[#6b7280]"}`}
>
2026-05-21 00:02:12 +07:00
Fill
</p>
<div className="flex items-center gap-2 mb-3">
2026-05-31 03:03:49 +07:00
<label
className={`text-xs ${isDark ? "text-[#d7dae0]" : "text-[#1f2430]"}`}
>
Color
</label>
2026-05-21 00:02:12 +07:00
<input
type="color"
value={fillColor ?? "#ff0000"}
onChange={(e) => onSetFillColor(e.target.value)}
className="h-7 w-10 cursor-pointer rounded border border-black/10 p-0.5"
/>
2026-05-31 03:03:49 +07:00
<span
className={`text-xs font-mono ${isDark ? "text-[#d7dae0]" : "text-[#1f2430]"}`}
>
{fillColor ?? "#ff0000"}
</span>
2026-05-21 00:02:12 +07:00
</div>
<div className="flex flex-col gap-1">
<div className="flex items-center justify-between">
2026-05-31 03:03:49 +07:00
<label
className={`text-xs ${isDark ? "text-[#d7dae0]" : "text-[#1f2430]"}`}
>
Tolerance
</label>
<span
className={`text-xs font-mono ${isDark ? "text-[#8b9ab1]" : "text-[#6b7280]"}`}
>
{fillTolerance ?? 32}
</span>
2026-05-21 00:02:12 +07:00
</div>
<input
type="range"
min={0}
max={255}
value={fillTolerance ?? 32}
onChange={(e) => onSetFillTolerance(Number(e.target.value))}
className="w-full accent-[var(--color-accent-strong)]"
/>
</div>
2026-05-24 21:05:21 +07:00
{onCreateFillLayer ? (
<button
type="button"
onClick={onCreateFillLayer}
className={`mt-3 w-full rounded border px-3 py-2 text-xs font-semibold ${isDark ? "border-white/10 bg-[#353942] text-[#f0f3f8] hover:bg-[#3d424d]" : "border-black/10 bg-[#f4f6f9] text-[#1f2430] hover:bg-white"}`}
>
Create fill layer
</button>
) : null}
2026-05-21 00:02:12 +07:00
</div>
) : null}
2026-05-31 03:03:49 +07:00
{tool === "brush" &&
onSetBrushColor &&
onSetBrushSize &&
onSetBrushOpacity &&
onSetBrushHardness ? (
<div
className={`rounded-lg border p-3 ${isDark ? "border-white/10 bg-[#2d3036]" : "border-black/10 bg-white"}`}
>
<p
className={`mb-2 text-xs font-semibold uppercase tracking-wider ${isDark ? "text-[#8b9ab1]" : "text-[#6b7280]"}`}
>
2026-05-21 00:02:12 +07:00
Brush
</p>
<div className="flex items-center gap-2 mb-3">
2026-05-31 03:03:49 +07:00
<label
className={`text-xs ${isDark ? "text-[#d7dae0]" : "text-[#1f2430]"}`}
>
Color
</label>
2026-05-21 00:02:12 +07:00
<input
type="color"
value={brushColor ?? "#000000"}
onChange={(e) => onSetBrushColor(e.target.value)}
className="h-7 w-10 cursor-pointer rounded border border-black/10 p-0.5"
/>
2026-05-31 03:03:49 +07:00
<span
className={`text-xs font-mono ${isDark ? "text-[#d7dae0]" : "text-[#1f2430]"}`}
>
{brushColor ?? "#000000"}
</span>
2026-05-21 00:02:12 +07:00
</div>
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-1">
<div className="flex items-center justify-between">
2026-05-31 03:03:49 +07:00
<label
className={`text-xs ${isDark ? "text-[#d7dae0]" : "text-[#1f2430]"}`}
>
Size
</label>
<span
className={`text-xs font-mono ${isDark ? "text-[#8b9ab1]" : "text-[#6b7280]"}`}
>
{brushSize ?? 20}px
</span>
2026-05-21 00:02:12 +07:00
</div>
2026-05-31 03:03:49 +07:00
<input
type="range"
min={1}
max={200}
value={brushSize ?? 20}
onChange={(e) => onSetBrushSize(Number(e.target.value))}
className="w-full accent-[var(--color-accent-strong)]"
/>
2026-05-21 00:02:12 +07:00
</div>
<div className="flex flex-col gap-1">
<div className="flex items-center justify-between">
2026-05-31 03:03:49 +07:00
<label
className={`text-xs ${isDark ? "text-[#d7dae0]" : "text-[#1f2430]"}`}
>
Opacity
</label>
<span
className={`text-xs font-mono ${isDark ? "text-[#8b9ab1]" : "text-[#6b7280]"}`}
>
{Math.round((brushOpacity ?? 1) * 100)}%
</span>
2026-05-21 00:02:12 +07:00
</div>
2026-05-31 03:03:49 +07:00
<input
type="range"
min={0}
max={100}
value={Math.round((brushOpacity ?? 1) * 100)}
onChange={(e) =>
onSetBrushOpacity(Number(e.target.value) / 100)
}
className="w-full accent-[var(--color-accent-strong)]"
/>
2026-05-21 00:02:12 +07:00
</div>
<div className="flex flex-col gap-1">
<div className="flex items-center justify-between">
2026-05-31 03:03:49 +07:00
<label
className={`text-xs ${isDark ? "text-[#d7dae0]" : "text-[#1f2430]"}`}
>
Hardness
</label>
<span
className={`text-xs font-mono ${isDark ? "text-[#8b9ab1]" : "text-[#6b7280]"}`}
>
{Math.round((brushHardness ?? 0.8) * 100)}%
</span>
2026-05-21 00:02:12 +07:00
</div>
2026-05-31 03:03:49 +07:00
<input
type="range"
min={0}
max={100}
value={Math.round((brushHardness ?? 0.8) * 100)}
onChange={(e) =>
onSetBrushHardness(Number(e.target.value) / 100)
}
className="w-full accent-[var(--color-accent-strong)]"
/>
2026-05-21 00:02:12 +07:00
</div>
</div>
</div>
) : null}
2026-05-10 03:06:54 +07:00
{tool === "face" ? (
<FacePanel
isDark={isDark}
selectedLayer={selectedLayer}
faceDetections={faceDetections}
facePreviews={facePreviews}
faceStatus={faceStatus}
blurMethod={blurMethod}
blurAmount={blurAmount}
censorColor={censorColor}
selectedFaceIndices={selectedFaceIndices}
2026-05-31 03:03:49 +07:00
hasActiveBlur={Boolean(
selectedLayer &&
selectedLayer.effects.some((e) => e.kind === "face-blur"),
)}
2026-05-10 03:06:54 +07:00
onSetBlurMethod={onSetBlurMethod}
onSetBlurAmount={onSetBlurAmount}
onSetCensorColor={onSetCensorColor}
onToggleFaceIndex={onToggleFaceIndex}
onBlur={onBlur}
onClearBlur={onClearBlur}
/>
) : null}
<HistoryPanel
history={history}
isDark={isDark}
canUndo={canUndo}
canRedo={canRedo}
onUndo={onUndo}
onRedo={onRedo}
onJumpToPast={onJumpToPast}
onJumpToFuture={onJumpToFuture}
/>
</div>
</aside>
);
}