@pixerate/editor-react 0.74.0 → 0.75.1
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 +328 -220
- 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 +318 -212
- package/dist/vector/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -29,6 +29,8 @@ __export(paint_editor_exports, {
|
|
|
29
29
|
PaintSlider: () => PaintSlider,
|
|
30
30
|
PaintToolbar: () => PaintToolbar,
|
|
31
31
|
PaperPanel: () => PaperPanel,
|
|
32
|
+
StickerPanel: () => StickerPanel,
|
|
33
|
+
WetPanel: () => WetPanel,
|
|
32
34
|
usePaintDirector: () => usePaintDirector,
|
|
33
35
|
usePaintEditor: () => usePaintEditor,
|
|
34
36
|
usePaintEditorState: () => usePaintEditorState
|
|
@@ -66,6 +68,7 @@ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount =
|
|
|
66
68
|
const state = usePaintEditorState(controller);
|
|
67
69
|
const viewport = (0, import_react2.useRef)(null);
|
|
68
70
|
const canvas = (0, import_react2.useRef)(null);
|
|
71
|
+
const water = (0, import_react2.useRef)(null);
|
|
69
72
|
const [size, setSize] = (0, import_react2.useState)({ width: 800, height: 600 });
|
|
70
73
|
const measured = (0, import_react2.useRef)(false);
|
|
71
74
|
const [predicted, setPredicted] = (0, import_react2.useState)([]);
|
|
@@ -105,6 +108,11 @@ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount =
|
|
|
105
108
|
if (ctx && typeof ctx.createImageData === "function") presenter.render(ctx);
|
|
106
109
|
else controller.takeDirty();
|
|
107
110
|
}, [state, presenter, controller]);
|
|
111
|
+
(0, import_react2.useEffect)(() => (0, import_paint_editor2.startWetClock)(controller), [controller]);
|
|
112
|
+
(0, import_react2.useEffect)(() => {
|
|
113
|
+
const ctx = water.current?.getContext("2d");
|
|
114
|
+
if (ctx && typeof ctx.createImageData === "function") (0, import_paint_editor2.drawWaterOverlay)(controller, ctx);
|
|
115
|
+
}, [state, controller]);
|
|
108
116
|
(0, import_react2.useEffect)(() => {
|
|
109
117
|
const el = viewport.current;
|
|
110
118
|
if (!el) return;
|
|
@@ -159,6 +167,17 @@ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount =
|
|
|
159
167
|
style: { transform, transformOrigin: "0 0", imageRendering: state.view.zoom > 2 ? "pixelated" : "auto" }
|
|
160
168
|
}
|
|
161
169
|
),
|
|
170
|
+
state.wet.overlay && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
171
|
+
"canvas",
|
|
172
|
+
{
|
|
173
|
+
ref: water,
|
|
174
|
+
width: doc.width,
|
|
175
|
+
height: doc.height,
|
|
176
|
+
"data-testid": "paint-water-overlay",
|
|
177
|
+
className: "absolute left-0 top-0 pointer-events-none opacity-60",
|
|
178
|
+
style: { transform, transformOrigin: "0 0" }
|
|
179
|
+
}
|
|
180
|
+
),
|
|
162
181
|
/* @__PURE__ */ (0, import_jsx_runtime.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: [
|
|
163
182
|
lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
164
183
|
"line",
|
|
@@ -167,8 +186,8 @@ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount =
|
|
|
167
186
|
y1: l.from.y,
|
|
168
187
|
x2: l.to.x,
|
|
169
188
|
y2: l.to.y,
|
|
170
|
-
stroke: l.kind === "symmetry" ? "#22d3ee" : "#f472b6",
|
|
171
|
-
strokeOpacity: l.kind === "symmetry" ? 0.8 : 0.35,
|
|
189
|
+
stroke: l.kind === "symmetry" ? "#22d3ee" : l.kind === "stencil" ? "#fbbf24" : "#f472b6",
|
|
190
|
+
strokeOpacity: l.kind === "symmetry" || l.kind === "stencil" ? 0.8 : 0.35,
|
|
172
191
|
strokeWidth: 1 / state.view.zoom,
|
|
173
192
|
strokeDasharray: l.kind === "symmetry" ? `${6 / state.view.zoom} ${4 / state.view.zoom}` : void 0
|
|
174
193
|
},
|
|
@@ -352,6 +371,8 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
|
352
371
|
var PaintToolbar = ({ controller, tool, onToolChange, fileName = "painting", className }) => {
|
|
353
372
|
const state = usePaintEditorState(controller);
|
|
354
373
|
const [error, attempt] = useAttempt();
|
|
374
|
+
const [busy, setBusy] = (0, import_react4.useState)(null);
|
|
375
|
+
const [note, setNote] = (0, import_react4.useState)(null);
|
|
355
376
|
const openRef = (0, import_react4.useRef)(null);
|
|
356
377
|
const tracingRef = (0, import_react4.useRef)(null);
|
|
357
378
|
const refRef = (0, import_react4.useRef)(null);
|
|
@@ -448,6 +469,12 @@ var PaintToolbar = ({ controller, tool, onToolChange, fileName = "painting", cla
|
|
|
448
469
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: button, onClick: () => controller.clearTracingImage(), children: "Remove tracing" })
|
|
449
470
|
] }),
|
|
450
471
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: button, onClick: () => refRef.current?.click(), children: "Reference\u2026" }),
|
|
472
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "flex items-center gap-1", role: "group", "aria-label": "Stencils", children: [
|
|
473
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: button, title: "A ruler: strokes that start near it follow it", onClick: () => attempt(() => controller.addStencil((0, import_paint_editor3.quickStencil)("ruler", canvas))), children: "Ruler" }),
|
|
474
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: button, title: "A circle guide: strokes that start near it follow it", onClick: () => attempt(() => controller.addStencil((0, import_paint_editor3.quickStencil)("circle", canvas))), children: "Circle" }),
|
|
475
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: button, title: "An ellipse mask: paint lands only inside it", onClick: () => attempt(() => controller.addStencil((0, import_paint_editor3.quickStencil)("ellipse-mask", canvas))), children: "Mask" }),
|
|
476
|
+
state.guides.stencils.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => controller.removeStencils()), children: "Clear stencils" })
|
|
477
|
+
] }),
|
|
451
478
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }),
|
|
452
479
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-1 h-4 w-px bg-slate-700" }),
|
|
453
480
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => (0, import_paint_editor3.downloadBytes)(JSON.stringify(controller.toJSON()), `${fileName}.paint.json`, "application/json")), children: "Save" }),
|
|
@@ -455,6 +482,45 @@ var PaintToolbar = ({ controller, tool, onToolChange, fileName = "painting", cla
|
|
|
455
482
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: button, onClick: () => exportImage("image/png"), children: "PNG" }),
|
|
456
483
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: button, onClick: () => exportImage("image/jpeg"), children: "JPEG" }),
|
|
457
484
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => (0, import_paint_editor3.downloadBytes)(controller.exportPsd().bytes, `${fileName}.psd`, "image/vnd.adobe.photoshop")), children: "PSD" }),
|
|
485
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
486
|
+
"button",
|
|
487
|
+
{
|
|
488
|
+
type: "button",
|
|
489
|
+
className: button,
|
|
490
|
+
disabled: busy !== null,
|
|
491
|
+
title: "Repaint the painting from its strokes as a time-lapse: frames, a video project and the ffmpeg command, zipped",
|
|
492
|
+
onClick: () => attempt(async () => {
|
|
493
|
+
setBusy("Repainting\u2026");
|
|
494
|
+
try {
|
|
495
|
+
const tl = await controller.timeLapse({ onProgress: (p) => setBusy(`Repainting\u2026 ${Math.round(p * 100)}%`) });
|
|
496
|
+
(0, import_paint_editor3.downloadBytes)(await (0, import_paint_editor3.timeLapseZip)(tl), `${fileName}-timelapse.zip`, "application/zip");
|
|
497
|
+
setNote(`Time-lapse: ${tl.frames.length} frames, ${(tl.durationMs / 1e3).toFixed(1)} s (run render.sh in the zip for the MP4).`);
|
|
498
|
+
} finally {
|
|
499
|
+
setBusy(null);
|
|
500
|
+
}
|
|
501
|
+
}),
|
|
502
|
+
children: "Time-lapse"
|
|
503
|
+
}
|
|
504
|
+
),
|
|
505
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
506
|
+
"button",
|
|
507
|
+
{
|
|
508
|
+
type: "button",
|
|
509
|
+
className: button,
|
|
510
|
+
disabled: busy !== null,
|
|
511
|
+
title: "Repaint the painting from its strokes at twice the size (more detail, not upscaled)",
|
|
512
|
+
onClick: () => attempt(async () => {
|
|
513
|
+
setBusy("Repainting at 2\xD7\u2026");
|
|
514
|
+
try {
|
|
515
|
+
(0, import_paint_editor3.downloadBytes)(await controller.exportHighResPng({ scale: 2 }), `${fileName}@2x.png`, "image/png");
|
|
516
|
+
} finally {
|
|
517
|
+
setBusy(null);
|
|
518
|
+
}
|
|
519
|
+
}),
|
|
520
|
+
children: "PNG 2\xD7"
|
|
521
|
+
}
|
|
522
|
+
),
|
|
523
|
+
(busy ?? note) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[11px] text-slate-400", "data-testid": "paint-replay-status", children: busy ?? note }),
|
|
458
524
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
459
525
|
"input",
|
|
460
526
|
{
|
|
@@ -564,6 +630,39 @@ var BrushPanel = ({ controller, thumbnailWidth = 112, thumbnailHeight = 32, clas
|
|
|
564
630
|
] }),
|
|
565
631
|
engine && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-1.5", "data-testid": "brush-engine", children: [
|
|
566
632
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "font-medium text-slate-300", children: engine.title }),
|
|
633
|
+
engine.wetTool && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
634
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-slate-400", children: "Wet tool" }),
|
|
635
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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: import_paint_editor4.WET_TOOLS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: t, children: import_paint_editor4.WET_TOOL_LABELS[t] }, t)) })
|
|
636
|
+
] }),
|
|
637
|
+
engine.wetTool && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
638
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-slate-400", children: "Medium" }),
|
|
639
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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: import_paint_editor4.WET_MEDIA.map((m) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: m, children: import_paint_editor4.WET_MEDIUM_LABELS[m] }, m)) })
|
|
640
|
+
] }),
|
|
641
|
+
engine.particleMenus && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
642
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
643
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-slate-400", children: "Particles" }),
|
|
644
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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: import_paint_editor4.PARTICLE_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: k, children: import_paint_editor4.PARTICLE_KIND_LABELS[k] }, k)) })
|
|
645
|
+
] }),
|
|
646
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
647
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-slate-400", children: "Drawn as" }),
|
|
648
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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: import_paint_editor4.PARTICLE_RENDERS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: k, children: import_paint_editor4.PARTICLE_RENDER_LABELS[k] }, k)) })
|
|
649
|
+
] })
|
|
650
|
+
] }),
|
|
651
|
+
engine.stickerStamps && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex flex-wrap gap-1", role: "group", "aria-label": "Sticker stamps", children: import_paint_editor4.STAMP_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
652
|
+
"button",
|
|
653
|
+
{
|
|
654
|
+
type: "button",
|
|
655
|
+
"aria-pressed": brush.sticker.stamps.includes(k),
|
|
656
|
+
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"}`,
|
|
657
|
+
onClick: () => controller.setBrush({ sticker: { stamps: (0, import_paint_editor4.toggleStamp)(brush.sticker.stamps, k) } }),
|
|
658
|
+
children: import_paint_editor4.STAMP_LABELS[k]
|
|
659
|
+
},
|
|
660
|
+
k
|
|
661
|
+
)) }),
|
|
662
|
+
engine.impastoTool && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
663
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-slate-400", children: "Tool" }),
|
|
664
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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: import_paint_editor4.IMPASTO_TOOLS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: t, children: import_paint_editor4.IMPASTO_TOOL_LABELS[t] }, t)) })
|
|
665
|
+
] }),
|
|
567
666
|
engine.sliders.map((c) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor4.readPath)(brush, c.path), onChange: (v) => controller.setBrush((0, import_paint_editor4.patchPath)(brush, c.path, v)) }, c.path)),
|
|
568
667
|
engine.mixingPath && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
569
668
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-slate-400", children: "Mixing" }),
|
|
@@ -683,6 +782,11 @@ var PaperPanel = ({ controller, className }) => {
|
|
|
683
782
|
" Mirror tiling"
|
|
684
783
|
] }),
|
|
685
784
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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" }),
|
|
785
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1.5", "data-testid": "light-panel", children: [
|
|
786
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "font-medium text-slate-300", children: "Light (thick paint, metallic paper)" }),
|
|
787
|
+
import_paint_editor5.LIGHT_SLIDERS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor5.readPath)(controller.getLighting(), c.path), onChange: (v) => attempt(() => controller.setLighting({ [c.path]: v })) }, c.path)),
|
|
788
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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" })
|
|
789
|
+
] }),
|
|
686
790
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ErrorLine, { error })
|
|
687
791
|
] });
|
|
688
792
|
};
|
|
@@ -979,26 +1083,117 @@ var PaintLayersPanel = ({ controller, className }) => {
|
|
|
979
1083
|
] });
|
|
980
1084
|
};
|
|
981
1085
|
|
|
982
|
-
// src/paint-editor/
|
|
983
|
-
var
|
|
1086
|
+
// src/paint-editor/WetPanel.tsx
|
|
1087
|
+
var import_paint_editor8 = require("@pixerate/editor/paint-editor");
|
|
984
1088
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1089
|
+
var FLOW_LABELS = { none: "None", linear: "One way", swirl: "Swirl", radial: "Outwards", turbulent: "Eddies" };
|
|
1090
|
+
var WetPanel = ({ controller, className }) => {
|
|
1091
|
+
const state = usePaintEditorState(controller);
|
|
1092
|
+
const [error, attempt] = useAttempt();
|
|
1093
|
+
const wet = state.wet;
|
|
1094
|
+
const layerId = state.document.activeLayerId;
|
|
1095
|
+
const info = layerId ? controller.wetLayerInfo(layerId) : null;
|
|
1096
|
+
const backend = controller.wetState();
|
|
1097
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: `${panel} ${className ?? ""}`, "data-testid": "wet-panel", children: [
|
|
1098
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
1099
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: wet.wet ? "text-sky-300" : "text-slate-400", "data-testid": "wet-status", children: wet.wet ? wet.paused ? "Wet (paused)" : "Wet" : "Dry" }),
|
|
1100
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-[10px] text-slate-500", children: [
|
|
1101
|
+
"step ",
|
|
1102
|
+
wet.clock
|
|
1103
|
+
] })
|
|
1104
|
+
] }),
|
|
1105
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
|
|
1106
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !wet.layerIds.length, onClick: () => attempt(async () => (await controller.wetIdle(), controller.dryPaint())), children: "Dry now" }),
|
|
1107
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: chip(wet.paused), "aria-pressed": wet.paused, onClick: () => controller.setWetPaused(!wet.paused), children: wet.paused ? "Resume" : "Pause" }),
|
|
1108
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: chip(wet.overlay), "aria-pressed": wet.overlay, onClick: () => controller.setWaterOverlay(!wet.overlay), children: "Water" }),
|
|
1109
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("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" }),
|
|
1110
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !layerId, onClick: () => attempt(async () => (await controller.wetIdle(), controller.wetPaper({}))), children: "Wet paper" })
|
|
1111
|
+
] }),
|
|
1112
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PaintSlider, { control: import_paint_editor8.WET_SPEED_SLIDER, value: wet.speed, onChange: (v) => controller.setWetSpeed(v) }),
|
|
1113
|
+
import_paint_editor8.WET_TILT_SLIDERS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor8.readPath)(wet.tilt, c.path), onCommit: (v) => attempt(async () => (await controller.wetIdle(), controller.setCanvasTilt({ [c.path]: v }))) }, c.path)),
|
|
1114
|
+
info && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-1.5", "data-testid": "wet-layer", children: [
|
|
1115
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-medium text-slate-300", children: "This layer" }),
|
|
1116
|
+
import_paint_editor8.WET_PARAM_SLIDERS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor8.readPath)(info.params, c.path), onCommit: (v) => attempt(async () => (await controller.wetIdle(), controller.setWetParams(layerId, { [c.path]: v }))) }, c.path)),
|
|
1117
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
1118
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-slate-400", children: "Flow map" }),
|
|
1119
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("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: import_paint_editor8.WET_FLOW_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: k, children: FLOW_LABELS[k] }, k)) })
|
|
1120
|
+
] }),
|
|
1121
|
+
info.flow.kind !== "none" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1122
|
+
PaintSlider,
|
|
1123
|
+
{
|
|
1124
|
+
control: { path: "strength", label: "Flow strength", min: 0, max: 1, step: 0.01 },
|
|
1125
|
+
value: info.flow.strength,
|
|
1126
|
+
onCommit: (v) => attempt(async () => (await controller.wetIdle(), controller.setFlowMap(layerId, { strength: v })))
|
|
1127
|
+
}
|
|
1128
|
+
)
|
|
1129
|
+
] }),
|
|
1130
|
+
backend.message && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[10px] text-amber-300", "data-testid": "wet-message", children: backend.message }),
|
|
1131
|
+
backend.backend && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("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" }),
|
|
1132
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorLine, { error })
|
|
1133
|
+
] });
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
// src/paint-editor/StickerPanel.tsx
|
|
1137
|
+
var import_react9 = require("react");
|
|
1138
|
+
var import_paint_editor9 = require("@pixerate/editor/paint-editor");
|
|
1139
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1140
|
+
var StickerPanel = ({ controller, className }) => {
|
|
1141
|
+
const state = usePaintEditorState(controller);
|
|
1142
|
+
const [error, attempt] = useAttempt();
|
|
1143
|
+
const [picked, setPicked] = (0, import_react9.useState)(null);
|
|
1144
|
+
const layerId = state.document.activeLayerId;
|
|
1145
|
+
const stickers = layerId ? state.document.stickers?.[layerId] ?? [] : [];
|
|
1146
|
+
if (!stickers.length) return null;
|
|
1147
|
+
const sel = stickers.find((s) => s.id === picked) ?? null;
|
|
1148
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: `${panel} ${className ?? ""}`, "data-testid": "sticker-panel", children: [
|
|
1149
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-medium text-slate-300", children: [
|
|
1150
|
+
"Stickers (",
|
|
1151
|
+
stickers.length,
|
|
1152
|
+
")"
|
|
1153
|
+
] }),
|
|
1154
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex max-h-24 flex-wrap gap-1 overflow-y-auto", role: "listbox", "aria-label": "Stickers", children: stickers.map((s) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", role: "option", "aria-selected": s.id === picked, title: `${import_paint_editor9.STAMP_LABELS[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)) }),
|
|
1155
|
+
sel && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-1.5", "data-testid": "sticker-edit", children: [
|
|
1156
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
1157
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-slate-400", children: "Stamp" }),
|
|
1158
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("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: import_paint_editor9.STAMP_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: k, children: import_paint_editor9.STAMP_LABELS[k] }, k)) })
|
|
1159
|
+
] }),
|
|
1160
|
+
import_paint_editor9.STICKER_SLIDERS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor9.readPath)(sel, c.path), onCommit: (v) => attempt(() => controller.updateSticker(layerId, sel.id, { [c.path]: v })) }, c.path)),
|
|
1161
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center justify-between", children: [
|
|
1162
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-slate-400", children: "Colour" }),
|
|
1163
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(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" })
|
|
1164
|
+
] }),
|
|
1165
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
|
|
1166
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => controller.arrangeStickers(layerId, [sel.id], "front")), children: "To front" }),
|
|
1167
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => controller.arrangeStickers(layerId, [sel.id], "back")), children: "To back" }),
|
|
1168
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => (controller.removeStickers(layerId, [sel.id]), setPicked(null))), children: "Delete" })
|
|
1169
|
+
] })
|
|
1170
|
+
] }),
|
|
1171
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorLine, { error })
|
|
1172
|
+
] });
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
// src/paint-editor/PaintEditor.tsx
|
|
1176
|
+
var import_react10 = require("react");
|
|
1177
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
985
1178
|
var PaintEditor = ({ controller, fileName, height = 720, presence, className }) => {
|
|
986
|
-
const [tool, setTool] = (0,
|
|
987
|
-
const [tab, setTab] = (0,
|
|
988
|
-
const [error, setError] = (0,
|
|
989
|
-
return /* @__PURE__ */ (0,
|
|
990
|
-
/* @__PURE__ */ (0,
|
|
991
|
-
/* @__PURE__ */ (0,
|
|
992
|
-
/* @__PURE__ */ (0,
|
|
993
|
-
/* @__PURE__ */ (0,
|
|
994
|
-
/* @__PURE__ */ (0,
|
|
995
|
-
error && /* @__PURE__ */ (0,
|
|
1179
|
+
const [tool, setTool] = (0, import_react10.useState)("brush");
|
|
1180
|
+
const [tab, setTab] = (0, import_react10.useState)("color");
|
|
1181
|
+
const [error, setError] = (0, import_react10.useState)(null);
|
|
1182
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("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: [
|
|
1183
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaintToolbar, { controller, tool, onToolChange: setTool, fileName }),
|
|
1184
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex min-h-0 flex-1 gap-2", children: [
|
|
1185
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BrushPanel, { controller, className: "w-60 shrink-0 overflow-y-auto pr-1" }),
|
|
1186
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
1187
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaintCanvas, { controller, tool, onError: setError, presence, className: "flex-1 rounded-lg" }),
|
|
1188
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { role: "alert", className: "mt-1 text-[11px] text-rose-400", children: error })
|
|
996
1189
|
] }),
|
|
997
|
-
/* @__PURE__ */ (0,
|
|
998
|
-
/* @__PURE__ */ (0,
|
|
999
|
-
tab === "color" && /* @__PURE__ */ (0,
|
|
1000
|
-
tab === "
|
|
1001
|
-
tab === "
|
|
1190
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex w-64 shrink-0 flex-col gap-2 overflow-y-auto pr-1", children: [
|
|
1191
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex gap-1", role: "tablist", "aria-label": "Paint panels", children: ["color", "wet", "paper", "layers"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("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)) }),
|
|
1192
|
+
tab === "color" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ColorPanel, { controller }),
|
|
1193
|
+
tab === "wet" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(WetPanel, { controller }),
|
|
1194
|
+
tab === "paper" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaperPanel, { controller }),
|
|
1195
|
+
tab === "layers" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaintLayersPanel, { controller }),
|
|
1196
|
+
tab === "layers" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StickerPanel, { controller })
|
|
1002
1197
|
] })
|
|
1003
1198
|
] })
|
|
1004
1199
|
] });
|
|
@@ -1014,6 +1209,8 @@ var PaintEditor = ({ controller, fileName, height = 720, presence, className })
|
|
|
1014
1209
|
PaintSlider,
|
|
1015
1210
|
PaintToolbar,
|
|
1016
1211
|
PaperPanel,
|
|
1212
|
+
StickerPanel,
|
|
1213
|
+
WetPanel,
|
|
1017
1214
|
usePaintDirector,
|
|
1018
1215
|
usePaintEditor,
|
|
1019
1216
|
usePaintEditorState
|