@pixerate/editor-react 0.68.1 → 0.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/paint-editor/index.cjs +133 -5
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +37 -5
- package/dist/paint-editor/index.d.ts +37 -5
- package/dist/paint-editor/index.js +151 -11
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/vector/index.cjs +302 -152
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +23 -2
- package/dist/vector/index.d.ts +23 -2
- package/dist/vector/index.js +285 -136
- package/dist/vector/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PaintEditorController, PaintEditorState, PaintEditorOptions, PaintTool, SliderControl } from '@pixerate/editor/paint-editor';
|
|
1
|
+
import { PaintEditorController, PaintEditorState, PaintDirectorOptions, PaintPresence, PaintDirector, PaintEditorOptions, PaintTool, PaintView, SliderControl } from '@pixerate/editor/paint-editor';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
|
|
4
4
|
interface UsePaintEditorReturn {
|
|
@@ -13,6 +13,15 @@ interface UsePaintEditorReturn {
|
|
|
13
13
|
declare function usePaintEditor(options?: PaintEditorOptions | PaintEditorController): UsePaintEditorReturn;
|
|
14
14
|
/** Subscribes to a controller's state. */
|
|
15
15
|
declare function usePaintEditorState(controller: PaintEditorController): PaintEditorState;
|
|
16
|
+
/**
|
|
17
|
+
* A `PaintPresence` and `PaintDirector` for a paint editor: pass `presence` to `PaintEditor` (or
|
|
18
|
+
* `PaintCanvas`) and `director` to `createPaintEditorToolProvider`, so agent strokes are painted
|
|
19
|
+
* live by a cursor on the canvas. Options are read on the first render.
|
|
20
|
+
*/
|
|
21
|
+
declare function usePaintDirector(controller: PaintEditorController, options?: Omit<PaintDirectorOptions, 'clock'>): {
|
|
22
|
+
presence: PaintPresence;
|
|
23
|
+
director: PaintDirector;
|
|
24
|
+
};
|
|
16
25
|
|
|
17
26
|
interface PaintCanvasProps {
|
|
18
27
|
controller: PaintEditorController;
|
|
@@ -24,6 +33,8 @@ interface PaintCanvasProps {
|
|
|
24
33
|
fitOnMount?: boolean;
|
|
25
34
|
/** Called with a refusal (hidden or locked layer). */
|
|
26
35
|
onError?: (message: string | null) => void;
|
|
36
|
+
/** Agent cursors to draw over the canvas (see `PaintDirector` and `usePaintDirector`). */
|
|
37
|
+
presence?: PaintPresence;
|
|
27
38
|
className?: string;
|
|
28
39
|
}
|
|
29
40
|
/**
|
|
@@ -34,6 +45,22 @@ interface PaintCanvasProps {
|
|
|
34
45
|
* rotation. Guides and floating reference images are drawn on top.
|
|
35
46
|
*/
|
|
36
47
|
declare const PaintCanvas: React__default.FC<PaintCanvasProps>;
|
|
48
|
+
/**
|
|
49
|
+
* Agent cursors and notes over the canvas. Positions are document pixels, placed through the view
|
|
50
|
+
* (zoom, pan, rotation, mirroring), while the cursors themselves stay upright and unscaled.
|
|
51
|
+
*/
|
|
52
|
+
declare const PaintPresenceLayer: React__default.FC<{
|
|
53
|
+
presence: PaintPresence;
|
|
54
|
+
view: PaintView;
|
|
55
|
+
canvas: {
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
};
|
|
59
|
+
viewport: {
|
|
60
|
+
width: number;
|
|
61
|
+
height: number;
|
|
62
|
+
};
|
|
63
|
+
}>;
|
|
37
64
|
|
|
38
65
|
interface PaintToolbarProps {
|
|
39
66
|
controller: PaintEditorController;
|
|
@@ -60,8 +87,10 @@ interface BrushPanelProps {
|
|
|
60
87
|
/**
|
|
61
88
|
* The brush library (thumbnails rendered from a test stroke on the current paper, grouped by
|
|
62
89
|
* category) and the active brush's settings: size, opacity, flow, hardness, spacing, roundness,
|
|
63
|
-
* angle, paper grain, position and pressure smoothing, dynamics
|
|
64
|
-
*
|
|
90
|
+
* angle, paper grain, position and pressure smoothing, dynamics, rotation mode and blend mode;
|
|
91
|
+
* then the engine's own controls (`brushEngineControls`): blender strength and blend distance,
|
|
92
|
+
* smudge strength and length, pick-up and mix ratios, RGB / pigment mixing, dirty brush,
|
|
93
|
+
* multi-colour and the brush load. Save the edited brush, import and export libraries as JSON.
|
|
65
94
|
*/
|
|
66
95
|
declare const BrushPanel: React__default.FC<BrushPanelProps>;
|
|
67
96
|
|
|
@@ -87,7 +116,8 @@ interface ColorPanelProps {
|
|
|
87
116
|
/**
|
|
88
117
|
* Colour: a hue wheel (painter's RYB or RGB) with the chosen harmony marked on it, saturation and
|
|
89
118
|
* value, the paint and second colours, harmony chips, swatches, and a mixing pad to paint on,
|
|
90
|
-
* smear with a palette knife (pigment mixing) and pick from
|
|
119
|
+
* smear with a palette knife (pigment mixing) and pick from (with a sample size), mix the paint
|
|
120
|
+
* and second colours in measured parts on, and keep by name.
|
|
91
121
|
*/
|
|
92
122
|
declare const ColorPanel: React__default.FC<ColorPanelProps>;
|
|
93
123
|
|
|
@@ -108,6 +138,8 @@ interface PaintEditorProps {
|
|
|
108
138
|
fileName?: string;
|
|
109
139
|
/** Height of the editor (default 720 px). */
|
|
110
140
|
height?: number | string;
|
|
141
|
+
/** Agent cursors to draw over the canvas (see `usePaintDirector`). */
|
|
142
|
+
presence?: PaintPresence;
|
|
111
143
|
className?: string;
|
|
112
144
|
}
|
|
113
145
|
/**
|
|
@@ -127,4 +159,4 @@ declare const PaintSlider: React__default.FC<{
|
|
|
127
159
|
onCommit?: (v: number) => void;
|
|
128
160
|
}>;
|
|
129
161
|
|
|
130
|
-
export { BrushPanel, type BrushPanelProps, ColorPanel, type ColorPanelProps, PaintCanvas, type PaintCanvasProps, PaintEditor, type PaintEditorProps, PaintLayersPanel, type PaintLayersPanelProps, PaintSlider, PaintToolbar, type PaintToolbarProps, PaperPanel, type PaperPanelProps, type UsePaintEditorReturn, usePaintEditor, usePaintEditorState };
|
|
162
|
+
export { BrushPanel, type BrushPanelProps, ColorPanel, type ColorPanelProps, PaintCanvas, type PaintCanvasProps, PaintEditor, type PaintEditorProps, PaintLayersPanel, type PaintLayersPanelProps, PaintPresenceLayer, PaintSlider, PaintToolbar, type PaintToolbarProps, PaperPanel, type PaperPanelProps, type UsePaintEditorReturn, usePaintDirector, usePaintEditor, usePaintEditorState };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/paint-editor/usePaintEditor.ts
|
|
2
2
|
import { useMemo, useState, useSyncExternalStore } from "react";
|
|
3
|
-
import { PaintEditorController } from "@pixerate/editor/paint-editor";
|
|
3
|
+
import { PaintDirector, PaintEditorController, PaintPresence } from "@pixerate/editor/paint-editor";
|
|
4
4
|
function usePaintEditor(options = {}) {
|
|
5
5
|
const [controller] = useState(() => options instanceof PaintEditorController ? options : new PaintEditorController(options));
|
|
6
6
|
const state = usePaintEditorState(controller);
|
|
@@ -13,19 +13,27 @@ function usePaintEditorState(controller) {
|
|
|
13
13
|
() => controller.getState()
|
|
14
14
|
);
|
|
15
15
|
}
|
|
16
|
+
function usePaintDirector(controller, options = {}) {
|
|
17
|
+
const [value] = useState(() => {
|
|
18
|
+
const presence = new PaintPresence();
|
|
19
|
+
return { presence, director: new PaintDirector(controller, presence, options) };
|
|
20
|
+
});
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
16
23
|
|
|
17
24
|
// src/paint-editor/PaintCanvas.tsx
|
|
18
|
-
import { useEffect, useMemo as useMemo2, useRef, useState as useState2 } from "react";
|
|
25
|
+
import { useEffect, useMemo as useMemo2, useRef, useState as useState2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
19
26
|
import {
|
|
20
27
|
PaintCanvasPresenter,
|
|
21
28
|
PaintGesture,
|
|
29
|
+
documentToScreen,
|
|
22
30
|
fitZoom,
|
|
23
31
|
guideOverlay,
|
|
24
32
|
handlePaintShortcut,
|
|
25
33
|
viewCssTransform
|
|
26
34
|
} from "@pixerate/editor/paint-editor";
|
|
27
35
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
28
|
-
var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount = true, onError, className }) => {
|
|
36
|
+
var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount = true, onError, presence, className }) => {
|
|
29
37
|
const state = usePaintEditorState(controller);
|
|
30
38
|
const viewport = useRef(null);
|
|
31
39
|
const canvas = useRef(null);
|
|
@@ -140,6 +148,7 @@ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount =
|
|
|
140
148
|
predicted.length > 0 && /* @__PURE__ */ jsx("polyline", { points: predicted.map((p) => `${p.x},${p.y}`).join(" "), fill: "none", stroke: state.color, strokeOpacity: 0.5, strokeWidth: Math.max(1, state.brush.tip.size / 2), strokeLinecap: "round" })
|
|
141
149
|
] }),
|
|
142
150
|
state.guides.references.filter((r) => r.visible).map((r) => /* @__PURE__ */ jsx(ReferencePanel, { controller, id: r.id }, r.id)),
|
|
151
|
+
presence && /* @__PURE__ */ jsx(PaintPresenceLayer, { presence, view: state.view, canvas: doc, viewport: size }),
|
|
143
152
|
/* @__PURE__ */ jsxs("div", { className: "absolute bottom-1 right-2 text-[10px] text-slate-500 pointer-events-none", "data-testid": "paint-view-status", children: [
|
|
144
153
|
Math.round(state.view.zoom * 100),
|
|
145
154
|
"% \xB7 ",
|
|
@@ -151,6 +160,16 @@ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount =
|
|
|
151
160
|
}
|
|
152
161
|
);
|
|
153
162
|
};
|
|
163
|
+
var PaintPresenceLayer = ({ presence, view, canvas, viewport }) => {
|
|
164
|
+
const { actors } = useSyncExternalStore2(presence.subscribe, presence.getState, presence.getState);
|
|
165
|
+
return /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 overflow-hidden", "data-paint-presence": "", "aria-hidden": "true", children: actors.map((a) => {
|
|
166
|
+
const p = documentToScreen(view, canvas, viewport, a.x, a.y);
|
|
167
|
+
return /* @__PURE__ */ jsxs("div", { className: "absolute", style: { left: p.x, top: p.y }, "data-actor": a.id, children: [
|
|
168
|
+
/* @__PURE__ */ jsx("svg", { width: "18", height: "20", viewBox: "0 0 18 20", className: "drop-shadow-md", style: { transform: a.pressed ? "scale(0.85)" : void 0, transformOrigin: "0 0" }, children: /* @__PURE__ */ jsx("path", { d: "M0 0 L0 15 L4.5 10.8 L7.6 17 L10.2 15.8 L7.2 9.7 L13.5 9.4 Z", fill: a.color, stroke: "white", strokeWidth: "1.4", strokeLinejoin: "round" }) }),
|
|
169
|
+
/* @__PURE__ */ jsx("span", { className: "absolute left-4 top-4 w-max max-w-[240px] rounded-md px-2 py-0.5 text-[11px] font-semibold text-white shadow", style: { background: a.color }, children: a.message ?? a.name })
|
|
170
|
+
] }, a.id);
|
|
171
|
+
}) });
|
|
172
|
+
};
|
|
154
173
|
var ReferencePanel = ({ controller, id }) => {
|
|
155
174
|
const state = usePaintEditorState(controller);
|
|
156
175
|
const ref = state.guides.references.find((r) => r.id === id);
|
|
@@ -458,8 +477,19 @@ var PaintToolbar = ({ controller, tool, onToolChange, fileName = "painting", cla
|
|
|
458
477
|
|
|
459
478
|
// src/paint-editor/BrushPanel.tsx
|
|
460
479
|
import { useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
|
|
461
|
-
import {
|
|
462
|
-
|
|
480
|
+
import {
|
|
481
|
+
BLEND_MENU_GROUPS,
|
|
482
|
+
BRUSH_ROTATIONS,
|
|
483
|
+
BRUSH_SLIDERS,
|
|
484
|
+
brushEngineControls,
|
|
485
|
+
brushLoadColor,
|
|
486
|
+
downloadBytes as downloadBytes2,
|
|
487
|
+
multiColorSliders,
|
|
488
|
+
patchBrushValue,
|
|
489
|
+
patchPath,
|
|
490
|
+
readPath
|
|
491
|
+
} from "@pixerate/editor/paint-editor";
|
|
492
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
463
493
|
var BrushPanel = ({ controller, thumbnailWidth = 112, thumbnailHeight = 32, className }) => {
|
|
464
494
|
const state = usePaintEditorState(controller);
|
|
465
495
|
const [error, attempt] = useAttempt();
|
|
@@ -473,6 +503,10 @@ var BrushPanel = ({ controller, thumbnailWidth = 112, thumbnailHeight = 32, clas
|
|
|
473
503
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
474
504
|
[controller, brushes, paper, thumbnailWidth, thumbnailHeight]
|
|
475
505
|
);
|
|
506
|
+
const engine = brushEngineControls(brush);
|
|
507
|
+
const mc = brush.behavior.multicolor;
|
|
508
|
+
const carried = brushLoadColor(state.mixerLoad);
|
|
509
|
+
const setColors = (colors) => controller.setBrush({ behavior: { multicolor: { colors } } });
|
|
476
510
|
const slug = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "brush";
|
|
477
511
|
return /* @__PURE__ */ jsxs4("div", { className: `${panel} ${className ?? ""}`, "data-testid": "brush-panel", children: [
|
|
478
512
|
/* @__PURE__ */ jsx4("div", { className: "grid grid-cols-2 gap-1", role: "listbox", "aria-label": "Brushes", children: brushes.map((b) => /* @__PURE__ */ jsxs4(
|
|
@@ -504,6 +538,68 @@ var BrushPanel = ({ controller, thumbnailWidth = 112, thumbnailHeight = 32, clas
|
|
|
504
538
|
/* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2", children: [
|
|
505
539
|
/* @__PURE__ */ jsx4("input", { type: "checkbox", checked: brush.tip.pressureOpacity, onChange: (e) => controller.setBrush({ tip: { pressureOpacity: e.target.checked } }) }),
|
|
506
540
|
" Pressure \u2192 opacity"
|
|
541
|
+
] }),
|
|
542
|
+
brush.engine === "dab" && /* @__PURE__ */ jsxs4("label", { className: "flex items-center justify-between gap-2", children: [
|
|
543
|
+
/* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "Blend" }),
|
|
544
|
+
/* @__PURE__ */ jsx4("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: BLEND_MENU_GROUPS.map((g) => /* @__PURE__ */ jsx4("optgroup", { label: g.label, children: g.modes.map((m) => /* @__PURE__ */ jsx4("option", { value: m.id, title: m.description, children: m.label }, m.id)) }, g.label)) })
|
|
545
|
+
] })
|
|
546
|
+
] }),
|
|
547
|
+
engine && /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-1.5", "data-testid": "brush-engine", children: [
|
|
548
|
+
/* @__PURE__ */ jsx4("span", { className: "font-medium text-slate-300", children: engine.title }),
|
|
549
|
+
engine.sliders.map((c) => /* @__PURE__ */ jsx4(PaintSlider, { control: c, value: readPath(brush, c.path), onChange: (v) => controller.setBrush(patchPath(brush, c.path, v)) }, c.path)),
|
|
550
|
+
engine.mixingPath && /* @__PURE__ */ jsxs4("label", { className: "flex items-center justify-between gap-2", children: [
|
|
551
|
+
/* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "Mixing" }),
|
|
552
|
+
/* @__PURE__ */ jsxs4(
|
|
553
|
+
"select",
|
|
554
|
+
{
|
|
555
|
+
"aria-label": "Brush mixing",
|
|
556
|
+
value: engine.mixingPath === "blender.mixing" ? brush.blender.mixing : engine.mixingPath === "smudge.mixing" ? brush.smudge.mixing : brush.behavior.mixing,
|
|
557
|
+
onChange: (e) => controller.setBrush(patchBrushValue(brush, engine.mixingPath, e.target.value)),
|
|
558
|
+
className: "rounded bg-slate-800 px-1 py-0.5",
|
|
559
|
+
children: [
|
|
560
|
+
/* @__PURE__ */ jsx4("option", { value: "pigment", children: "Pigment" }),
|
|
561
|
+
/* @__PURE__ */ jsx4("option", { value: "rgb", children: "RGB" })
|
|
562
|
+
]
|
|
563
|
+
}
|
|
564
|
+
)
|
|
565
|
+
] }),
|
|
566
|
+
engine.dirty && /* @__PURE__ */ jsxs4("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: [
|
|
567
|
+
/* @__PURE__ */ jsx4("input", { type: "checkbox", "aria-label": "Dirty brush", checked: brush.behavior.dirty, onChange: (e) => controller.setBrush({ behavior: { dirty: e.target.checked } }) }),
|
|
568
|
+
" Dirty brush"
|
|
569
|
+
] }),
|
|
570
|
+
engine.multicolor && /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-1.5", "data-testid": "multicolor", children: [
|
|
571
|
+
/* @__PURE__ */ jsxs4("label", { className: "flex items-center justify-between gap-2", children: [
|
|
572
|
+
/* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "Multi-colour" }),
|
|
573
|
+
/* @__PURE__ */ jsxs4("select", { "aria-label": "Multi-colour", value: mc.mode, onChange: (e) => controller.setBrush({ behavior: { multicolor: { mode: e.target.value } } }), className: "rounded bg-slate-800 px-1 py-0.5", children: [
|
|
574
|
+
/* @__PURE__ */ jsx4("option", { value: "off", children: "Off" }),
|
|
575
|
+
/* @__PURE__ */ jsx4("option", { value: "tip", children: "Across the tip" }),
|
|
576
|
+
/* @__PURE__ */ jsx4("option", { value: "dab", children: "Per dab" })
|
|
577
|
+
] })
|
|
578
|
+
] }),
|
|
579
|
+
mc.mode !== "off" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
580
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-1", "aria-label": "Multi-colour colours", children: [
|
|
581
|
+
mc.colors.map((col, i) => /* @__PURE__ */ jsxs4("span", { className: "flex items-center", children: [
|
|
582
|
+
/* @__PURE__ */ jsx4("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" }),
|
|
583
|
+
/* @__PURE__ */ jsx4("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" })
|
|
584
|
+
] }, i)),
|
|
585
|
+
mc.colors.length < 8 && /* @__PURE__ */ jsx4("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: "+" }),
|
|
586
|
+
mc.colors.length < 2 && /* @__PURE__ */ jsx4("span", { className: "text-[10px] text-slate-500", children: "Uses the paint and second colours" })
|
|
587
|
+
] }),
|
|
588
|
+
mc.mode === "dab" && /* @__PURE__ */ jsxs4("label", { className: "flex items-center justify-between gap-2", children: [
|
|
589
|
+
/* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "Order" }),
|
|
590
|
+
/* @__PURE__ */ jsxs4("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: [
|
|
591
|
+
/* @__PURE__ */ jsx4("option", { value: "sequence", children: "In order" }),
|
|
592
|
+
/* @__PURE__ */ jsx4("option", { value: "random", children: "Random" })
|
|
593
|
+
] })
|
|
594
|
+
] }),
|
|
595
|
+
multiColorSliders(mc.mode).map((c) => /* @__PURE__ */ jsx4(PaintSlider, { control: c, value: readPath(brush, c.path), onChange: (v) => controller.setBrush(patchPath(brush, c.path, v)) }, c.path))
|
|
596
|
+
] })
|
|
597
|
+
] }),
|
|
598
|
+
engine.load && /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1", children: [
|
|
599
|
+
/* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "Carries" }),
|
|
600
|
+
/* @__PURE__ */ jsx4("span", { "aria-label": "Brush load", title: carried ?? "Clean", className: "h-4 w-4 rounded ring-1 ring-slate-700", style: { background: carried ?? "transparent" } }),
|
|
601
|
+
brush.engine === "mixer" && /* @__PURE__ */ jsx4("button", { type: "button", className: button, onClick: () => controller.loadMixerBrush(), children: "Load" }),
|
|
602
|
+
/* @__PURE__ */ jsx4("button", { type: "button", className: button, onClick: () => controller.cleanMixerBrush(brush.engine === "mixer"), children: "Clean" })
|
|
507
603
|
] })
|
|
508
604
|
] }),
|
|
509
605
|
/* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
@@ -579,7 +675,9 @@ import {
|
|
|
579
675
|
HARMONY_KINDS,
|
|
580
676
|
MIXING_PAD_HEIGHT,
|
|
581
677
|
MIXING_PAD_WIDTH,
|
|
678
|
+
PAD_SAMPLE_SIZES,
|
|
582
679
|
hsvToRgb,
|
|
680
|
+
padSampleRadius,
|
|
583
681
|
hueToWheel,
|
|
584
682
|
parseHexColor,
|
|
585
683
|
renderColorWheel,
|
|
@@ -595,6 +693,9 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
|
|
|
595
693
|
const [wheel, setWheel] = useState5("ryb");
|
|
596
694
|
const [kind, setKind] = useState5("complementary");
|
|
597
695
|
const [padTool, setPadTool] = useState5("paint");
|
|
696
|
+
const [sample, setSample] = useState5(3);
|
|
697
|
+
const [parts, setParts] = useState5([1, 1]);
|
|
698
|
+
const [padName, setPadName] = useState5("");
|
|
598
699
|
const padPoints = useRef5(null);
|
|
599
700
|
const ring = useMemo5(() => renderColorWheel(wheelSize, wheel), [wheelSize, wheel]);
|
|
600
701
|
const color = state.color;
|
|
@@ -706,7 +807,7 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
|
|
|
706
807
|
onPointerDown: (e) => {
|
|
707
808
|
const p = padPoint(e);
|
|
708
809
|
if (padTool === "pick") {
|
|
709
|
-
attempt(() => controller.mixingPadPick(p.x, p.y, { setColor: true }));
|
|
810
|
+
attempt(() => controller.mixingPadPick(p.x, p.y, { setColor: true, radius: padSampleRadius(sample) }));
|
|
710
811
|
return;
|
|
711
812
|
}
|
|
712
813
|
padPoints.current = [p];
|
|
@@ -720,7 +821,44 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
|
|
|
720
821
|
},
|
|
721
822
|
children: /* @__PURE__ */ jsx6(PixelCanvas, { pixels: pad, label: "Mixing pad pixels", className: "w-full rounded", style: { aspectRatio: `${MIXING_PAD_WIDTH} / ${MIXING_PAD_HEIGHT}` } })
|
|
722
823
|
}
|
|
723
|
-
)
|
|
824
|
+
),
|
|
825
|
+
/* @__PURE__ */ jsx6("div", { className: "flex flex-wrap items-center gap-1", children: /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1", children: [
|
|
826
|
+
/* @__PURE__ */ jsx6("span", { className: "text-slate-400", children: "Sample" }),
|
|
827
|
+
/* @__PURE__ */ jsx6("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: PAD_SAMPLE_SIZES.map((n) => /* @__PURE__ */ jsx6("option", { value: n, children: n === 1 ? "1 px" : `${n}\xD7${n}` }, n)) })
|
|
828
|
+
] }) }),
|
|
829
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
830
|
+
/* @__PURE__ */ jsx6("span", { className: "h-4 w-4 rounded ring-1 ring-slate-700", style: { background: color } }),
|
|
831
|
+
/* @__PURE__ */ jsx6("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" }),
|
|
832
|
+
/* @__PURE__ */ jsx6("span", { className: "text-slate-500", children: ":" }),
|
|
833
|
+
/* @__PURE__ */ jsx6("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" }),
|
|
834
|
+
/* @__PURE__ */ jsx6("span", { className: "h-4 w-4 rounded ring-1 ring-slate-700", style: { background: state.secondaryColor } }),
|
|
835
|
+
/* @__PURE__ */ jsx6(
|
|
836
|
+
"button",
|
|
837
|
+
{
|
|
838
|
+
type: "button",
|
|
839
|
+
className: button,
|
|
840
|
+
title: "Mix the paint and second colours in these parts on the pad, and use the mix",
|
|
841
|
+
onClick: () => attempt(
|
|
842
|
+
() => controller.mixOnPad(
|
|
843
|
+
[
|
|
844
|
+
{ color, parts: parts[0] },
|
|
845
|
+
{ color: state.secondaryColor, parts: parts[1] }
|
|
846
|
+
],
|
|
847
|
+
{ setColor: true }
|
|
848
|
+
)
|
|
849
|
+
),
|
|
850
|
+
children: "Mix"
|
|
851
|
+
}
|
|
852
|
+
)
|
|
853
|
+
] }),
|
|
854
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
855
|
+
/* @__PURE__ */ jsx6("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" }),
|
|
856
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: button, onClick: () => attempt(() => (controller.saveMixingPad(padName || void 0), setPadName(""))), children: "Keep pad" })
|
|
857
|
+
] }),
|
|
858
|
+
state.savedMixingPads.length > 0 && /* @__PURE__ */ jsx6("ul", { className: "flex flex-col gap-0.5", "aria-label": "Kept pads", children: state.savedMixingPads.map((p) => /* @__PURE__ */ jsxs6("li", { className: "flex items-center gap-1", children: [
|
|
859
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: `${button} flex-1 text-left`, onClick: () => attempt(() => controller.loadMixingPad(p.id)), children: p.name }),
|
|
860
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: button, "aria-label": `Remove ${p.name}`, onClick: () => attempt(() => controller.removeMixingPad(p.id)), children: "\xD7" })
|
|
861
|
+
] }, p.id)) })
|
|
724
862
|
] }),
|
|
725
863
|
/* @__PURE__ */ jsx6(ErrorLine, { error })
|
|
726
864
|
] });
|
|
@@ -728,7 +866,7 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
|
|
|
728
866
|
|
|
729
867
|
// src/paint-editor/PaintLayersPanel.tsx
|
|
730
868
|
import { useState as useState6 } from "react";
|
|
731
|
-
import { BLEND_MENU_GROUPS } from "@pixerate/editor/paint-editor";
|
|
869
|
+
import { BLEND_MENU_GROUPS as BLEND_MENU_GROUPS2 } from "@pixerate/editor/paint-editor";
|
|
732
870
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
733
871
|
function siblingIndex(layers, l) {
|
|
734
872
|
const sibs = layers.filter((x) => x.parentId === l.parentId).reverse();
|
|
@@ -825,7 +963,7 @@ var PaintLayersPanel = ({ controller, className }) => {
|
|
|
825
963
|
/* @__PURE__ */ jsx7("span", { className: "w-14 text-slate-400", children: "Blend" }),
|
|
826
964
|
/* @__PURE__ */ jsxs7("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: [
|
|
827
965
|
active.type === "group" && /* @__PURE__ */ jsx7("option", { value: "pass-through", children: "Pass through" }),
|
|
828
|
-
|
|
966
|
+
BLEND_MENU_GROUPS2.map((g) => /* @__PURE__ */ jsx7("optgroup", { label: g.label, children: g.modes.map((m) => /* @__PURE__ */ jsx7("option", { value: m.id, title: m.description, children: m.label }, m.id)) }, g.label))
|
|
829
967
|
] })
|
|
830
968
|
] }),
|
|
831
969
|
/* @__PURE__ */ jsxs7("div", { className: "flex gap-1", children: [
|
|
@@ -840,7 +978,7 @@ var PaintLayersPanel = ({ controller, className }) => {
|
|
|
840
978
|
// src/paint-editor/PaintEditor.tsx
|
|
841
979
|
import { useState as useState7 } from "react";
|
|
842
980
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
843
|
-
var PaintEditor = ({ controller, fileName, height = 720, className }) => {
|
|
981
|
+
var PaintEditor = ({ controller, fileName, height = 720, presence, className }) => {
|
|
844
982
|
const [tool, setTool] = useState7("brush");
|
|
845
983
|
const [tab, setTab] = useState7("color");
|
|
846
984
|
const [error, setError] = useState7(null);
|
|
@@ -849,7 +987,7 @@ var PaintEditor = ({ controller, fileName, height = 720, className }) => {
|
|
|
849
987
|
/* @__PURE__ */ jsxs8("div", { className: "flex min-h-0 flex-1 gap-2", children: [
|
|
850
988
|
/* @__PURE__ */ jsx8(BrushPanel, { controller, className: "w-60 shrink-0 overflow-y-auto pr-1" }),
|
|
851
989
|
/* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
852
|
-
/* @__PURE__ */ jsx8(PaintCanvas, { controller, tool, onError: setError, className: "flex-1 rounded-lg" }),
|
|
990
|
+
/* @__PURE__ */ jsx8(PaintCanvas, { controller, tool, onError: setError, presence, className: "flex-1 rounded-lg" }),
|
|
853
991
|
error && /* @__PURE__ */ jsx8("p", { role: "alert", className: "mt-1 text-[11px] text-rose-400", children: error })
|
|
854
992
|
] }),
|
|
855
993
|
/* @__PURE__ */ jsxs8("div", { className: "flex w-64 shrink-0 flex-col gap-2 overflow-y-auto pr-1", children: [
|
|
@@ -867,9 +1005,11 @@ export {
|
|
|
867
1005
|
PaintCanvas,
|
|
868
1006
|
PaintEditor,
|
|
869
1007
|
PaintLayersPanel,
|
|
1008
|
+
PaintPresenceLayer,
|
|
870
1009
|
PaintSlider,
|
|
871
1010
|
PaintToolbar,
|
|
872
1011
|
PaperPanel,
|
|
1012
|
+
usePaintDirector,
|
|
873
1013
|
usePaintEditor,
|
|
874
1014
|
usePaintEditorState
|
|
875
1015
|
};
|