@pixerate/editor-react 0.74.0 → 0.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/paint-editor/index.cjs +216 -19
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +31 -6
- package/dist/paint-editor/index.d.ts +31 -6
- package/dist/paint-editor/index.js +244 -34
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/vector/index.cjs +298 -174
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +22 -1
- package/dist/vector/index.d.ts +22 -1
- package/dist/vector/index.js +282 -160
- package/dist/vector/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -72,8 +72,9 @@ interface PaintToolbarProps {
|
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
74
|
* Tools (brush, eyedropper, pan, rotate view), undo / redo, view rotation, mirroring and reset,
|
|
75
|
-
* the symmetry and perspective guides, the tracing image and reference images, the drawing hand,
|
|
76
|
-
* and file actions: save / open JSON, export PNG, JPEG and PSD
|
|
75
|
+
* the symmetry and perspective guides, stencils (ruler, circle, mask), the tracing image and reference images, the drawing hand,
|
|
76
|
+
* and file actions: save / open JSON, export PNG, JPEG and PSD, a time-lapse (zipped frames, video
|
|
77
|
+
* project and ffmpeg command) and a PNG repainted from the strokes at twice the size.
|
|
77
78
|
*/
|
|
78
79
|
declare const PaintToolbar: React__default.FC<PaintToolbarProps>;
|
|
79
80
|
|
|
@@ -100,8 +101,9 @@ interface PaperPanelProps {
|
|
|
100
101
|
}
|
|
101
102
|
/**
|
|
102
103
|
* The paper library by family (each a generated grain preview) and the current paper's colour,
|
|
103
|
-
* grain depth, scale, shading and contrast,
|
|
104
|
-
*
|
|
104
|
+
* grain depth, scale, shading and contrast, the wet-media properties and metallic sheen (each
|
|
105
|
+
* change one undo step; sliders commit on release); then the light for thick paint and metallic
|
|
106
|
+
* paper (not an undo step) and Flatten relief for the active layer.
|
|
105
107
|
*/
|
|
106
108
|
declare const PaperPanel: React__default.FC<PaperPanelProps>;
|
|
107
109
|
|
|
@@ -132,6 +134,29 @@ interface PaintLayersPanelProps {
|
|
|
132
134
|
*/
|
|
133
135
|
declare const PaintLayersPanel: React__default.FC<PaintLayersPanelProps>;
|
|
134
136
|
|
|
137
|
+
interface WetPanelProps {
|
|
138
|
+
controller: PaintEditorController;
|
|
139
|
+
className?: string;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Wet media: whether paint is wet, Dry now, Pause / Resume, the water overlay and speed; Wet paper
|
|
143
|
+
* on the active layer; canvas tilt; the active layer's wet settings (viscosity, evaporation, edge
|
|
144
|
+
* darkening, granulation, blooms, bleed, pigment weight, lifting, staining) and flow map; and the
|
|
145
|
+
* kernel in use, with a note when it runs slower than it could (no cross-origin isolation).
|
|
146
|
+
*/
|
|
147
|
+
declare const WetPanel: React__default.FC<WetPanelProps>;
|
|
148
|
+
|
|
149
|
+
interface StickerPanelProps {
|
|
150
|
+
controller: PaintEditorController;
|
|
151
|
+
className?: string;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The active layer's stickers: pick one to change its stamp, size, rotation, opacity and colour,
|
|
155
|
+
* bring it to the front or back, or delete it (each change one undo step). Shows nothing for a
|
|
156
|
+
* layer without stickers.
|
|
157
|
+
*/
|
|
158
|
+
declare const StickerPanel: React__default.FC<StickerPanelProps>;
|
|
159
|
+
|
|
135
160
|
interface PaintEditorProps {
|
|
136
161
|
controller: PaintEditorController;
|
|
137
162
|
/** Base name of saved and exported files (default `painting`). */
|
|
@@ -144,7 +169,7 @@ interface PaintEditorProps {
|
|
|
144
169
|
}
|
|
145
170
|
/**
|
|
146
171
|
* The paint editor: toolbar, brush library on the left, the canvas, and colour (wheel,
|
|
147
|
-
* harmonies, swatches, mixing pad), paper and layers on the right.
|
|
172
|
+
* harmonies, swatches, mixing pad), wet media, paper and layers on the right.
|
|
148
173
|
*/
|
|
149
174
|
declare const PaintEditor: React__default.FC<PaintEditorProps>;
|
|
150
175
|
|
|
@@ -159,4 +184,4 @@ declare const PaintSlider: React__default.FC<{
|
|
|
159
184
|
onCommit?: (v: number) => void;
|
|
160
185
|
}>;
|
|
161
186
|
|
|
162
|
-
export { BrushPanel, type BrushPanelProps, ColorPanel, type ColorPanelProps, PaintCanvas, type PaintCanvasProps, PaintEditor, type PaintEditorProps, PaintLayersPanel, type PaintLayersPanelProps, PaintPresenceLayer, PaintSlider, PaintToolbar, type PaintToolbarProps, PaperPanel, type PaperPanelProps, type UsePaintEditorReturn, usePaintDirector, usePaintEditor, usePaintEditorState };
|
|
187
|
+
export { BrushPanel, type BrushPanelProps, ColorPanel, type ColorPanelProps, PaintCanvas, type PaintCanvasProps, PaintEditor, type PaintEditorProps, PaintLayersPanel, type PaintLayersPanelProps, PaintPresenceLayer, PaintSlider, PaintToolbar, type PaintToolbarProps, PaperPanel, type PaperPanelProps, StickerPanel, type StickerPanelProps, type UsePaintEditorReturn, WetPanel, type WetPanelProps, usePaintDirector, usePaintEditor, usePaintEditorState };
|
|
@@ -72,8 +72,9 @@ interface PaintToolbarProps {
|
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
74
|
* Tools (brush, eyedropper, pan, rotate view), undo / redo, view rotation, mirroring and reset,
|
|
75
|
-
* the symmetry and perspective guides, the tracing image and reference images, the drawing hand,
|
|
76
|
-
* and file actions: save / open JSON, export PNG, JPEG and PSD
|
|
75
|
+
* the symmetry and perspective guides, stencils (ruler, circle, mask), the tracing image and reference images, the drawing hand,
|
|
76
|
+
* and file actions: save / open JSON, export PNG, JPEG and PSD, a time-lapse (zipped frames, video
|
|
77
|
+
* project and ffmpeg command) and a PNG repainted from the strokes at twice the size.
|
|
77
78
|
*/
|
|
78
79
|
declare const PaintToolbar: React__default.FC<PaintToolbarProps>;
|
|
79
80
|
|
|
@@ -100,8 +101,9 @@ interface PaperPanelProps {
|
|
|
100
101
|
}
|
|
101
102
|
/**
|
|
102
103
|
* The paper library by family (each a generated grain preview) and the current paper's colour,
|
|
103
|
-
* grain depth, scale, shading and contrast,
|
|
104
|
-
*
|
|
104
|
+
* grain depth, scale, shading and contrast, the wet-media properties and metallic sheen (each
|
|
105
|
+
* change one undo step; sliders commit on release); then the light for thick paint and metallic
|
|
106
|
+
* paper (not an undo step) and Flatten relief for the active layer.
|
|
105
107
|
*/
|
|
106
108
|
declare const PaperPanel: React__default.FC<PaperPanelProps>;
|
|
107
109
|
|
|
@@ -132,6 +134,29 @@ interface PaintLayersPanelProps {
|
|
|
132
134
|
*/
|
|
133
135
|
declare const PaintLayersPanel: React__default.FC<PaintLayersPanelProps>;
|
|
134
136
|
|
|
137
|
+
interface WetPanelProps {
|
|
138
|
+
controller: PaintEditorController;
|
|
139
|
+
className?: string;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Wet media: whether paint is wet, Dry now, Pause / Resume, the water overlay and speed; Wet paper
|
|
143
|
+
* on the active layer; canvas tilt; the active layer's wet settings (viscosity, evaporation, edge
|
|
144
|
+
* darkening, granulation, blooms, bleed, pigment weight, lifting, staining) and flow map; and the
|
|
145
|
+
* kernel in use, with a note when it runs slower than it could (no cross-origin isolation).
|
|
146
|
+
*/
|
|
147
|
+
declare const WetPanel: React__default.FC<WetPanelProps>;
|
|
148
|
+
|
|
149
|
+
interface StickerPanelProps {
|
|
150
|
+
controller: PaintEditorController;
|
|
151
|
+
className?: string;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The active layer's stickers: pick one to change its stamp, size, rotation, opacity and colour,
|
|
155
|
+
* bring it to the front or back, or delete it (each change one undo step). Shows nothing for a
|
|
156
|
+
* layer without stickers.
|
|
157
|
+
*/
|
|
158
|
+
declare const StickerPanel: React__default.FC<StickerPanelProps>;
|
|
159
|
+
|
|
135
160
|
interface PaintEditorProps {
|
|
136
161
|
controller: PaintEditorController;
|
|
137
162
|
/** Base name of saved and exported files (default `painting`). */
|
|
@@ -144,7 +169,7 @@ interface PaintEditorProps {
|
|
|
144
169
|
}
|
|
145
170
|
/**
|
|
146
171
|
* The paint editor: toolbar, brush library on the left, the canvas, and colour (wheel,
|
|
147
|
-
* harmonies, swatches, mixing pad), paper and layers on the right.
|
|
172
|
+
* harmonies, swatches, mixing pad), wet media, paper and layers on the right.
|
|
148
173
|
*/
|
|
149
174
|
declare const PaintEditor: React__default.FC<PaintEditorProps>;
|
|
150
175
|
|
|
@@ -159,4 +184,4 @@ declare const PaintSlider: React__default.FC<{
|
|
|
159
184
|
onCommit?: (v: number) => void;
|
|
160
185
|
}>;
|
|
161
186
|
|
|
162
|
-
export { BrushPanel, type BrushPanelProps, ColorPanel, type ColorPanelProps, PaintCanvas, type PaintCanvasProps, PaintEditor, type PaintEditorProps, PaintLayersPanel, type PaintLayersPanelProps, PaintPresenceLayer, PaintSlider, PaintToolbar, type PaintToolbarProps, PaperPanel, type PaperPanelProps, type UsePaintEditorReturn, usePaintDirector, usePaintEditor, usePaintEditorState };
|
|
187
|
+
export { BrushPanel, type BrushPanelProps, ColorPanel, type ColorPanelProps, PaintCanvas, type PaintCanvasProps, PaintEditor, type PaintEditorProps, PaintLayersPanel, type PaintLayersPanelProps, PaintPresenceLayer, PaintSlider, PaintToolbar, type PaintToolbarProps, PaperPanel, type PaperPanelProps, StickerPanel, type StickerPanelProps, type UsePaintEditorReturn, WetPanel, type WetPanelProps, usePaintDirector, usePaintEditor, usePaintEditorState };
|
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
PaintCanvasPresenter,
|
|
28
28
|
PaintGesture,
|
|
29
29
|
documentToScreen,
|
|
30
|
+
drawWaterOverlay,
|
|
31
|
+
startWetClock,
|
|
30
32
|
fitZoom,
|
|
31
33
|
guideOverlay,
|
|
32
34
|
handlePaintShortcut,
|
|
@@ -37,6 +39,7 @@ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount =
|
|
|
37
39
|
const state = usePaintEditorState(controller);
|
|
38
40
|
const viewport = useRef(null);
|
|
39
41
|
const canvas = useRef(null);
|
|
42
|
+
const water = useRef(null);
|
|
40
43
|
const [size, setSize] = useState2({ width: 800, height: 600 });
|
|
41
44
|
const measured = useRef(false);
|
|
42
45
|
const [predicted, setPredicted] = useState2([]);
|
|
@@ -76,6 +79,11 @@ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount =
|
|
|
76
79
|
if (ctx && typeof ctx.createImageData === "function") presenter.render(ctx);
|
|
77
80
|
else controller.takeDirty();
|
|
78
81
|
}, [state, presenter, controller]);
|
|
82
|
+
useEffect(() => startWetClock(controller), [controller]);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const ctx = water.current?.getContext("2d");
|
|
85
|
+
if (ctx && typeof ctx.createImageData === "function") drawWaterOverlay(controller, ctx);
|
|
86
|
+
}, [state, controller]);
|
|
79
87
|
useEffect(() => {
|
|
80
88
|
const el = viewport.current;
|
|
81
89
|
if (!el) return;
|
|
@@ -130,6 +138,17 @@ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount =
|
|
|
130
138
|
style: { transform, transformOrigin: "0 0", imageRendering: state.view.zoom > 2 ? "pixelated" : "auto" }
|
|
131
139
|
}
|
|
132
140
|
),
|
|
141
|
+
state.wet.overlay && /* @__PURE__ */ jsx(
|
|
142
|
+
"canvas",
|
|
143
|
+
{
|
|
144
|
+
ref: water,
|
|
145
|
+
width: doc.width,
|
|
146
|
+
height: doc.height,
|
|
147
|
+
"data-testid": "paint-water-overlay",
|
|
148
|
+
className: "absolute left-0 top-0 pointer-events-none opacity-60",
|
|
149
|
+
style: { transform, transformOrigin: "0 0" }
|
|
150
|
+
}
|
|
151
|
+
),
|
|
133
152
|
/* @__PURE__ */ jsxs("svg", { className: "absolute left-0 top-0 pointer-events-none overflow-visible", width: doc.width, height: doc.height, style: { transform, transformOrigin: "0 0" }, "aria-hidden": "true", children: [
|
|
134
153
|
lines.map((l, i) => /* @__PURE__ */ jsx(
|
|
135
154
|
"line",
|
|
@@ -138,8 +157,8 @@ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount =
|
|
|
138
157
|
y1: l.from.y,
|
|
139
158
|
x2: l.to.x,
|
|
140
159
|
y2: l.to.y,
|
|
141
|
-
stroke: l.kind === "symmetry" ? "#22d3ee" : "#f472b6",
|
|
142
|
-
strokeOpacity: l.kind === "symmetry" ? 0.8 : 0.35,
|
|
160
|
+
stroke: l.kind === "symmetry" ? "#22d3ee" : l.kind === "stencil" ? "#fbbf24" : "#f472b6",
|
|
161
|
+
strokeOpacity: l.kind === "symmetry" || l.kind === "stencil" ? 0.8 : 0.35,
|
|
143
162
|
strokeWidth: 1 / state.view.zoom,
|
|
144
163
|
strokeDasharray: l.kind === "symmetry" ? `${6 / state.view.zoom} ${4 / state.view.zoom}` : void 0
|
|
145
164
|
},
|
|
@@ -225,8 +244,8 @@ var ReferencePanel = ({ controller, id }) => {
|
|
|
225
244
|
};
|
|
226
245
|
|
|
227
246
|
// src/paint-editor/PaintToolbar.tsx
|
|
228
|
-
import { useRef as useRef3 } from "react";
|
|
229
|
-
import { PAINT_TOOLS, SYMMETRY_MODES, decodeImageBlob, downloadBytes } from "@pixerate/editor/paint-editor";
|
|
247
|
+
import { useRef as useRef3, useState as useState4 } from "react";
|
|
248
|
+
import { PAINT_TOOLS, SYMMETRY_MODES, decodeImageBlob, downloadBytes, quickStencil, timeLapseZip } from "@pixerate/editor/paint-editor";
|
|
230
249
|
|
|
231
250
|
// src/paint-editor/shared.tsx
|
|
232
251
|
import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
|
|
@@ -323,6 +342,8 @@ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
|
323
342
|
var PaintToolbar = ({ controller, tool, onToolChange, fileName = "painting", className }) => {
|
|
324
343
|
const state = usePaintEditorState(controller);
|
|
325
344
|
const [error, attempt] = useAttempt();
|
|
345
|
+
const [busy, setBusy] = useState4(null);
|
|
346
|
+
const [note, setNote] = useState4(null);
|
|
326
347
|
const openRef = useRef3(null);
|
|
327
348
|
const tracingRef = useRef3(null);
|
|
328
349
|
const refRef = useRef3(null);
|
|
@@ -419,6 +440,12 @@ var PaintToolbar = ({ controller, tool, onToolChange, fileName = "painting", cla
|
|
|
419
440
|
/* @__PURE__ */ jsx3("button", { type: "button", className: button, onClick: () => controller.clearTracingImage(), children: "Remove tracing" })
|
|
420
441
|
] }),
|
|
421
442
|
/* @__PURE__ */ jsx3("button", { type: "button", className: button, onClick: () => refRef.current?.click(), children: "Reference\u2026" }),
|
|
443
|
+
/* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-1", role: "group", "aria-label": "Stencils", children: [
|
|
444
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: button, title: "A ruler: strokes that start near it follow it", onClick: () => attempt(() => controller.addStencil(quickStencil("ruler", canvas))), children: "Ruler" }),
|
|
445
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: button, title: "A circle guide: strokes that start near it follow it", onClick: () => attempt(() => controller.addStencil(quickStencil("circle", canvas))), children: "Circle" }),
|
|
446
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: button, title: "An ellipse mask: paint lands only inside it", onClick: () => attempt(() => controller.addStencil(quickStencil("ellipse-mask", canvas))), children: "Mask" }),
|
|
447
|
+
state.guides.stencils.length > 0 && /* @__PURE__ */ jsx3("button", { type: "button", className: button, onClick: () => attempt(() => controller.removeStencils()), children: "Clear stencils" })
|
|
448
|
+
] }),
|
|
422
449
|
/* @__PURE__ */ jsx3("button", { type: "button", className: button, title: "Which hand holds the pen (palm rejection)", onClick: () => controller.setHand(state.hand === "right" ? "left" : "right"), children: state.hand === "right" ? "Right-handed" : "Left-handed" }),
|
|
423
450
|
/* @__PURE__ */ jsx3("span", { className: "mx-1 h-4 w-px bg-slate-700" }),
|
|
424
451
|
/* @__PURE__ */ jsx3("button", { type: "button", className: button, onClick: () => attempt(() => downloadBytes(JSON.stringify(controller.toJSON()), `${fileName}.paint.json`, "application/json")), children: "Save" }),
|
|
@@ -426,6 +453,45 @@ var PaintToolbar = ({ controller, tool, onToolChange, fileName = "painting", cla
|
|
|
426
453
|
/* @__PURE__ */ jsx3("button", { type: "button", className: button, onClick: () => exportImage("image/png"), children: "PNG" }),
|
|
427
454
|
/* @__PURE__ */ jsx3("button", { type: "button", className: button, onClick: () => exportImage("image/jpeg"), children: "JPEG" }),
|
|
428
455
|
/* @__PURE__ */ jsx3("button", { type: "button", className: button, onClick: () => attempt(() => downloadBytes(controller.exportPsd().bytes, `${fileName}.psd`, "image/vnd.adobe.photoshop")), children: "PSD" }),
|
|
456
|
+
/* @__PURE__ */ jsx3(
|
|
457
|
+
"button",
|
|
458
|
+
{
|
|
459
|
+
type: "button",
|
|
460
|
+
className: button,
|
|
461
|
+
disabled: busy !== null,
|
|
462
|
+
title: "Repaint the painting from its strokes as a time-lapse: frames, a video project and the ffmpeg command, zipped",
|
|
463
|
+
onClick: () => attempt(async () => {
|
|
464
|
+
setBusy("Repainting\u2026");
|
|
465
|
+
try {
|
|
466
|
+
const tl = await controller.timeLapse({ onProgress: (p) => setBusy(`Repainting\u2026 ${Math.round(p * 100)}%`) });
|
|
467
|
+
downloadBytes(await timeLapseZip(tl), `${fileName}-timelapse.zip`, "application/zip");
|
|
468
|
+
setNote(`Time-lapse: ${tl.frames.length} frames, ${(tl.durationMs / 1e3).toFixed(1)} s (run render.sh in the zip for the MP4).`);
|
|
469
|
+
} finally {
|
|
470
|
+
setBusy(null);
|
|
471
|
+
}
|
|
472
|
+
}),
|
|
473
|
+
children: "Time-lapse"
|
|
474
|
+
}
|
|
475
|
+
),
|
|
476
|
+
/* @__PURE__ */ jsx3(
|
|
477
|
+
"button",
|
|
478
|
+
{
|
|
479
|
+
type: "button",
|
|
480
|
+
className: button,
|
|
481
|
+
disabled: busy !== null,
|
|
482
|
+
title: "Repaint the painting from its strokes at twice the size (more detail, not upscaled)",
|
|
483
|
+
onClick: () => attempt(async () => {
|
|
484
|
+
setBusy("Repainting at 2\xD7\u2026");
|
|
485
|
+
try {
|
|
486
|
+
downloadBytes(await controller.exportHighResPng({ scale: 2 }), `${fileName}@2x.png`, "image/png");
|
|
487
|
+
} finally {
|
|
488
|
+
setBusy(null);
|
|
489
|
+
}
|
|
490
|
+
}),
|
|
491
|
+
children: "PNG 2\xD7"
|
|
492
|
+
}
|
|
493
|
+
),
|
|
494
|
+
(busy ?? note) && /* @__PURE__ */ jsx3("span", { className: "text-[11px] text-slate-400", "data-testid": "paint-replay-status", children: busy ?? note }),
|
|
429
495
|
/* @__PURE__ */ jsx3(
|
|
430
496
|
"input",
|
|
431
497
|
{
|
|
@@ -476,7 +542,7 @@ var PaintToolbar = ({ controller, tool, onToolChange, fileName = "painting", cla
|
|
|
476
542
|
};
|
|
477
543
|
|
|
478
544
|
// src/paint-editor/BrushPanel.tsx
|
|
479
|
-
import { useMemo as useMemo3, useRef as useRef4, useState as
|
|
545
|
+
import { useMemo as useMemo3, useRef as useRef4, useState as useState5 } from "react";
|
|
480
546
|
import {
|
|
481
547
|
BLEND_MENU_GROUPS,
|
|
482
548
|
BRUSH_ROTATIONS,
|
|
@@ -487,13 +553,26 @@ import {
|
|
|
487
553
|
multiColorSliders,
|
|
488
554
|
patchBrushValue,
|
|
489
555
|
patchPath,
|
|
490
|
-
readPath
|
|
556
|
+
readPath,
|
|
557
|
+
WET_TOOLS,
|
|
558
|
+
WET_TOOL_LABELS,
|
|
559
|
+
WET_MEDIA,
|
|
560
|
+
WET_MEDIUM_LABELS,
|
|
561
|
+
IMPASTO_TOOLS,
|
|
562
|
+
IMPASTO_TOOL_LABELS,
|
|
563
|
+
PARTICLE_KINDS,
|
|
564
|
+
PARTICLE_KIND_LABELS,
|
|
565
|
+
PARTICLE_RENDERS,
|
|
566
|
+
PARTICLE_RENDER_LABELS,
|
|
567
|
+
STAMP_KINDS,
|
|
568
|
+
STAMP_LABELS,
|
|
569
|
+
toggleStamp
|
|
491
570
|
} from "@pixerate/editor/paint-editor";
|
|
492
571
|
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
493
572
|
var BrushPanel = ({ controller, thumbnailWidth = 112, thumbnailHeight = 32, className }) => {
|
|
494
573
|
const state = usePaintEditorState(controller);
|
|
495
574
|
const [error, attempt] = useAttempt();
|
|
496
|
-
const [saveName, setSaveName] =
|
|
575
|
+
const [saveName, setSaveName] = useState5("");
|
|
497
576
|
const fileRef = useRef4(null);
|
|
498
577
|
const { brush, brushes } = state;
|
|
499
578
|
const paper = state.document.paper;
|
|
@@ -546,6 +625,39 @@ var BrushPanel = ({ controller, thumbnailWidth = 112, thumbnailHeight = 32, clas
|
|
|
546
625
|
] }),
|
|
547
626
|
engine && /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-1.5", "data-testid": "brush-engine", children: [
|
|
548
627
|
/* @__PURE__ */ jsx4("span", { className: "font-medium text-slate-300", children: engine.title }),
|
|
628
|
+
engine.wetTool && /* @__PURE__ */ jsxs4("label", { className: "flex items-center justify-between gap-2", children: [
|
|
629
|
+
/* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "Wet tool" }),
|
|
630
|
+
/* @__PURE__ */ jsx4("select", { "aria-label": "Wet tool", value: brush.wet.tool, onChange: (e) => controller.setBrush({ wet: { tool: e.target.value } }), className: "rounded bg-slate-800 px-1 py-0.5", children: WET_TOOLS.map((t) => /* @__PURE__ */ jsx4("option", { value: t, children: WET_TOOL_LABELS[t] }, t)) })
|
|
631
|
+
] }),
|
|
632
|
+
engine.wetTool && /* @__PURE__ */ jsxs4("label", { className: "flex items-center justify-between gap-2", children: [
|
|
633
|
+
/* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "Medium" }),
|
|
634
|
+
/* @__PURE__ */ jsx4("select", { "aria-label": "Wet medium", value: brush.wet.medium, onChange: (e) => controller.setBrush({ wet: { medium: e.target.value } }), className: "rounded bg-slate-800 px-1 py-0.5", children: WET_MEDIA.map((m) => /* @__PURE__ */ jsx4("option", { value: m, children: WET_MEDIUM_LABELS[m] }, m)) })
|
|
635
|
+
] }),
|
|
636
|
+
engine.particleMenus && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
637
|
+
/* @__PURE__ */ jsxs4("label", { className: "flex items-center justify-between gap-2", children: [
|
|
638
|
+
/* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "Particles" }),
|
|
639
|
+
/* @__PURE__ */ jsx4("select", { "aria-label": "Particle kind", value: brush.particle.kind, onChange: (e) => controller.setBrush({ particle: { kind: e.target.value } }), className: "rounded bg-slate-800 px-1 py-0.5", children: PARTICLE_KINDS.map((k) => /* @__PURE__ */ jsx4("option", { value: k, children: PARTICLE_KIND_LABELS[k] }, k)) })
|
|
640
|
+
] }),
|
|
641
|
+
/* @__PURE__ */ jsxs4("label", { className: "flex items-center justify-between gap-2", children: [
|
|
642
|
+
/* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "Drawn as" }),
|
|
643
|
+
/* @__PURE__ */ jsx4("select", { "aria-label": "Particle render", value: brush.particle.render, onChange: (e) => controller.setBrush({ particle: { render: e.target.value } }), className: "rounded bg-slate-800 px-1 py-0.5", children: PARTICLE_RENDERS.map((k) => /* @__PURE__ */ jsx4("option", { value: k, children: PARTICLE_RENDER_LABELS[k] }, k)) })
|
|
644
|
+
] })
|
|
645
|
+
] }),
|
|
646
|
+
engine.stickerStamps && /* @__PURE__ */ jsx4("div", { className: "flex flex-wrap gap-1", role: "group", "aria-label": "Sticker stamps", children: STAMP_KINDS.map((k) => /* @__PURE__ */ jsx4(
|
|
647
|
+
"button",
|
|
648
|
+
{
|
|
649
|
+
type: "button",
|
|
650
|
+
"aria-pressed": brush.sticker.stamps.includes(k),
|
|
651
|
+
className: `px-1.5 py-0.5 rounded text-[10px] ${brush.sticker.stamps.includes(k) ? "bg-indigo-600 text-white" : "bg-slate-800 text-slate-300"}`,
|
|
652
|
+
onClick: () => controller.setBrush({ sticker: { stamps: toggleStamp(brush.sticker.stamps, k) } }),
|
|
653
|
+
children: STAMP_LABELS[k]
|
|
654
|
+
},
|
|
655
|
+
k
|
|
656
|
+
)) }),
|
|
657
|
+
engine.impastoTool && /* @__PURE__ */ jsxs4("label", { className: "flex items-center justify-between gap-2", children: [
|
|
658
|
+
/* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "Tool" }),
|
|
659
|
+
/* @__PURE__ */ jsx4("select", { "aria-label": "Impasto tool", value: brush.impasto.tool, onChange: (e) => controller.setBrush({ impasto: { tool: e.target.value } }), className: "rounded bg-slate-800 px-1 py-0.5", children: IMPASTO_TOOLS.map((t) => /* @__PURE__ */ jsx4("option", { value: t, children: IMPASTO_TOOL_LABELS[t] }, t)) })
|
|
660
|
+
] }),
|
|
549
661
|
engine.sliders.map((c) => /* @__PURE__ */ jsx4(PaintSlider, { control: c, value: readPath(brush, c.path), onChange: (v) => controller.setBrush(patchPath(brush, c.path, v)) }, c.path)),
|
|
550
662
|
engine.mixingPath && /* @__PURE__ */ jsxs4("label", { className: "flex items-center justify-between gap-2", children: [
|
|
551
663
|
/* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "Mixing" }),
|
|
@@ -629,7 +741,7 @@ var BrushPanel = ({ controller, thumbnailWidth = 112, thumbnailHeight = 32, clas
|
|
|
629
741
|
|
|
630
742
|
// src/paint-editor/PaperPanel.tsx
|
|
631
743
|
import { useMemo as useMemo4 } from "react";
|
|
632
|
-
import { PAPER_FAMILIES, PAPER_LIBRARY, PAPER_SLIDERS, patchPath as patchPath2, readPath as readPath2, renderPaper } from "@pixerate/editor/paint-editor";
|
|
744
|
+
import { LIGHT_SLIDERS, PAPER_FAMILIES, PAPER_LIBRARY, PAPER_SLIDERS, patchPath as patchPath2, readPath as readPath2, renderPaper } from "@pixerate/editor/paint-editor";
|
|
633
745
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
634
746
|
var SWATCH = { x: 0, y: 0, width: 64, height: 40 };
|
|
635
747
|
var PaperPanel = ({ controller, className }) => {
|
|
@@ -665,12 +777,17 @@ var PaperPanel = ({ controller, className }) => {
|
|
|
665
777
|
" Mirror tiling"
|
|
666
778
|
] }),
|
|
667
779
|
/* @__PURE__ */ jsx5("button", { type: "button", className: "self-start px-2 py-1 rounded text-[11px] bg-slate-800 text-slate-200 hover:bg-slate-700", onClick: () => attempt(() => controller.setPaper({ seed: paper.seed + 1 })), children: "New sheet" }),
|
|
780
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1.5", "data-testid": "light-panel", children: [
|
|
781
|
+
/* @__PURE__ */ jsx5("span", { className: "font-medium text-slate-300", children: "Light (thick paint, metallic paper)" }),
|
|
782
|
+
LIGHT_SLIDERS.map((c) => /* @__PURE__ */ jsx5(PaintSlider, { control: c, value: readPath2(controller.getLighting(), c.path), onChange: (v) => attempt(() => controller.setLighting({ [c.path]: v })) }, c.path)),
|
|
783
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: "self-start px-2 py-1 rounded text-[11px] bg-slate-800 text-slate-200 hover:bg-slate-700", onClick: () => attempt(() => controller.flattenRelief()), children: "Flatten relief" })
|
|
784
|
+
] }),
|
|
668
785
|
/* @__PURE__ */ jsx5(ErrorLine, { error })
|
|
669
786
|
] });
|
|
670
787
|
};
|
|
671
788
|
|
|
672
789
|
// src/paint-editor/ColorPanel.tsx
|
|
673
|
-
import { useMemo as useMemo5, useRef as useRef5, useState as
|
|
790
|
+
import { useMemo as useMemo5, useRef as useRef5, useState as useState6 } from "react";
|
|
674
791
|
import {
|
|
675
792
|
HARMONY_KINDS,
|
|
676
793
|
MIXING_PAD_HEIGHT,
|
|
@@ -690,12 +807,12 @@ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
|
690
807
|
var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className }) => {
|
|
691
808
|
const state = usePaintEditorState(controller);
|
|
692
809
|
const [error, attempt] = useAttempt();
|
|
693
|
-
const [wheel, setWheel] =
|
|
694
|
-
const [kind, setKind] =
|
|
695
|
-
const [padTool, setPadTool] =
|
|
696
|
-
const [sample, setSample] =
|
|
697
|
-
const [parts, setParts] =
|
|
698
|
-
const [padName, setPadName] =
|
|
810
|
+
const [wheel, setWheel] = useState6("ryb");
|
|
811
|
+
const [kind, setKind] = useState6("complementary");
|
|
812
|
+
const [padTool, setPadTool] = useState6("paint");
|
|
813
|
+
const [sample, setSample] = useState6(3);
|
|
814
|
+
const [parts, setParts] = useState6([1, 1]);
|
|
815
|
+
const [padName, setPadName] = useState6("");
|
|
699
816
|
const padPoints = useRef5(null);
|
|
700
817
|
const ring = useMemo5(() => renderColorWheel(wheelSize, wheel), [wheelSize, wheel]);
|
|
701
818
|
const color = state.color;
|
|
@@ -865,7 +982,7 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
|
|
|
865
982
|
};
|
|
866
983
|
|
|
867
984
|
// src/paint-editor/PaintLayersPanel.tsx
|
|
868
|
-
import { useState as
|
|
985
|
+
import { useState as useState7 } from "react";
|
|
869
986
|
import { BLEND_MENU_GROUPS as BLEND_MENU_GROUPS2 } from "@pixerate/editor/paint-editor";
|
|
870
987
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
871
988
|
function siblingIndex(layers, l) {
|
|
@@ -875,7 +992,7 @@ function siblingIndex(layers, l) {
|
|
|
875
992
|
var PaintLayersPanel = ({ controller, className }) => {
|
|
876
993
|
usePaintEditorState(controller);
|
|
877
994
|
const [error, attempt] = useAttempt();
|
|
878
|
-
const [renaming, setRenaming] =
|
|
995
|
+
const [renaming, setRenaming] = useState7(null);
|
|
879
996
|
const layers = controller.listLayers();
|
|
880
997
|
const active = layers.find((l) => l.active) ?? null;
|
|
881
998
|
const move = (delta) => {
|
|
@@ -975,26 +1092,117 @@ var PaintLayersPanel = ({ controller, className }) => {
|
|
|
975
1092
|
] });
|
|
976
1093
|
};
|
|
977
1094
|
|
|
978
|
-
// src/paint-editor/
|
|
979
|
-
import {
|
|
1095
|
+
// src/paint-editor/WetPanel.tsx
|
|
1096
|
+
import { WET_FLOW_KINDS, WET_PARAM_SLIDERS, WET_SPEED_SLIDER, WET_TILT_SLIDERS, readPath as readPath3 } from "@pixerate/editor/paint-editor";
|
|
980
1097
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1098
|
+
var FLOW_LABELS = { none: "None", linear: "One way", swirl: "Swirl", radial: "Outwards", turbulent: "Eddies" };
|
|
1099
|
+
var WetPanel = ({ controller, className }) => {
|
|
1100
|
+
const state = usePaintEditorState(controller);
|
|
1101
|
+
const [error, attempt] = useAttempt();
|
|
1102
|
+
const wet = state.wet;
|
|
1103
|
+
const layerId = state.document.activeLayerId;
|
|
1104
|
+
const info = layerId ? controller.wetLayerInfo(layerId) : null;
|
|
1105
|
+
const backend = controller.wetState();
|
|
1106
|
+
return /* @__PURE__ */ jsxs8("div", { className: `${panel} ${className ?? ""}`, "data-testid": "wet-panel", children: [
|
|
1107
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between", children: [
|
|
1108
|
+
/* @__PURE__ */ jsx8("span", { className: wet.wet ? "text-sky-300" : "text-slate-400", "data-testid": "wet-status", children: wet.wet ? wet.paused ? "Wet (paused)" : "Wet" : "Dry" }),
|
|
1109
|
+
/* @__PURE__ */ jsxs8("span", { className: "text-[10px] text-slate-500", children: [
|
|
1110
|
+
"step ",
|
|
1111
|
+
wet.clock
|
|
1112
|
+
] })
|
|
1113
|
+
] }),
|
|
1114
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap gap-1", children: [
|
|
1115
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: button, disabled: !wet.layerIds.length, onClick: () => attempt(async () => (await controller.wetIdle(), controller.dryPaint())), children: "Dry now" }),
|
|
1116
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: chip(wet.paused), "aria-pressed": wet.paused, onClick: () => controller.setWetPaused(!wet.paused), children: wet.paused ? "Resume" : "Pause" }),
|
|
1117
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: chip(wet.overlay), "aria-pressed": wet.overlay, onClick: () => controller.setWaterOverlay(!wet.overlay), children: "Water" }),
|
|
1118
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: chip(wet.instantDry), "aria-pressed": wet.instantDry, title: "Each wet stroke dries as soon as it ends", onClick: () => controller.setInstantDry(!wet.instantDry), children: "Instant dry" }),
|
|
1119
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: button, disabled: !layerId, onClick: () => attempt(async () => (await controller.wetIdle(), controller.wetPaper({}))), children: "Wet paper" })
|
|
1120
|
+
] }),
|
|
1121
|
+
/* @__PURE__ */ jsx8(PaintSlider, { control: WET_SPEED_SLIDER, value: wet.speed, onChange: (v) => controller.setWetSpeed(v) }),
|
|
1122
|
+
WET_TILT_SLIDERS.map((c) => /* @__PURE__ */ jsx8(PaintSlider, { control: c, value: readPath3(wet.tilt, c.path), onCommit: (v) => attempt(async () => (await controller.wetIdle(), controller.setCanvasTilt({ [c.path]: v }))) }, c.path)),
|
|
1123
|
+
info && /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1.5", "data-testid": "wet-layer", children: [
|
|
1124
|
+
/* @__PURE__ */ jsx8("span", { className: "font-medium text-slate-300", children: "This layer" }),
|
|
1125
|
+
WET_PARAM_SLIDERS.map((c) => /* @__PURE__ */ jsx8(PaintSlider, { control: c, value: readPath3(info.params, c.path), onCommit: (v) => attempt(async () => (await controller.wetIdle(), controller.setWetParams(layerId, { [c.path]: v }))) }, c.path)),
|
|
1126
|
+
/* @__PURE__ */ jsxs8("label", { className: "flex items-center justify-between gap-2", children: [
|
|
1127
|
+
/* @__PURE__ */ jsx8("span", { className: "text-slate-400", children: "Flow map" }),
|
|
1128
|
+
/* @__PURE__ */ jsx8("select", { "aria-label": "Flow map", value: info.flow.kind, onChange: (e) => attempt(async () => (await controller.wetIdle(), controller.setFlowMap(layerId, { kind: e.target.value }))), className: "rounded bg-slate-800 px-1 py-0.5", children: WET_FLOW_KINDS.map((k) => /* @__PURE__ */ jsx8("option", { value: k, children: FLOW_LABELS[k] }, k)) })
|
|
1129
|
+
] }),
|
|
1130
|
+
info.flow.kind !== "none" && /* @__PURE__ */ jsx8(
|
|
1131
|
+
PaintSlider,
|
|
1132
|
+
{
|
|
1133
|
+
control: { path: "strength", label: "Flow strength", min: 0, max: 1, step: 0.01 },
|
|
1134
|
+
value: info.flow.strength,
|
|
1135
|
+
onCommit: (v) => attempt(async () => (await controller.wetIdle(), controller.setFlowMap(layerId, { strength: v })))
|
|
1136
|
+
}
|
|
1137
|
+
)
|
|
1138
|
+
] }),
|
|
1139
|
+
backend.message && /* @__PURE__ */ jsx8("p", { className: "text-[10px] text-amber-300", "data-testid": "wet-message", children: backend.message }),
|
|
1140
|
+
backend.backend && /* @__PURE__ */ jsx8("p", { className: "text-[10px] text-slate-500", children: backend.backend.kind === "wasm-threads" ? `WebAssembly, ${backend.backend.threads} threads` : backend.backend.kind === "wasm" ? "WebAssembly, one thread" : "JavaScript" }),
|
|
1141
|
+
/* @__PURE__ */ jsx8(ErrorLine, { error })
|
|
1142
|
+
] });
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
// src/paint-editor/StickerPanel.tsx
|
|
1146
|
+
import { useState as useState8 } from "react";
|
|
1147
|
+
import { STAMP_KINDS as STAMP_KINDS2, STAMP_LABELS as STAMP_LABELS2, STICKER_SLIDERS, readPath as readPath4 } from "@pixerate/editor/paint-editor";
|
|
1148
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1149
|
+
var StickerPanel = ({ controller, className }) => {
|
|
1150
|
+
const state = usePaintEditorState(controller);
|
|
1151
|
+
const [error, attempt] = useAttempt();
|
|
1152
|
+
const [picked, setPicked] = useState8(null);
|
|
1153
|
+
const layerId = state.document.activeLayerId;
|
|
1154
|
+
const stickers = layerId ? state.document.stickers?.[layerId] ?? [] : [];
|
|
1155
|
+
if (!stickers.length) return null;
|
|
1156
|
+
const sel = stickers.find((s) => s.id === picked) ?? null;
|
|
1157
|
+
return /* @__PURE__ */ jsxs9("div", { className: `${panel} ${className ?? ""}`, "data-testid": "sticker-panel", children: [
|
|
1158
|
+
/* @__PURE__ */ jsxs9("span", { className: "font-medium text-slate-300", children: [
|
|
1159
|
+
"Stickers (",
|
|
1160
|
+
stickers.length,
|
|
1161
|
+
")"
|
|
1162
|
+
] }),
|
|
1163
|
+
/* @__PURE__ */ jsx9("div", { className: "flex max-h-24 flex-wrap gap-1 overflow-y-auto", role: "listbox", "aria-label": "Stickers", children: stickers.map((s) => /* @__PURE__ */ jsx9("button", { type: "button", role: "option", "aria-selected": s.id === picked, title: `${STAMP_LABELS2[s.stamp]} ${s.id}`, className: `h-5 w-5 rounded ring-1 ${s.id === picked ? "ring-indigo-400" : "ring-slate-700"}`, style: { background: s.color }, onClick: () => setPicked(s.id) }, s.id)) }),
|
|
1164
|
+
sel && /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-1.5", "data-testid": "sticker-edit", children: [
|
|
1165
|
+
/* @__PURE__ */ jsxs9("label", { className: "flex items-center justify-between gap-2", children: [
|
|
1166
|
+
/* @__PURE__ */ jsx9("span", { className: "text-slate-400", children: "Stamp" }),
|
|
1167
|
+
/* @__PURE__ */ jsx9("select", { "aria-label": "Sticker stamp", value: sel.stamp, onChange: (e) => attempt(() => controller.updateSticker(layerId, sel.id, { stamp: e.target.value })), className: "rounded bg-slate-800 px-1 py-0.5", children: STAMP_KINDS2.map((k) => /* @__PURE__ */ jsx9("option", { value: k, children: STAMP_LABELS2[k] }, k)) })
|
|
1168
|
+
] }),
|
|
1169
|
+
STICKER_SLIDERS.map((c) => /* @__PURE__ */ jsx9(PaintSlider, { control: c, value: readPath4(sel, c.path), onCommit: (v) => attempt(() => controller.updateSticker(layerId, sel.id, { [c.path]: v })) }, c.path)),
|
|
1170
|
+
/* @__PURE__ */ jsxs9("label", { className: "flex items-center justify-between", children: [
|
|
1171
|
+
/* @__PURE__ */ jsx9("span", { className: "text-slate-400", children: "Colour" }),
|
|
1172
|
+
/* @__PURE__ */ jsx9(CommitColorInput, { label: "Sticker colour", value: sel.color, onCommit: (v) => attempt(() => controller.updateSticker(layerId, sel.id, { color: v })), className: "h-6 w-10 rounded bg-transparent" })
|
|
1173
|
+
] }),
|
|
1174
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap gap-1", children: [
|
|
1175
|
+
/* @__PURE__ */ jsx9("button", { type: "button", className: button, onClick: () => attempt(() => controller.arrangeStickers(layerId, [sel.id], "front")), children: "To front" }),
|
|
1176
|
+
/* @__PURE__ */ jsx9("button", { type: "button", className: button, onClick: () => attempt(() => controller.arrangeStickers(layerId, [sel.id], "back")), children: "To back" }),
|
|
1177
|
+
/* @__PURE__ */ jsx9("button", { type: "button", className: button, onClick: () => attempt(() => (controller.removeStickers(layerId, [sel.id]), setPicked(null))), children: "Delete" })
|
|
1178
|
+
] })
|
|
1179
|
+
] }),
|
|
1180
|
+
/* @__PURE__ */ jsx9(ErrorLine, { error })
|
|
1181
|
+
] });
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
// src/paint-editor/PaintEditor.tsx
|
|
1185
|
+
import { useState as useState9 } from "react";
|
|
1186
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
981
1187
|
var PaintEditor = ({ controller, fileName, height = 720, presence, className }) => {
|
|
982
|
-
const [tool, setTool] =
|
|
983
|
-
const [tab, setTab] =
|
|
984
|
-
const [error, setError] =
|
|
985
|
-
return /* @__PURE__ */
|
|
986
|
-
/* @__PURE__ */
|
|
987
|
-
/* @__PURE__ */
|
|
988
|
-
/* @__PURE__ */
|
|
989
|
-
/* @__PURE__ */
|
|
990
|
-
/* @__PURE__ */
|
|
991
|
-
error && /* @__PURE__ */
|
|
1188
|
+
const [tool, setTool] = useState9("brush");
|
|
1189
|
+
const [tab, setTab] = useState9("color");
|
|
1190
|
+
const [error, setError] = useState9(null);
|
|
1191
|
+
return /* @__PURE__ */ jsxs10("div", { className: `flex flex-col gap-2 rounded-xl border border-slate-800 bg-slate-900 p-2 ${className ?? ""}`, style: { height }, "data-testid": "paint-editor", children: [
|
|
1192
|
+
/* @__PURE__ */ jsx10(PaintToolbar, { controller, tool, onToolChange: setTool, fileName }),
|
|
1193
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex min-h-0 flex-1 gap-2", children: [
|
|
1194
|
+
/* @__PURE__ */ jsx10(BrushPanel, { controller, className: "w-60 shrink-0 overflow-y-auto pr-1" }),
|
|
1195
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
1196
|
+
/* @__PURE__ */ jsx10(PaintCanvas, { controller, tool, onError: setError, presence, className: "flex-1 rounded-lg" }),
|
|
1197
|
+
error && /* @__PURE__ */ jsx10("p", { role: "alert", className: "mt-1 text-[11px] text-rose-400", children: error })
|
|
992
1198
|
] }),
|
|
993
|
-
/* @__PURE__ */
|
|
994
|
-
/* @__PURE__ */
|
|
995
|
-
tab === "color" && /* @__PURE__ */
|
|
996
|
-
tab === "
|
|
997
|
-
tab === "
|
|
1199
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex w-64 shrink-0 flex-col gap-2 overflow-y-auto pr-1", children: [
|
|
1200
|
+
/* @__PURE__ */ jsx10("div", { className: "flex gap-1", role: "tablist", "aria-label": "Paint panels", children: ["color", "wet", "paper", "layers"].map((t) => /* @__PURE__ */ jsx10("button", { type: "button", role: "tab", "aria-selected": tab === t, className: chip(tab === t), onClick: () => setTab(t), children: t === "color" ? "Colour" : t === "wet" ? "Wet" : t === "paper" ? "Paper" : "Layers" }, t)) }),
|
|
1201
|
+
tab === "color" && /* @__PURE__ */ jsx10(ColorPanel, { controller }),
|
|
1202
|
+
tab === "wet" && /* @__PURE__ */ jsx10(WetPanel, { controller }),
|
|
1203
|
+
tab === "paper" && /* @__PURE__ */ jsx10(PaperPanel, { controller }),
|
|
1204
|
+
tab === "layers" && /* @__PURE__ */ jsx10(PaintLayersPanel, { controller }),
|
|
1205
|
+
tab === "layers" && /* @__PURE__ */ jsx10(StickerPanel, { controller })
|
|
998
1206
|
] })
|
|
999
1207
|
] })
|
|
1000
1208
|
] });
|
|
@@ -1009,6 +1217,8 @@ export {
|
|
|
1009
1217
|
PaintSlider,
|
|
1010
1218
|
PaintToolbar,
|
|
1011
1219
|
PaperPanel,
|
|
1220
|
+
StickerPanel,
|
|
1221
|
+
WetPanel,
|
|
1012
1222
|
usePaintDirector,
|
|
1013
1223
|
usePaintEditor,
|
|
1014
1224
|
usePaintEditorState
|