♻️ refactor!: massive refactor

This commit is contained in:
2026-05-31 03:04:30 +07:00
parent c1f12c7201
commit df3c944547
86 changed files with 3691 additions and 1129 deletions
+94 -49
View File
@@ -13,8 +13,17 @@ import { getToolUiDefinition } from "../lib/tools/registry";
import { getToolDefinition } from "@pien-studio/editor-core";
import { useCanvasInteractions } from "../hooks/use-canvas-interactions";
import { useTranslations } from "../hooks/use-translations";
import { createStroke, paintSegment, type BrushStroke, type BrushOptions } from "../lib/brush-painter";
import type { Layer, LayerEffect } from "@pien-studio/types";
import {
createStroke,
paintSegment,
type BrushStroke,
type BrushOptions,
} from "../lib/brush-painter";
import {
getLayerRuntimeSource,
type Layer,
type LayerEffect,
} from "@pien-studio/types";
interface CanvasRendererProps {
layers: Layer[];
@@ -50,7 +59,15 @@ interface CanvasRendererProps {
} | null;
}
function BrushOverlayCanvas({ stroke, width, height }: { stroke: HTMLCanvasElement; width: number; height: number }) {
function BrushOverlayCanvas({
stroke,
width,
height,
}: {
stroke: HTMLCanvasElement;
width: number;
height: number;
}) {
const canvasRef = React.useRef<HTMLCanvasElement | null>(null);
React.useEffect(() => {
@@ -87,6 +104,7 @@ function EffectImageLayer({
const imageRef = React.useRef<HTMLImageElement | null>(null);
const activeEffects = effectsOverride ?? layer.effects;
const sourceUri = getLayerRuntimeSource(layer);
const draw = React.useCallback(() => {
const canvas = canvasRef.current;
@@ -95,11 +113,17 @@ function EffectImageLayer({
const ctx = canvas.getContext("2d");
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
renderLayerWithEffects(ctx, image, activeEffects, canvas.width, canvas.height);
renderLayerWithEffects(
ctx,
image,
activeEffects,
canvas.width,
canvas.height,
);
}, [activeEffects]);
React.useEffect(() => {
if (!layer.sourceUri) return;
if (!sourceUri) return;
let canceled = false;
const image = new Image();
image.crossOrigin = "anonymous";
@@ -108,11 +132,11 @@ function EffectImageLayer({
imageRef.current = image;
draw();
};
image.src = layer.sourceUri;
image.src = sourceUri;
return () => {
canceled = true;
};
}, [draw, layer.sourceUri]);
}, [draw, sourceUri]);
React.useEffect(() => {
draw();
@@ -156,32 +180,51 @@ export function CanvasRenderer({
const brushStrokeRef = React.useRef<BrushStroke | null>(null);
const brushLastPosRef = React.useRef<{ x: number; y: number } | null>(null);
const [brushOverlay, setBrushOverlay] = React.useState<{ layerId: string; canvas: HTMLCanvasElement } | null>(null);
const [brushOverlay, setBrushOverlay] = React.useState<{
layerId: string;
canvas: HTMLCanvasElement;
} | null>(null);
const handleBrushStrokeStart = React.useCallback((layerId: string, x: number, y: number, layerWidth: number, layerHeight: number): void => {
if (!brushOptions) return;
const stroke = createStroke(layerWidth, layerHeight);
brushStrokeRef.current = stroke;
brushLastPosRef.current = { x, y };
paintSegment(stroke, x, y, x, y, brushOptions);
setBrushOverlay({ layerId, canvas: stroke.canvas });
}, [brushOptions]);
const handleBrushStrokeStart = React.useCallback(
(
layerId: string,
x: number,
y: number,
layerWidth: number,
layerHeight: number,
): void => {
if (!brushOptions) return;
const stroke = createStroke(layerWidth, layerHeight);
brushStrokeRef.current = stroke;
brushLastPosRef.current = { x, y };
paintSegment(stroke, x, y, x, y, brushOptions);
setBrushOverlay({ layerId, canvas: stroke.canvas });
},
[brushOptions],
);
const handleBrushStrokeMove = React.useCallback((_layerId: string, x: number, y: number) => {
if (!brushStrokeRef.current || !brushLastPosRef.current || !brushOptions) return;
const { x: lx, y: ly } = brushLastPosRef.current;
paintSegment(brushStrokeRef.current, lx, ly, x, y, brushOptions);
brushLastPosRef.current = { x, y };
setBrushOverlay((prev) => prev ? { ...prev } : prev);
}, [brushOptions]);
const handleBrushStrokeMove = React.useCallback(
(_layerId: string, x: number, y: number) => {
if (!brushStrokeRef.current || !brushLastPosRef.current || !brushOptions)
return;
const { x: lx, y: ly } = brushLastPosRef.current;
paintSegment(brushStrokeRef.current, lx, ly, x, y, brushOptions);
brushLastPosRef.current = { x, y };
setBrushOverlay((prev) => (prev ? { ...prev } : prev));
},
[brushOptions],
);
const handleBrushStrokeEnd = React.useCallback((layerId: string) => {
const stroke = brushStrokeRef.current;
brushStrokeRef.current = null;
brushLastPosRef.current = null;
setBrushOverlay(null);
if (stroke && onBrushCommit) onBrushCommit(layerId, stroke);
}, [onBrushCommit]);
const handleBrushStrokeEnd = React.useCallback(
(layerId: string) => {
const stroke = brushStrokeRef.current;
brushStrokeRef.current = null;
brushLastPosRef.current = null;
setBrushOverlay(null);
if (stroke && onBrushCommit) onBrushCommit(layerId, stroke);
},
[onBrushCommit],
);
const {
containerRef,
@@ -226,13 +269,7 @@ export function CanvasRenderer({
faceDetections,
viewport,
);
}, [
faceDetections,
faceOverlayLayerId,
layers,
tool,
viewport,
]);
}, [faceDetections, faceOverlayLayerId, layers, tool, viewport]);
return (
<div
@@ -243,10 +280,16 @@ export function CanvasRenderer({
height: "100%",
touchAction: "none",
userSelect: "none",
cursor: isSpacePan ? "grab" : (getToolUiDefinition(tool)?.cursor ?? "default"),
cursor: isSpacePan
? "grab"
: (getToolUiDefinition(tool)?.cursor ?? "default"),
}}
onPointerDown={(e) => {
if (getToolDefinition(tool)?.interactionMode === "select" && e.button === 0) onSelectLayer(null);
if (
getToolDefinition(tool)?.interactionMode === "select" &&
e.button === 0
)
onSelectLayer(null);
onContainerPointerDown(e);
}}
onMouseDown={(e) => e.preventDefault()}
@@ -282,12 +325,9 @@ export function CanvasRenderer({
{layers.map((layer, idx) => {
const isSelected = layer.id === selectedLayerId;
const isImage = layer.type === "raster";
const layerWidth =
layer.width ??
(isImage ? Math.round(200 * layer.scale) : undefined);
const layerHeight =
layer.height ??
(isImage ? Math.round(150 * layer.scale) : undefined);
const sourceUri = getLayerRuntimeSource(layer);
const layerWidth = layer.width;
const layerHeight = layer.height;
const handleSize = CANVAS_HANDLE_BASE_SIZE / viewport.scale;
const handleSizePx = `${handleSize}px`;
const largeHandleSize =
@@ -319,8 +359,9 @@ export function CanvasRenderer({
onPointerDown={(e) => onLayerPointerDown(e, layer)}
onClick={() => onSelectLayer(layer.id)}
>
{isImage && layer.sourceUri ? (
layer.effects.length > 0 || (faceBlurPreview && faceBlurPreview.layerId === layer.id) ? (
{isImage && sourceUri ? (
layer.effects.length > 0 ||
(faceBlurPreview && faceBlurPreview.layerId === layer.id) ? (
<EffectImageLayer
layer={layer}
width={layerWidth ?? 1}
@@ -333,7 +374,7 @@ export function CanvasRenderer({
/>
) : (
<NextImage
src={layer.sourceUri}
src={sourceUri}
alt={layer.name ?? t("editor.layer")}
width={layerWidth ?? 1}
height={layerHeight ?? 1}
@@ -356,7 +397,11 @@ export function CanvasRenderer({
</div>
)}
{brushOverlay && brushOverlay.layerId === layer.id ? (
<BrushOverlayCanvas stroke={brushOverlay.canvas} width={layerWidth ?? 1} height={layerHeight ?? 1} />
<BrushOverlayCanvas
stroke={brushOverlay.canvas}
width={layerWidth ?? 1}
height={layerHeight ?? 1}
/>
) : null}
{tool === "face" && faceOverlayLayerId === layer.id
? faceDetections.map((face, index) => (