@pixerate/editor-react 0.76.0 → 0.77.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/PaintGradientEditorView-CCPCY5Ib.d.cts +14 -0
- package/dist/PaintGradientEditorView-CCPCY5Ib.d.ts +14 -0
- package/dist/canvas/index.cjs +662 -561
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +36 -5
- package/dist/canvas/index.d.ts +36 -5
- package/dist/canvas/index.js +232 -129
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-MO354USR.js +126 -0
- package/dist/chunk-MO354USR.js.map +1 -0
- package/dist/{chunk-LWMUV5TC.js → chunk-QARGR5E5.js} +278 -253
- package/dist/chunk-QARGR5E5.js.map +1 -0
- package/dist/image-editor/index.cjs +677 -531
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +8 -2
- package/dist/image-editor/index.d.ts +8 -2
- package/dist/image-editor/index.js +4 -1
- package/dist/index.cjs +1095 -949
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/paint-editor/index.cjs +438 -289
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +8 -3
- package/dist/paint-editor/index.d.ts +8 -3
- package/dist/paint-editor/index.js +215 -187
- package/dist/paint-editor/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-LWMUV5TC.js.map +0 -1
|
@@ -24,6 +24,7 @@ __export(paint_editor_exports, {
|
|
|
24
24
|
ColorPanel: () => ColorPanel,
|
|
25
25
|
PaintCanvas: () => PaintCanvas,
|
|
26
26
|
PaintEditor: () => PaintEditor,
|
|
27
|
+
PaintGradientEditor: () => PaintGradientEditor,
|
|
27
28
|
PaintLayersPanel: () => PaintLayersPanel,
|
|
28
29
|
PaintPresenceLayer: () => PaintPresenceLayer,
|
|
29
30
|
PaintSlider: () => PaintSlider,
|
|
@@ -571,29 +572,159 @@ var PaintToolbar = ({ controller, tool, onToolChange, fileName = "painting", cla
|
|
|
571
572
|
};
|
|
572
573
|
|
|
573
574
|
// src/paint-editor/BrushPanel.tsx
|
|
574
|
-
var
|
|
575
|
+
var import_react6 = require("react");
|
|
576
|
+
var import_paint_editor5 = require("@pixerate/editor/paint-editor");
|
|
577
|
+
var import_paint_editor6 = require("@pixerate/editor/paint-editor");
|
|
578
|
+
|
|
579
|
+
// src/paint-editor/PaintGradientEditor.tsx
|
|
575
580
|
var import_paint_editor4 = require("@pixerate/editor/paint-editor");
|
|
581
|
+
|
|
582
|
+
// src/paint-editor/PaintGradientEditorView.tsx
|
|
583
|
+
var import_react5 = require("react");
|
|
576
584
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
585
|
+
var button2 = "px-2 py-1 rounded text-[11px] bg-slate-800 text-slate-200 hover:bg-slate-700 disabled:opacity-40";
|
|
586
|
+
var select = "rounded bg-slate-800 px-1 py-0.5 text-[11px]";
|
|
587
|
+
var number = "w-16 rounded bg-slate-800 px-1 py-0.5 text-[11px]";
|
|
588
|
+
var pct = (v) => Math.round(v * 1e3) / 10;
|
|
589
|
+
var PaintGradientEditorView = ({ value, onChange, colors, className, kit }) => {
|
|
590
|
+
const {
|
|
591
|
+
PAINT_GRADIENT_MAPPINGS,
|
|
592
|
+
PAINT_GRADIENT_MAPPING_LABELS,
|
|
593
|
+
PAINT_GRADIENT_PRESETS,
|
|
594
|
+
PAINT_GRADIENT_REPEATS,
|
|
595
|
+
PAINT_GRADIENT_REPEAT_LABELS,
|
|
596
|
+
PAINT_GRADIENT_SPACES,
|
|
597
|
+
PAINT_GRADIENT_SPACE_LABELS,
|
|
598
|
+
addPaintGradientStop,
|
|
599
|
+
paintGradientMappingFor,
|
|
600
|
+
paintSourceCss: paintSourceCss2,
|
|
601
|
+
removePaintGradientStop,
|
|
602
|
+
reversePaintGradient,
|
|
603
|
+
updatePaintGradientStop
|
|
604
|
+
} = kit;
|
|
605
|
+
const [selected, setSelected] = (0, import_react5.useState)(0);
|
|
606
|
+
const sel = Math.min(selected, value.stops.length - 1);
|
|
607
|
+
const stop = value.stops[sel];
|
|
608
|
+
const map = value.map;
|
|
609
|
+
const stopHex = (c) => c === "paint" ? colors.paint : c === "secondary" ? colors.secondary : c;
|
|
610
|
+
const patchStop = (patch) => {
|
|
611
|
+
const r = updatePaintGradientStop(value, sel, patch);
|
|
612
|
+
setSelected(r.index);
|
|
613
|
+
onChange(r.paint);
|
|
614
|
+
};
|
|
615
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `flex flex-col gap-1.5 text-xs text-slate-200 ${className ?? ""}`, "data-testid": "paint-gradient-editor", "aria-label": "Gradient paint", children: [
|
|
616
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
617
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("select", { "aria-label": "Gradient preset", className: select, value: "", onChange: (e) => e.target.value && (setSelected(0), onChange(PAINT_GRADIENT_PRESETS[Number(e.target.value)].paint)), children: [
|
|
618
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "", children: "Presets\u2026" }),
|
|
619
|
+
PAINT_GRADIENT_PRESETS.map((p, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: i, children: p.name }, p.name))
|
|
620
|
+
] }),
|
|
621
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: button2, onClick: () => onChange(reversePaintGradient(value)), children: "Reverse" })
|
|
622
|
+
] }),
|
|
623
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
624
|
+
"div",
|
|
625
|
+
{
|
|
626
|
+
role: "slider",
|
|
627
|
+
tabIndex: 0,
|
|
628
|
+
"aria-label": "Gradient ramp: click to add a stop",
|
|
629
|
+
"aria-valuemin": 0,
|
|
630
|
+
"aria-valuemax": 100,
|
|
631
|
+
"aria-valuenow": pct(stop.offset),
|
|
632
|
+
"data-testid": "paint-gradient-ramp",
|
|
633
|
+
className: "relative h-6 cursor-copy rounded ring-1 ring-slate-700",
|
|
634
|
+
style: { background: paintSourceCss2(value, colors) },
|
|
635
|
+
onClick: (e) => {
|
|
636
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
637
|
+
const r = addPaintGradientStop(value, rect.width ? (e.clientX - rect.left) / rect.width : 0.5, colors);
|
|
638
|
+
if (r.index >= 0) {
|
|
639
|
+
setSelected(r.index);
|
|
640
|
+
onChange(r.paint);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
),
|
|
645
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "relative h-4", "aria-label": "Stops", children: value.stops.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
646
|
+
"button",
|
|
647
|
+
{
|
|
648
|
+
type: "button",
|
|
649
|
+
"aria-label": `Stop ${i + 1} at ${pct(s.offset)}%`,
|
|
650
|
+
"aria-pressed": i === sel,
|
|
651
|
+
className: `absolute top-0 h-4 w-3 -translate-x-1/2 rounded-sm ring-1 ${i === sel ? "ring-white" : "ring-slate-600"}`,
|
|
652
|
+
style: { left: `${s.offset * 100}%`, background: stopHex(s.color) },
|
|
653
|
+
onClick: () => setSelected(i)
|
|
654
|
+
},
|
|
655
|
+
i
|
|
656
|
+
)) }),
|
|
657
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-wrap items-center gap-1", "data-testid": "paint-gradient-stop", children: [
|
|
658
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("select", { "aria-label": "Stop colour source", className: select, value: stop.color === "paint" || stop.color === "secondary" ? stop.color : "color", onChange: (e) => patchStop({ color: e.target.value === "color" ? stopHex(stop.color) : e.target.value }), children: [
|
|
659
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "color", children: "Colour" }),
|
|
660
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "paint", children: "Paint colour" }),
|
|
661
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "secondary", children: "Second colour" })
|
|
662
|
+
] }),
|
|
663
|
+
stop.color !== "paint" && stop.color !== "secondary" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "color", "aria-label": "Stop colour", value: stop.color, onChange: (e) => patchStop({ color: e.target.value }), className: "h-6 w-8 rounded bg-transparent" }),
|
|
664
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-1", children: [
|
|
665
|
+
"At",
|
|
666
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "number", "aria-label": "Stop position", min: 0, max: 100, step: 1, value: pct(stop.offset), onChange: (e) => patchStop({ offset: Number(e.target.value) / 100 }), className: number }),
|
|
667
|
+
"%"
|
|
668
|
+
] }),
|
|
669
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-1", children: [
|
|
670
|
+
"Opacity",
|
|
671
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "number", "aria-label": "Stop opacity", min: 0, max: 100, step: 5, value: pct(stop.opacity ?? 1), onChange: (e) => patchStop({ opacity: Number(e.target.value) / 100 }), className: number }),
|
|
672
|
+
"%"
|
|
673
|
+
] }),
|
|
674
|
+
sel < value.stops.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-1", children: [
|
|
675
|
+
"Midpoint",
|
|
676
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "number", "aria-label": "Stop midpoint", min: 5, max: 95, step: 1, value: pct(stop.midpoint ?? 0.5), onChange: (e) => patchStop({ midpoint: Number(e.target.value) / 100 }), className: number }),
|
|
677
|
+
"%"
|
|
678
|
+
] }),
|
|
679
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: button2, disabled: value.stops.length <= 2, onClick: () => (setSelected(Math.max(0, sel - 1)), onChange(removePaintGradientStop(value, sel))), children: "Remove stop" })
|
|
680
|
+
] }),
|
|
681
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-wrap items-center gap-1", "data-testid": "paint-gradient-mapping", children: [
|
|
682
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Gradient follows", className: select, value: map.by, onChange: (e) => onChange({ ...value, map: paintGradientMappingFor(e.target.value, map) }), children: PAINT_GRADIENT_MAPPINGS.map((m) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: m, children: PAINT_GRADIENT_MAPPING_LABELS[m] }, m)) }),
|
|
683
|
+
map.by === "distance" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
684
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "number", "aria-label": "Gradient length", min: 1, max: 1e5, value: map.length, onChange: (e) => onChange({ ...value, map: { ...map, length: Math.max(1, Number(e.target.value) || 1) } }), className: number }),
|
|
685
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("select", { "aria-label": "Gradient length unit", className: select, value: map.unit ?? "px", onChange: (e) => onChange({ ...value, map: { ...map, unit: e.target.value } }), children: [
|
|
686
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "px", children: "px" }),
|
|
687
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "dabs", children: "dabs" })
|
|
688
|
+
] })
|
|
689
|
+
] }),
|
|
690
|
+
map.by === "time" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "number", "aria-label": "Gradient duration", min: 1, max: 6e5, value: map.duration, onChange: (e) => onChange({ ...value, map: { ...map, duration: Math.max(1, Number(e.target.value) || 1) } }), className: number }),
|
|
691
|
+
map.by === "random" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "number", "aria-label": "Dabs per pick", min: 1, max: 500, value: map.every ?? 1, onChange: (e) => onChange({ ...value, map: { ...map, every: Math.max(1, Math.round(Number(e.target.value) || 1)) } }), className: number }),
|
|
692
|
+
(map.by === "distance" || map.by === "time") && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Gradient repeat", className: select, value: map.repeat ?? "clamp", onChange: (e) => onChange({ ...value, map: { ...map, repeat: e.target.value } }), children: PAINT_GRADIENT_REPEATS.map((r) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: r, children: PAINT_GRADIENT_REPEAT_LABELS[r] }, r)) })
|
|
693
|
+
] }),
|
|
694
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
695
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Gradient mixing", className: select, value: value.space, onChange: (e) => onChange({ ...value, space: e.target.value }), children: PAINT_GRADIENT_SPACES.map((s) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: s, children: PAINT_GRADIENT_SPACE_LABELS[s] }, s)) }),
|
|
696
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-1 items-center gap-1", children: [
|
|
697
|
+
"Softness",
|
|
698
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "range", "aria-label": "Gradient softness", min: 0, max: 1, step: 0.05, value: value.softness ?? 1, onChange: (e) => onChange({ ...value, softness: Number(e.target.value) }), className: "flex-1 accent-indigo-500" })
|
|
699
|
+
] })
|
|
700
|
+
] })
|
|
701
|
+
] });
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
// src/paint-editor/PaintGradientEditor.tsx
|
|
705
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
706
|
+
var PaintGradientEditor = (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PaintGradientEditorView, { ...props, kit: import_paint_editor4.PAINT_GRADIENT_KIT });
|
|
707
|
+
|
|
708
|
+
// src/paint-editor/BrushPanel.tsx
|
|
709
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
577
710
|
var BrushPanel = ({ controller, thumbnailWidth = 112, thumbnailHeight = 32, className }) => {
|
|
578
711
|
const state = usePaintEditorState(controller);
|
|
579
712
|
const [error, attempt] = useAttempt();
|
|
580
|
-
const [saveName, setSaveName] = (0,
|
|
581
|
-
const fileRef = (0,
|
|
713
|
+
const [saveName, setSaveName] = (0, import_react6.useState)("");
|
|
714
|
+
const fileRef = (0, import_react6.useRef)(null);
|
|
582
715
|
const { brush, brushes } = state;
|
|
583
716
|
const paper = state.document.paper;
|
|
584
|
-
const thumbs = (0,
|
|
717
|
+
const thumbs = (0, import_react6.useMemo)(
|
|
585
718
|
() => new Map(brushes.map((b) => [b.id, controller.brushThumbnail(b.id, { width: thumbnailWidth, height: thumbnailHeight, color: "#26324a" })])),
|
|
586
719
|
// Thumbnails follow the library and the paper.
|
|
587
720
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
588
721
|
[controller, brushes, paper, thumbnailWidth, thumbnailHeight]
|
|
589
722
|
);
|
|
590
|
-
const engine = (0,
|
|
591
|
-
const
|
|
592
|
-
const carried = (0, import_paint_editor4.brushLoadColor)(state.mixerLoad);
|
|
593
|
-
const setColors = (colors) => controller.setBrush({ behavior: { multicolor: { colors } } });
|
|
723
|
+
const engine = (0, import_paint_editor5.brushEngineControls)(brush);
|
|
724
|
+
const carried = (0, import_paint_editor5.brushLoadColor)(state.mixerLoad);
|
|
594
725
|
const slug = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "brush";
|
|
595
|
-
return /* @__PURE__ */ (0,
|
|
596
|
-
/* @__PURE__ */ (0,
|
|
726
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `${panel} ${className ?? ""}`, "data-testid": "brush-panel", children: [
|
|
727
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "grid grid-cols-2 gap-1", role: "listbox", "aria-label": "Brushes", children: brushes.map((b) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
597
728
|
"button",
|
|
598
729
|
{
|
|
599
730
|
type: "button",
|
|
@@ -603,128 +734,116 @@ var BrushPanel = ({ controller, thumbnailWidth = 112, thumbnailHeight = 32, clas
|
|
|
603
734
|
className: `flex flex-col items-start gap-0.5 rounded p-1 text-left ${brush.id === b.id ? "bg-indigo-600/30 ring-1 ring-indigo-500" : "bg-slate-900 hover:bg-slate-800"}`,
|
|
604
735
|
onClick: () => controller.setBrush({ id: b.id }),
|
|
605
736
|
children: [
|
|
606
|
-
/* @__PURE__ */ (0,
|
|
607
|
-
/* @__PURE__ */ (0,
|
|
737
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PixelCanvas, { pixels: thumbs.get(b.id) ?? null, label: `${b.name} thumbnail`, className: "rounded", style: { width: "100%", aspectRatio: `${thumbnailWidth} / ${thumbnailHeight}` } }),
|
|
738
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "truncate text-[10px] text-slate-300", children: b.name })
|
|
608
739
|
]
|
|
609
740
|
},
|
|
610
741
|
b.id
|
|
611
742
|
)) }),
|
|
612
|
-
/* @__PURE__ */ (0,
|
|
613
|
-
|
|
614
|
-
/* @__PURE__ */ (0,
|
|
615
|
-
/* @__PURE__ */ (0,
|
|
616
|
-
/* @__PURE__ */ (0,
|
|
743
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
|
|
744
|
+
import_paint_editor5.BRUSH_SLIDERS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor5.readPath)(brush, c.path), onChange: (v) => controller.setBrush((0, import_paint_editor5.patchPath)(brush, c.path, v)) }, c.path)),
|
|
745
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
746
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "Rotation" }),
|
|
747
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("select", { "aria-label": "Brush rotation", value: brush.rotation, onChange: (e) => controller.setBrush({ rotation: e.target.value }), className: "rounded bg-slate-800 px-1 py-0.5", children: import_paint_editor5.BRUSH_ROTATIONS.map((r) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: r, children: r === "stroke" ? "Follow stroke" : r === "barrel" ? "Pen barrel" : "Fixed" }, r)) })
|
|
617
748
|
] }),
|
|
618
|
-
/* @__PURE__ */ (0,
|
|
619
|
-
/* @__PURE__ */ (0,
|
|
749
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-2", children: [
|
|
750
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "checkbox", checked: brush.tip.pressureSize, onChange: (e) => controller.setBrush({ tip: { pressureSize: e.target.checked } }) }),
|
|
620
751
|
" Pressure \u2192 size"
|
|
621
752
|
] }),
|
|
622
|
-
/* @__PURE__ */ (0,
|
|
623
|
-
/* @__PURE__ */ (0,
|
|
753
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-2", children: [
|
|
754
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "checkbox", checked: brush.tip.pressureOpacity, onChange: (e) => controller.setBrush({ tip: { pressureOpacity: e.target.checked } }) }),
|
|
624
755
|
" Pressure \u2192 opacity"
|
|
625
756
|
] }),
|
|
626
|
-
brush.engine === "dab" && /* @__PURE__ */ (0,
|
|
627
|
-
/* @__PURE__ */ (0,
|
|
628
|
-
/* @__PURE__ */ (0,
|
|
757
|
+
brush.engine === "dab" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
758
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "Blend" }),
|
|
759
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("select", { "aria-label": "Brush blend", value: brush.tip.blend, onChange: (e) => controller.setBrush({ tip: { blend: e.target.value } }), className: "rounded bg-slate-800 px-1 py-0.5", children: import_paint_editor5.BLEND_MENU_GROUPS.map((g) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("optgroup", { label: g.label, children: g.modes.map((m) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: m.id, title: m.description, children: m.label }, m.id)) }, g.label)) })
|
|
629
760
|
] })
|
|
630
761
|
] }),
|
|
631
|
-
engine && /* @__PURE__ */ (0,
|
|
632
|
-
/* @__PURE__ */ (0,
|
|
633
|
-
engine.wetTool && /* @__PURE__ */ (0,
|
|
634
|
-
/* @__PURE__ */ (0,
|
|
635
|
-
/* @__PURE__ */ (0,
|
|
762
|
+
engine && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-1.5", "data-testid": "brush-engine", children: [
|
|
763
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "font-medium text-slate-300", children: engine.title }),
|
|
764
|
+
engine.wetTool && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
765
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "Wet tool" }),
|
|
766
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.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_editor5.WET_TOOLS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: t, children: import_paint_editor5.WET_TOOL_LABELS[t] }, t)) })
|
|
636
767
|
] }),
|
|
637
|
-
engine.wetTool && /* @__PURE__ */ (0,
|
|
638
|
-
/* @__PURE__ */ (0,
|
|
639
|
-
/* @__PURE__ */ (0,
|
|
768
|
+
engine.wetTool && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
769
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "Medium" }),
|
|
770
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.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_editor5.WET_MEDIA.map((m) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: m, children: import_paint_editor5.WET_MEDIUM_LABELS[m] }, m)) })
|
|
640
771
|
] }),
|
|
641
|
-
engine.particleMenus && /* @__PURE__ */ (0,
|
|
642
|
-
/* @__PURE__ */ (0,
|
|
643
|
-
/* @__PURE__ */ (0,
|
|
644
|
-
/* @__PURE__ */ (0,
|
|
772
|
+
engine.particleMenus && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
773
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
774
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "Particles" }),
|
|
775
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.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_editor5.PARTICLE_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: k, children: import_paint_editor5.PARTICLE_KIND_LABELS[k] }, k)) })
|
|
645
776
|
] }),
|
|
646
|
-
/* @__PURE__ */ (0,
|
|
647
|
-
/* @__PURE__ */ (0,
|
|
648
|
-
/* @__PURE__ */ (0,
|
|
777
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
778
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "Drawn as" }),
|
|
779
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.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_editor5.PARTICLE_RENDERS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: k, children: import_paint_editor5.PARTICLE_RENDER_LABELS[k] }, k)) })
|
|
649
780
|
] })
|
|
650
781
|
] }),
|
|
651
|
-
engine.stickerStamps && /* @__PURE__ */ (0,
|
|
782
|
+
engine.stickerStamps && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex flex-wrap gap-1", role: "group", "aria-label": "Sticker stamps", children: import_paint_editor5.STAMP_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
652
783
|
"button",
|
|
653
784
|
{
|
|
654
785
|
type: "button",
|
|
655
786
|
"aria-pressed": brush.sticker.stamps.includes(k),
|
|
656
787
|
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,
|
|
658
|
-
children:
|
|
788
|
+
onClick: () => controller.setBrush({ sticker: { stamps: (0, import_paint_editor5.toggleStamp)(brush.sticker.stamps, k) } }),
|
|
789
|
+
children: import_paint_editor5.STAMP_LABELS[k]
|
|
659
790
|
},
|
|
660
791
|
k
|
|
661
792
|
)) }),
|
|
662
|
-
engine.impastoTool && /* @__PURE__ */ (0,
|
|
663
|
-
/* @__PURE__ */ (0,
|
|
664
|
-
/* @__PURE__ */ (0,
|
|
793
|
+
engine.impastoTool && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
794
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "Tool" }),
|
|
795
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.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_editor5.IMPASTO_TOOLS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: t, children: import_paint_editor5.IMPASTO_TOOL_LABELS[t] }, t)) })
|
|
665
796
|
] }),
|
|
666
|
-
engine.sliders.map((c) => /* @__PURE__ */ (0,
|
|
667
|
-
engine.mixingPath && /* @__PURE__ */ (0,
|
|
668
|
-
/* @__PURE__ */ (0,
|
|
669
|
-
/* @__PURE__ */ (0,
|
|
797
|
+
engine.sliders.map((c) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor5.readPath)(brush, c.path), onChange: (v) => controller.setBrush((0, import_paint_editor5.patchPath)(brush, c.path, v)) }, c.path)),
|
|
798
|
+
engine.mixingPath && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
799
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "Mixing" }),
|
|
800
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
670
801
|
"select",
|
|
671
802
|
{
|
|
672
803
|
"aria-label": "Brush mixing",
|
|
673
804
|
value: engine.mixingPath === "blender.mixing" ? brush.blender.mixing : engine.mixingPath === "smudge.mixing" ? brush.smudge.mixing : brush.behavior.mixing,
|
|
674
|
-
onChange: (e) => controller.setBrush((0,
|
|
805
|
+
onChange: (e) => controller.setBrush((0, import_paint_editor5.patchBrushValue)(brush, engine.mixingPath, e.target.value)),
|
|
675
806
|
className: "rounded bg-slate-800 px-1 py-0.5",
|
|
676
807
|
children: [
|
|
677
|
-
/* @__PURE__ */ (0,
|
|
678
|
-
/* @__PURE__ */ (0,
|
|
808
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "pigment", children: "Pigment" }),
|
|
809
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "rgb", children: "RGB" })
|
|
679
810
|
]
|
|
680
811
|
}
|
|
681
812
|
)
|
|
682
813
|
] }),
|
|
683
|
-
engine.dirty && /* @__PURE__ */ (0,
|
|
684
|
-
/* @__PURE__ */ (0,
|
|
814
|
+
engine.dirty && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-2", title: "Keep the colour the brush picked up for the next stroke (needs Pick-up above 0)", children: [
|
|
815
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "checkbox", "aria-label": "Dirty brush", checked: brush.behavior.dirty, onChange: (e) => controller.setBrush({ behavior: { dirty: e.target.checked } }) }),
|
|
685
816
|
" Dirty brush"
|
|
686
817
|
] }),
|
|
687
|
-
|
|
688
|
-
/* @__PURE__ */ (0,
|
|
689
|
-
/* @__PURE__ */ (0,
|
|
690
|
-
/* @__PURE__ */ (0,
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
818
|
+
(0, import_paint_editor5.brushTakesPaint)(brush) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-1.5", "data-testid": "brush-gradient", children: [
|
|
819
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
|
|
820
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", title: "A gradient of the brush's own, used instead of the paint (the multi-colour brush)", children: "Brush gradient" }),
|
|
821
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
822
|
+
"button",
|
|
823
|
+
{
|
|
824
|
+
type: "button",
|
|
825
|
+
"aria-pressed": brush.paint !== null,
|
|
826
|
+
className: chip(brush.paint !== null),
|
|
827
|
+
onClick: () => attempt(() => controller.setBrush({ paint: brush.paint ? null : import_paint_editor6.DEFAULT_PAINT_GRADIENT })),
|
|
828
|
+
children: brush.paint ? "On" : "Off"
|
|
829
|
+
}
|
|
830
|
+
)
|
|
695
831
|
] }),
|
|
696
|
-
|
|
697
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-wrap items-center gap-1", "aria-label": "Multi-colour colours", children: [
|
|
698
|
-
mc.colors.map((col, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "flex items-center", children: [
|
|
699
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "color", "aria-label": `Multi-colour ${i + 1}`, value: col, onChange: (e) => setColors(mc.colors.map((x, j) => j === i ? e.target.value : x)), className: "h-5 w-6 rounded bg-transparent" }),
|
|
700
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "aria-label": `Remove multi-colour ${i + 1}`, className: "px-0.5 text-slate-500 hover:text-white", onClick: () => setColors(mc.colors.filter((_, j) => j !== i)), children: "\xD7" })
|
|
701
|
-
] }, i)),
|
|
702
|
-
mc.colors.length < 8 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: button, "aria-label": "Add multi-colour", onClick: () => setColors([...mc.colors.length ? mc.colors : [state.color, state.secondaryColor], state.color].slice(0, 8)), children: "+" }),
|
|
703
|
-
mc.colors.length < 2 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-[10px] text-slate-500", children: "Uses the paint and second colours" })
|
|
704
|
-
] }),
|
|
705
|
-
mc.mode === "dab" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
706
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-slate-400", children: "Order" }),
|
|
707
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("select", { "aria-label": "Colour order", value: mc.order, onChange: (e) => controller.setBrush({ behavior: { multicolor: { order: e.target.value } } }), className: "rounded bg-slate-800 px-1 py-0.5", children: [
|
|
708
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "sequence", children: "In order" }),
|
|
709
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "random", children: "Random" })
|
|
710
|
-
] })
|
|
711
|
-
] }),
|
|
712
|
-
(0, import_paint_editor4.multiColorSliders)(mc.mode).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))
|
|
713
|
-
] })
|
|
832
|
+
brush.paint && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PaintGradientEditor, { value: brush.paint, colors: { paint: state.color, secondary: state.secondaryColor }, onChange: (g) => attempt(() => controller.setBrush({ paint: g })) })
|
|
714
833
|
] }),
|
|
715
|
-
engine.load && /* @__PURE__ */ (0,
|
|
716
|
-
/* @__PURE__ */ (0,
|
|
717
|
-
/* @__PURE__ */ (0,
|
|
718
|
-
brush.engine === "mixer" && /* @__PURE__ */ (0,
|
|
719
|
-
/* @__PURE__ */ (0,
|
|
834
|
+
engine.load && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
835
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "Carries" }),
|
|
836
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "aria-label": "Brush load", title: carried ?? "Clean", className: "h-4 w-4 rounded ring-1 ring-slate-700", style: { background: carried ?? "transparent" } }),
|
|
837
|
+
brush.engine === "mixer" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: button, onClick: () => controller.loadMixerBrush(), children: "Load" }),
|
|
838
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: button, onClick: () => controller.cleanMixerBrush(brush.engine === "mixer"), children: "Clean" })
|
|
720
839
|
] })
|
|
721
840
|
] }),
|
|
722
|
-
/* @__PURE__ */ (0,
|
|
723
|
-
/* @__PURE__ */ (0,
|
|
724
|
-
/* @__PURE__ */ (0,
|
|
725
|
-
/* @__PURE__ */ (0,
|
|
726
|
-
/* @__PURE__ */ (0,
|
|
727
|
-
/* @__PURE__ */ (0,
|
|
841
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
842
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { value: saveName, placeholder: "Brush name", "aria-label": "Brush name", onChange: (e) => setSaveName(e.target.value), className: "w-28 rounded bg-slate-800 px-1.5 py-0.5" }),
|
|
843
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: button, disabled: !saveName.trim(), onClick: () => attempt(() => (controller.saveBrush({ id: slug(saveName), name: saveName.trim() }), setSaveName(""))), children: "Save brush" }),
|
|
844
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => (0, import_paint_editor5.downloadBytes)(JSON.stringify(controller.exportBrushes(), null, 2), "brushes.json", "application/json")), children: "Export" }),
|
|
845
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: button, onClick: () => fileRef.current?.click(), children: "Import\u2026" }),
|
|
846
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
728
847
|
"input",
|
|
729
848
|
{
|
|
730
849
|
ref: fileRef,
|
|
@@ -740,24 +859,24 @@ var BrushPanel = ({ controller, thumbnailWidth = 112, thumbnailHeight = 32, clas
|
|
|
740
859
|
}
|
|
741
860
|
)
|
|
742
861
|
] }),
|
|
743
|
-
/* @__PURE__ */ (0,
|
|
862
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorLine, { error })
|
|
744
863
|
] });
|
|
745
864
|
};
|
|
746
865
|
|
|
747
866
|
// src/paint-editor/PaperPanel.tsx
|
|
748
|
-
var
|
|
749
|
-
var
|
|
750
|
-
var
|
|
867
|
+
var import_react7 = require("react");
|
|
868
|
+
var import_paint_editor7 = require("@pixerate/editor/paint-editor");
|
|
869
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
751
870
|
var SWATCH = { x: 0, y: 0, width: 64, height: 40 };
|
|
752
871
|
var PaperPanel = ({ controller, className }) => {
|
|
753
872
|
const state = usePaintEditorState(controller);
|
|
754
873
|
const [error, attempt] = useAttempt();
|
|
755
874
|
const paper = state.document.paper;
|
|
756
|
-
const previews = (0,
|
|
757
|
-
return /* @__PURE__ */ (0,
|
|
758
|
-
|
|
759
|
-
/* @__PURE__ */ (0,
|
|
760
|
-
/* @__PURE__ */ (0,
|
|
875
|
+
const previews = (0, import_react7.useMemo)(() => new Map(import_paint_editor7.PAPER_LIBRARY.map((p) => [p.preset, { ...SWATCH, data: (0, import_paint_editor7.renderPaper)({ ...p, shading: Math.max(0.8, p.shading) }, SWATCH) }])), []);
|
|
876
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `${panel} ${className ?? ""}`, "data-testid": "paper-panel", children: [
|
|
877
|
+
import_paint_editor7.PAPER_FAMILIES.map((family) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
878
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mb-0.5 text-[10px] uppercase tracking-wide text-slate-500", children: family }),
|
|
879
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex flex-wrap gap-1", role: "listbox", "aria-label": `${family} papers`, children: import_paint_editor7.PAPER_LIBRARY.filter((p) => p.family === family).map((p) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
761
880
|
"button",
|
|
762
881
|
{
|
|
763
882
|
type: "button",
|
|
@@ -766,92 +885,93 @@ var PaperPanel = ({ controller, className }) => {
|
|
|
766
885
|
title: p.name,
|
|
767
886
|
className: `rounded p-0.5 ${paper.preset === p.preset ? "ring-2 ring-indigo-500" : "ring-1 ring-slate-700 hover:ring-slate-500"}`,
|
|
768
887
|
onClick: () => attempt(() => controller.setPaper({ preset: p.preset })),
|
|
769
|
-
children: /* @__PURE__ */ (0,
|
|
888
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PixelCanvas, { pixels: previews.get(p.preset) ?? null, label: p.name, style: { width: 48, height: 30 } })
|
|
770
889
|
},
|
|
771
890
|
p.preset
|
|
772
891
|
)) })
|
|
773
892
|
] }, family)),
|
|
774
|
-
/* @__PURE__ */ (0,
|
|
775
|
-
/* @__PURE__ */ (0,
|
|
776
|
-
/* @__PURE__ */ (0,
|
|
777
|
-
/* @__PURE__ */ (0,
|
|
893
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-slate-300", children: paper.name }),
|
|
894
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center justify-between", children: [
|
|
895
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-slate-400", children: "Colour" }),
|
|
896
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CommitColorInput, { label: "Paper colour", value: paper.color, onCommit: (v) => attempt(() => controller.setPaper({ color: v })), className: "h-6 w-10 rounded bg-transparent" })
|
|
778
897
|
] }),
|
|
779
|
-
|
|
780
|
-
/* @__PURE__ */ (0,
|
|
781
|
-
/* @__PURE__ */ (0,
|
|
898
|
+
import_paint_editor7.PAPER_SLIDERS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor7.readPath)(paper, c.path), onCommit: (v) => attempt(() => controller.setPaper((0, import_paint_editor7.patchPath)(paper, c.path, v))) }, c.path)),
|
|
899
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2", children: [
|
|
900
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: paper.tiling === "mirror", onChange: (e) => attempt(() => controller.setPaper({ tiling: e.target.checked ? "mirror" : "repeat" })) }),
|
|
782
901
|
" Mirror tiling"
|
|
783
902
|
] }),
|
|
784
|
-
/* @__PURE__ */ (0,
|
|
785
|
-
/* @__PURE__ */ (0,
|
|
786
|
-
/* @__PURE__ */ (0,
|
|
787
|
-
|
|
788
|
-
/* @__PURE__ */ (0,
|
|
903
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.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" }),
|
|
904
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-1.5", "data-testid": "light-panel", children: [
|
|
905
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-medium text-slate-300", children: "Light (thick paint, metallic paper)" }),
|
|
906
|
+
import_paint_editor7.LIGHT_SLIDERS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor7.readPath)(controller.getLighting(), c.path), onChange: (v) => attempt(() => controller.setLighting({ [c.path]: v })) }, c.path)),
|
|
907
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.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
908
|
] }),
|
|
790
|
-
/* @__PURE__ */ (0,
|
|
909
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ErrorLine, { error })
|
|
791
910
|
] });
|
|
792
911
|
};
|
|
793
912
|
|
|
794
913
|
// src/paint-editor/ColorPanel.tsx
|
|
795
|
-
var
|
|
796
|
-
var
|
|
797
|
-
var
|
|
914
|
+
var import_react8 = require("react");
|
|
915
|
+
var import_paint_editor8 = require("@pixerate/editor/paint-editor");
|
|
916
|
+
var import_paint_editor9 = require("@pixerate/editor/paint-editor");
|
|
917
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
798
918
|
var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className }) => {
|
|
799
919
|
const state = usePaintEditorState(controller);
|
|
800
920
|
const [error, attempt] = useAttempt();
|
|
801
|
-
const [wheel, setWheel] = (0,
|
|
802
|
-
const [kind, setKind] = (0,
|
|
803
|
-
const [padTool, setPadTool] = (0,
|
|
804
|
-
const [sample, setSample] = (0,
|
|
805
|
-
const [parts, setParts] = (0,
|
|
806
|
-
const [padName, setPadName] = (0,
|
|
807
|
-
const padPoints = (0,
|
|
808
|
-
const ring = (0,
|
|
921
|
+
const [wheel, setWheel] = (0, import_react8.useState)("ryb");
|
|
922
|
+
const [kind, setKind] = (0, import_react8.useState)("complementary");
|
|
923
|
+
const [padTool, setPadTool] = (0, import_react8.useState)("paint");
|
|
924
|
+
const [sample, setSample] = (0, import_react8.useState)(3);
|
|
925
|
+
const [parts, setParts] = (0, import_react8.useState)([1, 1]);
|
|
926
|
+
const [padName, setPadName] = (0, import_react8.useState)("");
|
|
927
|
+
const padPoints = (0, import_react8.useRef)(null);
|
|
928
|
+
const ring = (0, import_react8.useMemo)(() => (0, import_paint_editor8.renderColorWheel)(wheelSize, wheel), [wheelSize, wheel]);
|
|
809
929
|
const color = state.color;
|
|
810
|
-
const harmony = (0,
|
|
811
|
-
const [r, g, b] = (0,
|
|
812
|
-
const hsv = (0,
|
|
813
|
-
const pad = (0,
|
|
930
|
+
const harmony = (0, import_react8.useMemo)(() => controller.harmony(kind, { color, wheel }), [controller, kind, color, wheel]);
|
|
931
|
+
const [r, g, b] = (0, import_paint_editor8.parseHexColor)(color);
|
|
932
|
+
const hsv = (0, import_paint_editor8.rgbToHsv)(r, g, b);
|
|
933
|
+
const pad = (0, import_react8.useMemo)(() => ({ width: import_paint_editor8.MIXING_PAD_WIDTH, height: import_paint_editor8.MIXING_PAD_HEIGHT, data: controller.mixingPad.pixels() }), [controller, state.mixingPadVersion]);
|
|
814
934
|
const R = wheelSize / 2;
|
|
815
935
|
const mid = R * 0.82;
|
|
816
|
-
const setHsv = (s, v) => controller.setColor((0,
|
|
936
|
+
const setHsv = (s, v) => controller.setColor((0, import_paint_editor8.rgbToHexColor)(...(0, import_paint_editor8.hsvToRgb)(hsv.h, s, v)));
|
|
817
937
|
const padPoint = (e) => {
|
|
818
938
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
819
|
-
const kx =
|
|
820
|
-
const ky =
|
|
821
|
-
const x = Math.min(
|
|
822
|
-
const y = Math.min(
|
|
939
|
+
const kx = import_paint_editor8.MIXING_PAD_WIDTH / (rect.width || import_paint_editor8.MIXING_PAD_WIDTH);
|
|
940
|
+
const ky = import_paint_editor8.MIXING_PAD_HEIGHT / (rect.height || import_paint_editor8.MIXING_PAD_HEIGHT);
|
|
941
|
+
const x = Math.min(import_paint_editor8.MIXING_PAD_WIDTH, Math.max(0, (e.clientX - rect.left) * kx));
|
|
942
|
+
const y = Math.min(import_paint_editor8.MIXING_PAD_HEIGHT, Math.max(0, (e.clientY - rect.top) * ky));
|
|
823
943
|
return e.pointerType === "pen" && e.pressure > 0 ? { x, y, pressure: e.pressure } : { x, y };
|
|
824
944
|
};
|
|
825
|
-
return /* @__PURE__ */ (0,
|
|
826
|
-
/* @__PURE__ */ (0,
|
|
827
|
-
/* @__PURE__ */ (0,
|
|
945
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: `${panel} ${className ?? ""}`, "data-testid": "color-panel", children: [
|
|
946
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "relative self-center", style: { width: wheelSize, height: wheelSize }, children: [
|
|
947
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
828
948
|
"div",
|
|
829
949
|
{
|
|
830
950
|
role: "slider",
|
|
831
951
|
"aria-label": "Hue wheel",
|
|
832
952
|
"aria-valuemin": 0,
|
|
833
953
|
"aria-valuemax": 360,
|
|
834
|
-
"aria-valuenow": Math.round((0,
|
|
954
|
+
"aria-valuenow": Math.round((0, import_paint_editor8.hueToWheel)(hsv.h, wheel)),
|
|
835
955
|
tabIndex: 0,
|
|
836
956
|
className: "absolute inset-0 cursor-crosshair",
|
|
837
957
|
onPointerDown: (e) => {
|
|
838
958
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
839
|
-
controller.setColor((0,
|
|
959
|
+
controller.setColor((0, import_paint_editor8.wheelPick)(e.clientX - rect.left - R, e.clientY - rect.top - R, wheel, color));
|
|
840
960
|
},
|
|
841
|
-
children: /* @__PURE__ */ (0,
|
|
961
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PixelCanvas, { pixels: ring, label: "Colour wheel", style: { width: wheelSize, height: wheelSize } })
|
|
842
962
|
}
|
|
843
963
|
),
|
|
844
|
-
/* @__PURE__ */ (0,
|
|
845
|
-
const p = (0,
|
|
846
|
-
return /* @__PURE__ */ (0,
|
|
964
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { className: "pointer-events-none absolute inset-0", width: wheelSize, height: wheelSize, "aria-hidden": "true", children: harmony.map((h, i) => {
|
|
965
|
+
const p = (0, import_paint_editor8.wheelPoint)(h.angle, mid);
|
|
966
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: R + p.x, cy: R + p.y, r: i === 0 ? 7 : 5, fill: h.color, stroke: "#fff", strokeWidth: i === 0 ? 2 : 1 }, i);
|
|
847
967
|
}) }),
|
|
848
|
-
/* @__PURE__ */ (0,
|
|
968
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "absolute rounded-full border border-slate-700", style: { left: R - R * 0.45, top: R - R * 0.45, width: R * 0.9, height: R * 0.9, background: color } })
|
|
849
969
|
] }),
|
|
850
|
-
/* @__PURE__ */ (0,
|
|
851
|
-
/* @__PURE__ */ (0,
|
|
852
|
-
/* @__PURE__ */ (0,
|
|
853
|
-
/* @__PURE__ */ (0,
|
|
854
|
-
/* @__PURE__ */ (0,
|
|
970
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
971
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "color", "aria-label": "Paint colour", value: color, onChange: (e) => controller.setColor(e.target.value), className: "h-7 w-9 rounded bg-transparent" }),
|
|
972
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "color", "aria-label": "Second colour", value: state.secondaryColor, onChange: (e) => controller.setSecondaryColor(e.target.value), className: "h-5 w-7 rounded bg-transparent" }),
|
|
973
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, title: "Swap colours (X)", onClick: () => controller.swapColors(), children: "Swap" }),
|
|
974
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
855
975
|
"input",
|
|
856
976
|
{
|
|
857
977
|
"aria-label": "Hex colour",
|
|
@@ -862,35 +982,58 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
|
|
|
862
982
|
color
|
|
863
983
|
)
|
|
864
984
|
] }),
|
|
865
|
-
/* @__PURE__ */ (0,
|
|
866
|
-
/* @__PURE__ */ (0,
|
|
867
|
-
|
|
985
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2", "data-testid": "paint-source", children: [
|
|
986
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
987
|
+
"span",
|
|
988
|
+
{
|
|
989
|
+
className: "h-5 flex-1 rounded ring-1 ring-slate-700",
|
|
990
|
+
"aria-label": state.paint.type === "gradient" ? "Gradient paint" : "Paint colour",
|
|
991
|
+
"data-testid": "paint-well",
|
|
992
|
+
style: { background: (0, import_paint_editor9.paintSourceCss)(state.paint, { paint: color, secondary: state.secondaryColor }) }
|
|
993
|
+
}
|
|
994
|
+
),
|
|
995
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
996
|
+
"button",
|
|
997
|
+
{
|
|
998
|
+
type: "button",
|
|
999
|
+
className: chip(state.paint.type === "gradient"),
|
|
1000
|
+
"aria-pressed": state.paint.type === "gradient",
|
|
1001
|
+
title: "Paint that changes colour as the stroke goes",
|
|
1002
|
+
onClick: () => attempt(() => state.paint.type === "gradient" ? controller.setColor(color) : controller.setPaint(import_paint_editor9.DEFAULT_PAINT_GRADIENT)),
|
|
1003
|
+
children: "Gradient"
|
|
1004
|
+
}
|
|
1005
|
+
)
|
|
868
1006
|
] }),
|
|
869
|
-
/* @__PURE__ */ (0,
|
|
870
|
-
|
|
871
|
-
/* @__PURE__ */ (0,
|
|
1007
|
+
state.paint.type === "gradient" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PaintGradientEditor, { value: state.paint, colors: { paint: color, secondary: state.secondaryColor }, onChange: (g2) => attempt(() => controller.setPaint(g2)) }),
|
|
1008
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-2", children: [
|
|
1009
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "w-16 text-slate-400", children: "Saturation" }),
|
|
1010
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "range", "aria-label": "Saturation", min: 0, max: 1, step: 0.01, value: hsv.s, onChange: (e) => setHsv(Number(e.target.value), hsv.v), className: "flex-1 accent-indigo-500" })
|
|
872
1011
|
] }),
|
|
873
|
-
/* @__PURE__ */ (0,
|
|
874
|
-
/* @__PURE__ */ (0,
|
|
875
|
-
/* @__PURE__ */ (0,
|
|
876
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: chip(wheel === "rgb"), "aria-pressed": wheel === "rgb", onClick: () => setWheel("rgb"), children: "RGB" })
|
|
1012
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-2", children: [
|
|
1013
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "w-16 text-slate-400", children: "Value" }),
|
|
1014
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "range", "aria-label": "Value", min: 0, max: 1, step: 0.01, value: hsv.v, onChange: (e) => setHsv(hsv.s, Number(e.target.value)), className: "flex-1 accent-indigo-500" })
|
|
877
1015
|
] }),
|
|
878
|
-
/* @__PURE__ */ (0,
|
|
879
|
-
|
|
880
|
-
/* @__PURE__ */ (0,
|
|
1016
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
1017
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("select", { "aria-label": "Harmony", value: kind, onChange: (e) => setKind(e.target.value), className: "rounded bg-slate-800 px-1 py-0.5", children: import_paint_editor8.HARMONY_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: k, children: k }, k)) }),
|
|
1018
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: chip(wheel === "ryb"), "aria-pressed": wheel === "ryb", onClick: () => setWheel("ryb"), children: "RYB" }),
|
|
1019
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: chip(wheel === "rgb"), "aria-pressed": wheel === "rgb", onClick: () => setWheel("rgb"), children: "RGB" })
|
|
881
1020
|
] }),
|
|
882
|
-
/* @__PURE__ */ (0,
|
|
883
|
-
|
|
1021
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-wrap gap-1", "aria-label": "Harmony colours", children: [
|
|
1022
|
+
harmony.map((h, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", "aria-label": `Use ${h.color}`, title: h.color, className: "h-6 w-6 rounded ring-1 ring-slate-700", style: { background: h.color }, onClick: () => controller.setColor(h.color) }, i)),
|
|
1023
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, onClick: () => harmony.forEach((h) => controller.addSwatch(h.color)), children: "+ swatches" })
|
|
1024
|
+
] }),
|
|
1025
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-wrap gap-1", role: "listbox", "aria-label": "Swatches", children: [
|
|
1026
|
+
state.swatches.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
884
1027
|
"button",
|
|
885
1028
|
{
|
|
886
1029
|
type: "button",
|
|
887
1030
|
role: "option",
|
|
888
|
-
"aria-selected": s.color === color,
|
|
889
|
-
"aria-label": s.name ?? s.color,
|
|
890
|
-
title: `${s.name ?? s.color} (right-click to remove)`,
|
|
1031
|
+
"aria-selected": s.paint ? state.paint === s.paint : state.paint.type === "color" && s.color === color,
|
|
1032
|
+
"aria-label": s.name ?? (s.paint ? "Gradient" : s.color),
|
|
1033
|
+
title: `${s.name ?? (s.paint ? "Gradient" : s.color)} (right-click to remove)`,
|
|
891
1034
|
className: "h-5 w-5 rounded ring-1 ring-slate-700",
|
|
892
|
-
style: { background: s.color },
|
|
893
|
-
onClick: () => controller.setColor(s.color),
|
|
1035
|
+
style: { background: s.paint ? (0, import_paint_editor9.paintSourceCss)(s.paint, { paint: color, secondary: state.secondaryColor }) : s.color },
|
|
1036
|
+
onClick: () => attempt(() => s.paint ? controller.setPaint(s.paint) : controller.setColor(s.color)),
|
|
894
1037
|
onContextMenu: (e) => {
|
|
895
1038
|
e.preventDefault();
|
|
896
1039
|
controller.removeSwatch(i);
|
|
@@ -898,15 +1041,15 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
|
|
|
898
1041
|
},
|
|
899
1042
|
i
|
|
900
1043
|
)),
|
|
901
|
-
/* @__PURE__ */ (0,
|
|
1044
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, "aria-label": "Add swatch", onClick: () => attempt(() => controller.addSwatch(state.paint)), children: "+" })
|
|
902
1045
|
] }),
|
|
903
|
-
mixingPad && /* @__PURE__ */ (0,
|
|
904
|
-
/* @__PURE__ */ (0,
|
|
905
|
-
/* @__PURE__ */ (0,
|
|
906
|
-
["paint", "knife", "pick"].map((t) => /* @__PURE__ */ (0,
|
|
907
|
-
/* @__PURE__ */ (0,
|
|
1046
|
+
mixingPad && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-1", "data-testid": "mixing-pad", children: [
|
|
1047
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
1048
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-slate-400", children: "Mixing pad" }),
|
|
1049
|
+
["paint", "knife", "pick"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", "aria-pressed": padTool === t, className: chip(padTool === t), onClick: () => setPadTool(t), children: t === "paint" ? "Paint" : t === "knife" ? "Knife" : "Pick" }, t)),
|
|
1050
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, onClick: () => controller.clearMixingPad(), children: "Clear" })
|
|
908
1051
|
] }),
|
|
909
|
-
/* @__PURE__ */ (0,
|
|
1052
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
910
1053
|
"div",
|
|
911
1054
|
{
|
|
912
1055
|
role: "application",
|
|
@@ -915,7 +1058,7 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
|
|
|
915
1058
|
onPointerDown: (e) => {
|
|
916
1059
|
const p = padPoint(e);
|
|
917
1060
|
if (padTool === "pick") {
|
|
918
|
-
attempt(() => controller.mixingPadPick(p.x, p.y, { setColor: true, radius: (0,
|
|
1061
|
+
attempt(() => controller.mixingPadPick(p.x, p.y, { setColor: true, radius: (0, import_paint_editor8.padSampleRadius)(sample) }));
|
|
919
1062
|
return;
|
|
920
1063
|
}
|
|
921
1064
|
padPoints.current = [p];
|
|
@@ -927,20 +1070,20 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
|
|
|
927
1070
|
padPoints.current = null;
|
|
928
1071
|
if (pts?.length) attempt(() => controller.mixingPadStroke(pts, { tool: padTool === "knife" ? "knife" : "paint" }));
|
|
929
1072
|
},
|
|
930
|
-
children: /* @__PURE__ */ (0,
|
|
1073
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PixelCanvas, { pixels: pad, label: "Mixing pad pixels", className: "w-full rounded", style: { aspectRatio: `${import_paint_editor8.MIXING_PAD_WIDTH} / ${import_paint_editor8.MIXING_PAD_HEIGHT}` } })
|
|
931
1074
|
}
|
|
932
1075
|
),
|
|
933
|
-
/* @__PURE__ */ (0,
|
|
934
|
-
/* @__PURE__ */ (0,
|
|
935
|
-
/* @__PURE__ */ (0,
|
|
1076
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex flex-wrap items-center gap-1", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-1", children: [
|
|
1077
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-slate-400", children: "Sample" }),
|
|
1078
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("select", { "aria-label": "Pick sample size", value: sample, onChange: (e) => setSample(Number(e.target.value)), className: "rounded bg-slate-800 px-1 py-0.5", children: import_paint_editor8.PAD_SAMPLE_SIZES.map((n) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: n, children: n === 1 ? "1 px" : `${n}\xD7${n}` }, n)) })
|
|
936
1079
|
] }) }),
|
|
937
|
-
/* @__PURE__ */ (0,
|
|
938
|
-
/* @__PURE__ */ (0,
|
|
939
|
-
/* @__PURE__ */ (0,
|
|
940
|
-
/* @__PURE__ */ (0,
|
|
941
|
-
/* @__PURE__ */ (0,
|
|
942
|
-
/* @__PURE__ */ (0,
|
|
943
|
-
/* @__PURE__ */ (0,
|
|
1080
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
1081
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "h-4 w-4 rounded ring-1 ring-slate-700", style: { background: color } }),
|
|
1082
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "number", "aria-label": "Paint colour parts", min: 0, max: 20, value: parts[0], onChange: (e) => setParts([Number(e.target.value), parts[1]]), className: "w-10 rounded bg-slate-800 px-1 py-0.5" }),
|
|
1083
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-slate-500", children: ":" }),
|
|
1084
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "number", "aria-label": "Second colour parts", min: 0, max: 20, value: parts[1], onChange: (e) => setParts([parts[0], Number(e.target.value)]), className: "w-10 rounded bg-slate-800 px-1 py-0.5" }),
|
|
1085
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "h-4 w-4 rounded ring-1 ring-slate-700", style: { background: state.secondaryColor } }),
|
|
1086
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
944
1087
|
"button",
|
|
945
1088
|
{
|
|
946
1089
|
type: "button",
|
|
@@ -959,23 +1102,23 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
|
|
|
959
1102
|
}
|
|
960
1103
|
)
|
|
961
1104
|
] }),
|
|
962
|
-
/* @__PURE__ */ (0,
|
|
963
|
-
/* @__PURE__ */ (0,
|
|
964
|
-
/* @__PURE__ */ (0,
|
|
1105
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
1106
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { value: padName, placeholder: "Pad name", "aria-label": "Pad name", onChange: (e) => setPadName(e.target.value), className: "w-24 rounded bg-slate-800 px-1.5 py-0.5" }),
|
|
1107
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => (controller.saveMixingPad(padName || void 0), setPadName(""))), children: "Keep pad" })
|
|
965
1108
|
] }),
|
|
966
|
-
state.savedMixingPads.length > 0 && /* @__PURE__ */ (0,
|
|
967
|
-
/* @__PURE__ */ (0,
|
|
968
|
-
/* @__PURE__ */ (0,
|
|
1109
|
+
state.savedMixingPads.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { className: "flex flex-col gap-0.5", "aria-label": "Kept pads", children: state.savedMixingPads.map((p) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { className: "flex items-center gap-1", children: [
|
|
1110
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: `${button} flex-1 text-left`, onClick: () => attempt(() => controller.loadMixingPad(p.id)), children: p.name }),
|
|
1111
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, "aria-label": `Remove ${p.name}`, onClick: () => attempt(() => controller.removeMixingPad(p.id)), children: "\xD7" })
|
|
969
1112
|
] }, p.id)) })
|
|
970
1113
|
] }),
|
|
971
|
-
/* @__PURE__ */ (0,
|
|
1114
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorLine, { error })
|
|
972
1115
|
] });
|
|
973
1116
|
};
|
|
974
1117
|
|
|
975
1118
|
// src/paint-editor/PaintLayersPanel.tsx
|
|
976
|
-
var
|
|
977
|
-
var
|
|
978
|
-
var
|
|
1119
|
+
var import_react9 = require("react");
|
|
1120
|
+
var import_paint_editor10 = require("@pixerate/editor/paint-editor");
|
|
1121
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
979
1122
|
function siblingIndex(layers, l) {
|
|
980
1123
|
const sibs = layers.filter((x) => x.parentId === l.parentId).reverse();
|
|
981
1124
|
return { index: sibs.findIndex((x) => x.id === l.id), count: sibs.length };
|
|
@@ -983,7 +1126,7 @@ function siblingIndex(layers, l) {
|
|
|
983
1126
|
var PaintLayersPanel = ({ controller, className }) => {
|
|
984
1127
|
usePaintEditorState(controller);
|
|
985
1128
|
const [error, attempt] = useAttempt();
|
|
986
|
-
const [renaming, setRenaming] = (0,
|
|
1129
|
+
const [renaming, setRenaming] = (0, import_react9.useState)(null);
|
|
987
1130
|
const layers = controller.listLayers();
|
|
988
1131
|
const active = layers.find((l) => l.active) ?? null;
|
|
989
1132
|
const move = (delta) => {
|
|
@@ -992,17 +1135,17 @@ var PaintLayersPanel = ({ controller, className }) => {
|
|
|
992
1135
|
const to = Math.min(count - 1, Math.max(0, index + delta));
|
|
993
1136
|
if (to !== index) attempt(() => controller.moveLayer(active.id, to));
|
|
994
1137
|
};
|
|
995
|
-
return /* @__PURE__ */ (0,
|
|
996
|
-
/* @__PURE__ */ (0,
|
|
997
|
-
/* @__PURE__ */ (0,
|
|
998
|
-
/* @__PURE__ */ (0,
|
|
999
|
-
/* @__PURE__ */ (0,
|
|
1000
|
-
/* @__PURE__ */ (0,
|
|
1001
|
-
/* @__PURE__ */ (0,
|
|
1002
|
-
/* @__PURE__ */ (0,
|
|
1003
|
-
/* @__PURE__ */ (0,
|
|
1138
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: `${panel} ${className ?? ""}`, "data-testid": "paint-layers-panel", children: [
|
|
1139
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
|
|
1140
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => controller.addLayer()), children: "New layer" }),
|
|
1141
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => controller.addLayer({ type: "group" })), children: "New group" }),
|
|
1142
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button, disabled: !active, onClick: () => active && attempt(() => controller.duplicateLayer(active.id)), children: "Duplicate" }),
|
|
1143
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button, disabled: !active, "aria-label": "Move layer up", onClick: () => move(1), children: "\u2191" }),
|
|
1144
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button, disabled: !active, "aria-label": "Move layer down", onClick: () => move(-1), children: "\u2193" }),
|
|
1145
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button, disabled: !active, onClick: () => active && attempt(() => controller.mergeDown(active.id)), children: "Merge down" }),
|
|
1146
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button, disabled: !active || layers.length < 2, onClick: () => active && attempt(() => controller.removeLayer(active.id)), children: "Delete" })
|
|
1004
1147
|
] }),
|
|
1005
|
-
/* @__PURE__ */ (0,
|
|
1148
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { role: "tree", "aria-label": "Layers", className: "flex flex-col gap-0.5", children: layers.map((l) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1006
1149
|
"li",
|
|
1007
1150
|
{
|
|
1008
1151
|
role: "treeitem",
|
|
@@ -1011,7 +1154,7 @@ var PaintLayersPanel = ({ controller, className }) => {
|
|
|
1011
1154
|
style: { paddingLeft: 6 + l.depth * 12 },
|
|
1012
1155
|
onClick: () => controller.setActiveLayer(l.id),
|
|
1013
1156
|
children: [
|
|
1014
|
-
/* @__PURE__ */ (0,
|
|
1157
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1015
1158
|
"button",
|
|
1016
1159
|
{
|
|
1017
1160
|
type: "button",
|
|
@@ -1024,7 +1167,7 @@ var PaintLayersPanel = ({ controller, className }) => {
|
|
|
1024
1167
|
children: l.visible ? "\u25CF" : "\u25CB"
|
|
1025
1168
|
}
|
|
1026
1169
|
),
|
|
1027
|
-
renaming === l.id ? /* @__PURE__ */ (0,
|
|
1170
|
+
renaming === l.id ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1028
1171
|
"input",
|
|
1029
1172
|
{
|
|
1030
1173
|
autoFocus: true,
|
|
@@ -1041,19 +1184,19 @@ var PaintLayersPanel = ({ controller, className }) => {
|
|
|
1041
1184
|
if (e.key === "Escape") setRenaming(null);
|
|
1042
1185
|
}
|
|
1043
1186
|
}
|
|
1044
|
-
) : /* @__PURE__ */ (0,
|
|
1187
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "flex-1 truncate", onDoubleClick: () => setRenaming(l.id), children: [
|
|
1045
1188
|
l.type === "group" ? "\u25B8 " : "",
|
|
1046
1189
|
l.name
|
|
1047
1190
|
] }),
|
|
1048
|
-
l.locks.pixels || l.locks.all ? /* @__PURE__ */ (0,
|
|
1191
|
+
l.locks.pixels || l.locks.all ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { title: "Locked", children: "\u{1F512}" }) : null
|
|
1049
1192
|
]
|
|
1050
1193
|
},
|
|
1051
1194
|
l.id
|
|
1052
1195
|
)) }),
|
|
1053
|
-
active && /* @__PURE__ */ (0,
|
|
1054
|
-
/* @__PURE__ */ (0,
|
|
1055
|
-
/* @__PURE__ */ (0,
|
|
1056
|
-
/* @__PURE__ */ (0,
|
|
1196
|
+
active && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
|
|
1197
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center gap-2", children: [
|
|
1198
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "w-14 text-slate-400", children: "Opacity" }),
|
|
1199
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1057
1200
|
"input",
|
|
1058
1201
|
{
|
|
1059
1202
|
type: "range",
|
|
@@ -1067,25 +1210,25 @@ var PaintLayersPanel = ({ controller, className }) => {
|
|
|
1067
1210
|
}
|
|
1068
1211
|
)
|
|
1069
1212
|
] }),
|
|
1070
|
-
/* @__PURE__ */ (0,
|
|
1071
|
-
/* @__PURE__ */ (0,
|
|
1072
|
-
/* @__PURE__ */ (0,
|
|
1073
|
-
active.type === "group" && /* @__PURE__ */ (0,
|
|
1074
|
-
|
|
1213
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center gap-2", children: [
|
|
1214
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "w-14 text-slate-400", children: "Blend" }),
|
|
1215
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("select", { "aria-label": "Layer blend", value: active.blend, onChange: (e) => attempt(() => controller.updateLayer(active.id, { blend: e.target.value })), className: "flex-1 rounded bg-slate-800 px-1 py-0.5", children: [
|
|
1216
|
+
active.type === "group" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "pass-through", children: "Pass through" }),
|
|
1217
|
+
import_paint_editor10.BLEND_MENU_GROUPS.map((g) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("optgroup", { label: g.label, children: g.modes.map((m) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: m.id, title: m.description, children: m.label }, m.id)) }, g.label))
|
|
1075
1218
|
] })
|
|
1076
1219
|
] }),
|
|
1077
|
-
/* @__PURE__ */ (0,
|
|
1078
|
-
["pixels", "transparency"].map((k) => /* @__PURE__ */ (0,
|
|
1079
|
-
/* @__PURE__ */ (0,
|
|
1220
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex gap-1", children: [
|
|
1221
|
+
["pixels", "transparency"].map((k) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", "aria-pressed": Boolean(active.locks[k]), className: chip(Boolean(active.locks[k])), onClick: () => attempt(() => controller.updateLayer(active.id, { locks: { [k]: !active.locks[k] } })), children: k === "pixels" ? "Lock pixels" : "Lock transparency" }, k)),
|
|
1222
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", "aria-pressed": active.clipped, className: chip(active.clipped), onClick: () => attempt(() => controller.updateLayer(active.id, { clipped: !active.clipped })), children: "Clip" })
|
|
1080
1223
|
] })
|
|
1081
1224
|
] }),
|
|
1082
|
-
/* @__PURE__ */ (0,
|
|
1225
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorLine, { error })
|
|
1083
1226
|
] });
|
|
1084
1227
|
};
|
|
1085
1228
|
|
|
1086
1229
|
// src/paint-editor/WetPanel.tsx
|
|
1087
|
-
var
|
|
1088
|
-
var
|
|
1230
|
+
var import_paint_editor11 = require("@pixerate/editor/paint-editor");
|
|
1231
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1089
1232
|
var FLOW_LABELS = { none: "None", linear: "One way", swirl: "Swirl", radial: "Outwards", turbulent: "Eddies" };
|
|
1090
1233
|
var WetPanel = ({ controller, className }) => {
|
|
1091
1234
|
const state = usePaintEditorState(controller);
|
|
@@ -1094,31 +1237,31 @@ var WetPanel = ({ controller, className }) => {
|
|
|
1094
1237
|
const layerId = state.document.activeLayerId;
|
|
1095
1238
|
const info = layerId ? controller.wetLayerInfo(layerId) : null;
|
|
1096
1239
|
const backend = controller.wetState();
|
|
1097
|
-
return /* @__PURE__ */ (0,
|
|
1098
|
-
/* @__PURE__ */ (0,
|
|
1099
|
-
/* @__PURE__ */ (0,
|
|
1100
|
-
/* @__PURE__ */ (0,
|
|
1240
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `${panel} ${className ?? ""}`, "data-testid": "wet-panel", children: [
|
|
1241
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
1242
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: wet.wet ? "text-sky-300" : "text-slate-400", "data-testid": "wet-status", children: wet.wet ? wet.paused ? "Wet (paused)" : "Wet" : "Dry" }),
|
|
1243
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-[10px] text-slate-500", children: [
|
|
1101
1244
|
"step ",
|
|
1102
1245
|
wet.clock
|
|
1103
1246
|
] })
|
|
1104
1247
|
] }),
|
|
1105
|
-
/* @__PURE__ */ (0,
|
|
1106
|
-
/* @__PURE__ */ (0,
|
|
1107
|
-
/* @__PURE__ */ (0,
|
|
1108
|
-
/* @__PURE__ */ (0,
|
|
1109
|
-
/* @__PURE__ */ (0,
|
|
1110
|
-
/* @__PURE__ */ (0,
|
|
1248
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
|
|
1249
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: button, disabled: !wet.layerIds.length, onClick: () => attempt(async () => (await controller.wetIdle(), controller.dryPaint())), children: "Dry now" }),
|
|
1250
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: chip(wet.paused), "aria-pressed": wet.paused, onClick: () => controller.setWetPaused(!wet.paused), children: wet.paused ? "Resume" : "Pause" }),
|
|
1251
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: chip(wet.overlay), "aria-pressed": wet.overlay, onClick: () => controller.setWaterOverlay(!wet.overlay), children: "Water" }),
|
|
1252
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.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" }),
|
|
1253
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: button, disabled: !layerId, onClick: () => attempt(async () => (await controller.wetIdle(), controller.wetPaper({}))), children: "Wet paper" })
|
|
1111
1254
|
] }),
|
|
1112
|
-
/* @__PURE__ */ (0,
|
|
1113
|
-
|
|
1114
|
-
info && /* @__PURE__ */ (0,
|
|
1115
|
-
/* @__PURE__ */ (0,
|
|
1116
|
-
|
|
1117
|
-
/* @__PURE__ */ (0,
|
|
1118
|
-
/* @__PURE__ */ (0,
|
|
1119
|
-
/* @__PURE__ */ (0,
|
|
1255
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaintSlider, { control: import_paint_editor11.WET_SPEED_SLIDER, value: wet.speed, onChange: (v) => controller.setWetSpeed(v) }),
|
|
1256
|
+
import_paint_editor11.WET_TILT_SLIDERS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor11.readPath)(wet.tilt, c.path), onCommit: (v) => attempt(async () => (await controller.wetIdle(), controller.setCanvasTilt({ [c.path]: v }))) }, c.path)),
|
|
1257
|
+
info && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-1.5", "data-testid": "wet-layer", children: [
|
|
1258
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-medium text-slate-300", children: "This layer" }),
|
|
1259
|
+
import_paint_editor11.WET_PARAM_SLIDERS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor11.readPath)(info.params, c.path), onCommit: (v) => attempt(async () => (await controller.wetIdle(), controller.setWetParams(layerId, { [c.path]: v }))) }, c.path)),
|
|
1260
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
1261
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-slate-400", children: "Flow map" }),
|
|
1262
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.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_editor11.WET_FLOW_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: k, children: FLOW_LABELS[k] }, k)) })
|
|
1120
1263
|
] }),
|
|
1121
|
-
info.flow.kind !== "none" && /* @__PURE__ */ (0,
|
|
1264
|
+
info.flow.kind !== "none" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1122
1265
|
PaintSlider,
|
|
1123
1266
|
{
|
|
1124
1267
|
control: { path: "strength", label: "Flow strength", min: 0, max: 1, step: 0.01 },
|
|
@@ -1127,73 +1270,78 @@ var WetPanel = ({ controller, className }) => {
|
|
|
1127
1270
|
}
|
|
1128
1271
|
)
|
|
1129
1272
|
] }),
|
|
1130
|
-
backend.message && /* @__PURE__ */ (0,
|
|
1131
|
-
backend.backend && /* @__PURE__ */ (0,
|
|
1132
|
-
/* @__PURE__ */ (0,
|
|
1273
|
+
backend.message && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-[10px] text-amber-300", "data-testid": "wet-message", children: backend.message }),
|
|
1274
|
+
backend.backend && /* @__PURE__ */ (0, import_jsx_runtime10.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" }),
|
|
1275
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
1276
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-slate-400", children: "Engine" }),
|
|
1277
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("select", { "aria-label": "Wet engine", value: wet.gpu, onChange: (e) => attempt(() => controller.setWetEngine({ gpu: e.target.value })), className: "rounded bg-slate-800 px-1 py-0.5", children: import_paint_editor11.WET_GPU_OPTIONS.map((o) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: o.value, children: o.label }, o.value)) })
|
|
1278
|
+
] }),
|
|
1279
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: `text-[10px] ${backend.engine.state === "active" ? "text-emerald-300" : "text-slate-500"}`, "data-testid": "wet-engine", children: (0, import_paint_editor11.wetEngineSummary)(backend.engine) }),
|
|
1280
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ErrorLine, { error })
|
|
1133
1281
|
] });
|
|
1134
1282
|
};
|
|
1135
1283
|
|
|
1136
1284
|
// src/paint-editor/StickerPanel.tsx
|
|
1137
|
-
var
|
|
1138
|
-
var
|
|
1139
|
-
var
|
|
1285
|
+
var import_react10 = require("react");
|
|
1286
|
+
var import_paint_editor12 = require("@pixerate/editor/paint-editor");
|
|
1287
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1140
1288
|
var StickerPanel = ({ controller, className }) => {
|
|
1141
1289
|
const state = usePaintEditorState(controller);
|
|
1142
1290
|
const [error, attempt] = useAttempt();
|
|
1143
|
-
const [picked, setPicked] = (0,
|
|
1291
|
+
const [picked, setPicked] = (0, import_react10.useState)(null);
|
|
1144
1292
|
const layerId = state.document.activeLayerId;
|
|
1145
1293
|
const stickers = layerId ? state.document.stickers?.[layerId] ?? [] : [];
|
|
1146
1294
|
if (!stickers.length) return null;
|
|
1147
1295
|
const sel = stickers.find((s) => s.id === picked) ?? null;
|
|
1148
|
-
return /* @__PURE__ */ (0,
|
|
1149
|
-
/* @__PURE__ */ (0,
|
|
1296
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `${panel} ${className ?? ""}`, "data-testid": "sticker-panel", children: [
|
|
1297
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "font-medium text-slate-300", children: [
|
|
1150
1298
|
"Stickers (",
|
|
1151
1299
|
stickers.length,
|
|
1152
1300
|
")"
|
|
1153
1301
|
] }),
|
|
1154
|
-
/* @__PURE__ */ (0,
|
|
1155
|
-
sel && /* @__PURE__ */ (0,
|
|
1156
|
-
/* @__PURE__ */ (0,
|
|
1157
|
-
/* @__PURE__ */ (0,
|
|
1158
|
-
/* @__PURE__ */ (0,
|
|
1302
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.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_runtime11.jsx)("button", { type: "button", role: "option", "aria-selected": s.id === picked, title: `${import_paint_editor12.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)) }),
|
|
1303
|
+
sel && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-1.5", "data-testid": "sticker-edit", children: [
|
|
1304
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
|
|
1305
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-slate-400", children: "Stamp" }),
|
|
1306
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.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_editor12.STAMP_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: k, children: import_paint_editor12.STAMP_LABELS[k] }, k)) })
|
|
1159
1307
|
] }),
|
|
1160
|
-
|
|
1161
|
-
/* @__PURE__ */ (0,
|
|
1162
|
-
/* @__PURE__ */ (0,
|
|
1163
|
-
/* @__PURE__ */ (0,
|
|
1308
|
+
import_paint_editor12.STICKER_SLIDERS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor12.readPath)(sel, c.path), onCommit: (v) => attempt(() => controller.updateSticker(layerId, sel.id, { [c.path]: v })) }, c.path)),
|
|
1309
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { className: "flex items-center justify-between", children: [
|
|
1310
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-slate-400", children: "Colour" }),
|
|
1311
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.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
1312
|
] }),
|
|
1165
|
-
/* @__PURE__ */ (0,
|
|
1166
|
-
/* @__PURE__ */ (0,
|
|
1167
|
-
/* @__PURE__ */ (0,
|
|
1168
|
-
/* @__PURE__ */ (0,
|
|
1313
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
|
|
1314
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => controller.arrangeStickers(layerId, [sel.id], "front")), children: "To front" }),
|
|
1315
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => controller.arrangeStickers(layerId, [sel.id], "back")), children: "To back" }),
|
|
1316
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => (controller.removeStickers(layerId, [sel.id]), setPicked(null))), children: "Delete" })
|
|
1169
1317
|
] })
|
|
1170
1318
|
] }),
|
|
1171
|
-
/* @__PURE__ */ (0,
|
|
1319
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ErrorLine, { error })
|
|
1172
1320
|
] });
|
|
1173
1321
|
};
|
|
1174
1322
|
|
|
1175
1323
|
// src/paint-editor/PaintEditor.tsx
|
|
1176
|
-
var
|
|
1177
|
-
var
|
|
1324
|
+
var import_react11 = require("react");
|
|
1325
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1178
1326
|
var PaintEditor = ({ controller, fileName, height = 720, presence, className }) => {
|
|
1179
|
-
const [tool, setTool] = (0,
|
|
1180
|
-
const [tab, setTab] = (0,
|
|
1181
|
-
const [error, setError] = (0,
|
|
1182
|
-
return /* @__PURE__ */ (0,
|
|
1183
|
-
/* @__PURE__ */ (0,
|
|
1184
|
-
/* @__PURE__ */ (0,
|
|
1185
|
-
/* @__PURE__ */ (0,
|
|
1186
|
-
/* @__PURE__ */ (0,
|
|
1187
|
-
/* @__PURE__ */ (0,
|
|
1188
|
-
error && /* @__PURE__ */ (0,
|
|
1327
|
+
const [tool, setTool] = (0, import_react11.useState)("brush");
|
|
1328
|
+
const [tab, setTab] = (0, import_react11.useState)("color");
|
|
1329
|
+
const [error, setError] = (0, import_react11.useState)(null);
|
|
1330
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.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: [
|
|
1331
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PaintToolbar, { controller, tool, onToolChange: setTool, fileName }),
|
|
1332
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-h-0 flex-1 gap-2", children: [
|
|
1333
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BrushPanel, { controller, className: "w-60 shrink-0 overflow-y-auto pr-1" }),
|
|
1334
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
1335
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PaintCanvas, { controller, tool, onError: setError, presence, className: "flex-1 rounded-lg" }),
|
|
1336
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { role: "alert", className: "mt-1 text-[11px] text-rose-400", children: error })
|
|
1189
1337
|
] }),
|
|
1190
|
-
/* @__PURE__ */ (0,
|
|
1191
|
-
/* @__PURE__ */ (0,
|
|
1192
|
-
tab === "color" && /* @__PURE__ */ (0,
|
|
1193
|
-
tab === "wet" && /* @__PURE__ */ (0,
|
|
1194
|
-
tab === "paper" && /* @__PURE__ */ (0,
|
|
1195
|
-
tab === "layers" && /* @__PURE__ */ (0,
|
|
1196
|
-
tab === "layers" && /* @__PURE__ */ (0,
|
|
1338
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex w-64 shrink-0 flex-col gap-2 overflow-y-auto pr-1", children: [
|
|
1339
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex gap-1", role: "tablist", "aria-label": "Paint panels", children: ["color", "wet", "paper", "layers"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime12.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)) }),
|
|
1340
|
+
tab === "color" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ColorPanel, { controller }),
|
|
1341
|
+
tab === "wet" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(WetPanel, { controller }),
|
|
1342
|
+
tab === "paper" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PaperPanel, { controller }),
|
|
1343
|
+
tab === "layers" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PaintLayersPanel, { controller }),
|
|
1344
|
+
tab === "layers" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(StickerPanel, { controller })
|
|
1197
1345
|
] })
|
|
1198
1346
|
] })
|
|
1199
1347
|
] });
|
|
@@ -1204,6 +1352,7 @@ var PaintEditor = ({ controller, fileName, height = 720, presence, className })
|
|
|
1204
1352
|
ColorPanel,
|
|
1205
1353
|
PaintCanvas,
|
|
1206
1354
|
PaintEditor,
|
|
1355
|
+
PaintGradientEditor,
|
|
1207
1356
|
PaintLayersPanel,
|
|
1208
1357
|
PaintPresenceLayer,
|
|
1209
1358
|
PaintSlider,
|