@pixerate/editor-react 0.79.0 → 0.81.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/canvas/index.cjs +1301 -580
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +65 -4
- package/dist/canvas/index.d.ts +65 -4
- package/dist/canvas/index.js +363 -199
- package/dist/canvas/index.js.map +1 -1
- package/dist/{chunk-BVP43EWA.js → chunk-VKWTKESA.js} +4 -4
- package/dist/image-editor/index.js +2 -2
- package/dist/index.js +2 -2
- package/dist/paint-editor/index.cjs +734 -19
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +22 -3
- package/dist/paint-editor/index.d.ts +22 -3
- package/dist/paint-editor/index.js +173 -19
- package/dist/paint-editor/index.js.map +1 -1
- package/package.json +3 -3
- /package/dist/{chunk-BVP43EWA.js.map → chunk-VKWTKESA.js.map} +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/paint-editor/index.ts
|
|
@@ -24,6 +34,7 @@ __export(paint_editor_exports, {
|
|
|
24
34
|
ColorPanel: () => ColorPanel,
|
|
25
35
|
PaintCanvas: () => PaintCanvas,
|
|
26
36
|
PaintEditor: () => PaintEditor,
|
|
37
|
+
PaintEffectsPanel: () => PaintEffectsPanel,
|
|
27
38
|
PaintGradientEditor: () => PaintGradientEditor,
|
|
28
39
|
PaintLayersPanel: () => PaintLayersPanel,
|
|
29
40
|
PaintPresenceLayer: () => PaintPresenceLayer,
|
|
@@ -34,7 +45,9 @@ __export(paint_editor_exports, {
|
|
|
34
45
|
WetPanel: () => WetPanel,
|
|
35
46
|
usePaintDirector: () => usePaintDirector,
|
|
36
47
|
usePaintEditor: () => usePaintEditor,
|
|
37
|
-
usePaintEditorState: () => usePaintEditorState
|
|
48
|
+
usePaintEditorState: () => usePaintEditorState,
|
|
49
|
+
usePaintEffectsProvider: () => usePaintEffectsProvider,
|
|
50
|
+
usePaintEffectsTools: () => usePaintEffectsTools
|
|
38
51
|
});
|
|
39
52
|
module.exports = __toCommonJS(paint_editor_exports);
|
|
40
53
|
|
|
@@ -1321,27 +1334,726 @@ var StickerPanel = ({ controller, className }) => {
|
|
|
1321
1334
|
};
|
|
1322
1335
|
|
|
1323
1336
|
// src/paint-editor/PaintEditor.tsx
|
|
1337
|
+
var import_react17 = require("react");
|
|
1338
|
+
|
|
1339
|
+
// src/paint-editor/PaintEffectsPanel.tsx
|
|
1340
|
+
var import_react16 = require("react");
|
|
1341
|
+
var import_paint_editor13 = require("@pixerate/editor/paint-editor");
|
|
1342
|
+
|
|
1343
|
+
// src/effects/useEffects.ts
|
|
1324
1344
|
var import_react11 = require("react");
|
|
1345
|
+
function useEffectDescription(provider, effect) {
|
|
1346
|
+
const [state, setState] = (0, import_react11.useState)({ key: "", description: null, error: null });
|
|
1347
|
+
const key = provider && effect ? `${provider.id}:${effect}` : "";
|
|
1348
|
+
(0, import_react11.useEffect)(() => {
|
|
1349
|
+
if (!provider || !effect) return;
|
|
1350
|
+
let live = true;
|
|
1351
|
+
Promise.resolve().then(() => provider.describe(effect)).then(
|
|
1352
|
+
(description) => live && setState({ key, description, error: null }),
|
|
1353
|
+
(e) => live && setState({ key, description: null, error: e instanceof Error ? e.message : String(e) })
|
|
1354
|
+
);
|
|
1355
|
+
return () => {
|
|
1356
|
+
live = false;
|
|
1357
|
+
};
|
|
1358
|
+
}, [provider, effect, key]);
|
|
1359
|
+
return state.key === key ? { description: state.description, error: state.error } : { description: null, error: null };
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// src/effects/EffectThumbnail.tsx
|
|
1363
|
+
var import_react12 = require("react");
|
|
1364
|
+
var import_effects = require("@pixerate/editor/effects");
|
|
1325
1365
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1366
|
+
function EffectThumbnail({ provider, effect, width = 96, height = 54, className = "" }) {
|
|
1367
|
+
const ref = (0, import_react12.useRef)(null);
|
|
1368
|
+
const [failed, setFailed] = (0, import_react12.useState)(effect.kind === "motion");
|
|
1369
|
+
(0, import_react12.useEffect)(() => {
|
|
1370
|
+
if (effect.kind === "motion") return;
|
|
1371
|
+
let live = true;
|
|
1372
|
+
(0, import_effects.renderEffectThumbnail)(provider, effect, { width, height }).then(
|
|
1373
|
+
(pixels) => {
|
|
1374
|
+
const ctx = live ? ref.current?.getContext("2d") : null;
|
|
1375
|
+
if (!ctx || typeof ImageData === "undefined") return;
|
|
1376
|
+
ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels.data), pixels.width, pixels.height), 0, 0);
|
|
1377
|
+
},
|
|
1378
|
+
() => live && setFailed(true)
|
|
1379
|
+
);
|
|
1380
|
+
return () => {
|
|
1381
|
+
live = false;
|
|
1382
|
+
};
|
|
1383
|
+
}, [provider, effect, width, height]);
|
|
1384
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1385
|
+
"div",
|
|
1386
|
+
{
|
|
1387
|
+
className: `relative overflow-hidden rounded border border-border bg-[repeating-conic-gradient(#8884_0_25%,transparent_0_50%)] bg-[length:12px_12px] ${className}`,
|
|
1388
|
+
style: { aspectRatio: `${width} / ${height}` },
|
|
1389
|
+
"data-effect-thumbnail": effect.id,
|
|
1390
|
+
children: failed ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "absolute inset-0 flex items-center justify-center text-[10px] text-muted-foreground", children: effect.kind === "motion" ? "Motion" : "No preview" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("canvas", { ref, width, height, className: "h-full w-full", "aria-hidden": "true" })
|
|
1391
|
+
}
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// src/effects/EffectsBrowser.tsx
|
|
1396
|
+
var import_react13 = require("react");
|
|
1397
|
+
var import_effects2 = require("@pixerate/editor/effects");
|
|
1398
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1399
|
+
function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, title = "Effects", className = "" }) {
|
|
1400
|
+
const [all, setAll] = (0, import_react13.useState)([]);
|
|
1401
|
+
const [query, setQuery] = (0, import_react13.useState)("");
|
|
1402
|
+
const [category, setCategory] = (0, import_react13.useState)("");
|
|
1403
|
+
const [use, setUse] = (0, import_react13.useState)("");
|
|
1404
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1405
|
+
(0, import_react13.useEffect)(() => {
|
|
1406
|
+
if (!provider) return setAll([]);
|
|
1407
|
+
let live = true;
|
|
1408
|
+
Promise.resolve().then(() => provider.list()).then(
|
|
1409
|
+
(list) => live && (setAll(list), setError(null)),
|
|
1410
|
+
(e) => live && setError(e instanceof Error ? e.message : String(e))
|
|
1411
|
+
);
|
|
1412
|
+
return () => {
|
|
1413
|
+
live = false;
|
|
1414
|
+
};
|
|
1415
|
+
}, [provider]);
|
|
1416
|
+
const [found, setFound] = (0, import_react13.useState)([]);
|
|
1417
|
+
(0, import_react13.useEffect)(() => {
|
|
1418
|
+
if (!provider) return setFound([]);
|
|
1419
|
+
let live = true;
|
|
1420
|
+
Promise.resolve().then(() => provider.search(query, { ...category ? { category } : {}, ...use ? { use } : {} })).then((list) => live && setFound(list), () => live && setFound([]));
|
|
1421
|
+
return () => {
|
|
1422
|
+
live = false;
|
|
1423
|
+
};
|
|
1424
|
+
}, [provider, query, category, use]);
|
|
1425
|
+
const allowed = (e) => (!kinds || kinds.includes(e.kind)) && (uses ? e.kind !== "motion" || uses.includes("motion") : e.kind !== "motion") && (!uses || e.uses.some((u) => uses.includes(u)));
|
|
1426
|
+
const categories = (0, import_react13.useMemo)(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses, kinds]);
|
|
1427
|
+
const shown = found.filter(allowed);
|
|
1428
|
+
if (!provider)
|
|
1429
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: `rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground ${className}`, "data-effects-browser": "", "data-effects-unavailable": "", children: "No effects provider is registered, so effects cannot be added. Effects already in the document are kept." });
|
|
1430
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("section", { className: `flex flex-col gap-2 ${className}`, "data-effects-browser": "", "aria-label": "Effects browser", children: [
|
|
1431
|
+
title && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h3", { className: "text-xs font-semibold", children: title }),
|
|
1432
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
|
|
1433
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1434
|
+
"input",
|
|
1435
|
+
{
|
|
1436
|
+
type: "search",
|
|
1437
|
+
className: "min-w-0 flex-1 rounded border border-border bg-transparent px-1.5 py-0.5 text-xs",
|
|
1438
|
+
placeholder: "Search effects",
|
|
1439
|
+
"aria-label": "Search effects",
|
|
1440
|
+
value: query,
|
|
1441
|
+
onChange: (e) => setQuery(e.currentTarget.value)
|
|
1442
|
+
}
|
|
1443
|
+
),
|
|
1444
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("select", { className: "rounded border border-border bg-transparent px-1 py-0.5 text-xs", "aria-label": "Category", value: category, onChange: (e) => setCategory(e.currentTarget.value), children: [
|
|
1445
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "", children: "All categories" }),
|
|
1446
|
+
categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: c, children: c }, c))
|
|
1447
|
+
] }),
|
|
1448
|
+
uses && uses.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("select", { className: "rounded border border-border bg-transparent px-1 py-0.5 text-xs", "aria-label": "Use", value: use, onChange: (e) => setUse(e.currentTarget.value), children: [
|
|
1449
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "", children: "Any use" }),
|
|
1450
|
+
uses.map((u) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: u, children: import_effects2.EFFECT_USE_LABELS[u] }, u))
|
|
1451
|
+
] })
|
|
1452
|
+
] }),
|
|
1453
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: error }),
|
|
1454
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("ul", { className: "grid max-h-96 grid-cols-[repeat(auto-fill,minmax(6.5rem,1fr))] gap-1.5 overflow-y-auto", role: "list", children: shown.map((e) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1455
|
+
"button",
|
|
1456
|
+
{
|
|
1457
|
+
type: "button",
|
|
1458
|
+
className: "flex w-full flex-col gap-1 rounded border border-transparent p-1 text-left hover:border-border focus-visible:border-border",
|
|
1459
|
+
title: e.description ?? e.name,
|
|
1460
|
+
"data-effect-id": e.id,
|
|
1461
|
+
onClick: () => onPick(e),
|
|
1462
|
+
children: [
|
|
1463
|
+
thumbnails && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(EffectThumbnail, { provider, effect: e }),
|
|
1464
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "truncate text-[11px] font-medium", children: e.name }),
|
|
1465
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "truncate text-[10px] text-muted-foreground", children: e.uses.map((u) => import_effects2.EFFECT_USE_LABELS[u]).join(" \xB7 ") })
|
|
1466
|
+
]
|
|
1467
|
+
}
|
|
1468
|
+
) }, e.id)) }),
|
|
1469
|
+
!shown.length && !error && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects match." })
|
|
1470
|
+
] });
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// src/effects/EffectParameters.tsx
|
|
1474
|
+
var import_react15 = __toESM(require("react"), 1);
|
|
1475
|
+
var import_effects3 = require("@pixerate/editor/effects");
|
|
1476
|
+
var import_parameters2 = require("@pixerate/editor/parameters");
|
|
1477
|
+
|
|
1478
|
+
// src/parameters/fields.tsx
|
|
1479
|
+
var import_react14 = require("react");
|
|
1480
|
+
var import_parameters = require("@pixerate/editor/parameters");
|
|
1481
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1482
|
+
var parameterInputClass = "h-7 w-full min-w-0 rounded-md border border-input bg-background px-2 text-xs text-foreground tabular-nums focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50";
|
|
1483
|
+
var parameterButtonClass = "inline-flex h-7 items-center justify-center gap-1 rounded-md border border-input bg-background px-2 text-xs text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50";
|
|
1484
|
+
var rangeClass = "h-4 w-full min-w-0 flex-1 cursor-pointer accent-primary disabled:cursor-not-allowed disabled:opacity-50";
|
|
1485
|
+
function ParameterCommitInput({ value, onCommit, multiline, className, onKeyDown, ...rest }) {
|
|
1486
|
+
const commit = (el) => {
|
|
1487
|
+
if (el.value !== value) onCommit(el.value, el);
|
|
1488
|
+
};
|
|
1489
|
+
const keyDown = (e) => {
|
|
1490
|
+
onKeyDown?.(e);
|
|
1491
|
+
if (e.defaultPrevented) return;
|
|
1492
|
+
if (e.key === "Enter" && (!multiline || e.metaKey || e.ctrlKey)) {
|
|
1493
|
+
e.preventDefault();
|
|
1494
|
+
commit(e.currentTarget);
|
|
1495
|
+
}
|
|
1496
|
+
if (e.key === "Escape") e.currentTarget.value = value;
|
|
1497
|
+
};
|
|
1498
|
+
const props = {
|
|
1499
|
+
defaultValue: value,
|
|
1500
|
+
className: className ?? parameterInputClass,
|
|
1501
|
+
onBlur: (e) => commit(e.currentTarget),
|
|
1502
|
+
onKeyDown: keyDown,
|
|
1503
|
+
...rest
|
|
1504
|
+
};
|
|
1505
|
+
return multiline ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("textarea", { ...props, className: `${props.className} h-auto min-h-[3.5rem] py-1` }, value) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("input", { type: "text", ...props }, value);
|
|
1506
|
+
}
|
|
1507
|
+
var plainNumber = (field, value) => (0, import_parameters.formatParameterNumber)({ ...field, unit: void 0 }, value);
|
|
1508
|
+
function ParameterNumberInput({
|
|
1509
|
+
field,
|
|
1510
|
+
value,
|
|
1511
|
+
onChange,
|
|
1512
|
+
disabled,
|
|
1513
|
+
id,
|
|
1514
|
+
"aria-label": ariaLabel
|
|
1515
|
+
}) {
|
|
1516
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
|
|
1517
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1518
|
+
ParameterCommitInput,
|
|
1519
|
+
{
|
|
1520
|
+
id,
|
|
1521
|
+
inputMode: "decimal",
|
|
1522
|
+
"aria-label": ariaLabel ?? (0, import_parameters.parameterLabel)(field),
|
|
1523
|
+
value: plainNumber(field, value),
|
|
1524
|
+
disabled: disabled || field.readOnly,
|
|
1525
|
+
className: `${parameterInputClass} ${field.unit ? "pr-6" : ""}`,
|
|
1526
|
+
onCommit: (text, input) => {
|
|
1527
|
+
const n = (0, import_parameters.parseParameterNumberInput)(field, text);
|
|
1528
|
+
if (n === null || n === value) input.value = plainNumber(field, value);
|
|
1529
|
+
else onChange(n);
|
|
1530
|
+
},
|
|
1531
|
+
onKeyDown: (e) => {
|
|
1532
|
+
if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
|
|
1533
|
+
e.preventDefault();
|
|
1534
|
+
const step = (0, import_parameters.parameterStep)(field) * (e.shiftKey ? 10 : 1);
|
|
1535
|
+
onChange((0, import_parameters.clampParameterNumber)(field, value + (e.key === "ArrowUp" ? step : -step)));
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
),
|
|
1539
|
+
field.unit && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-[10px] text-muted-foreground", children: field.unit })
|
|
1540
|
+
] });
|
|
1541
|
+
}
|
|
1542
|
+
function ParameterSlider(props) {
|
|
1543
|
+
const { field, value, onChange, onGestureStart, onGestureEnd, disabled } = props;
|
|
1544
|
+
const dragging = (0, import_react14.useRef)(false);
|
|
1545
|
+
const end = () => {
|
|
1546
|
+
if (!dragging.current) return;
|
|
1547
|
+
dragging.current = false;
|
|
1548
|
+
onGestureEnd?.();
|
|
1549
|
+
};
|
|
1550
|
+
if (!(0, import_parameters.isSliderField)(field)) return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ParameterNumberInput, { ...props });
|
|
1551
|
+
const step = (0, import_parameters.parameterStep)(field);
|
|
1552
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1553
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1554
|
+
"input",
|
|
1555
|
+
{
|
|
1556
|
+
type: "range",
|
|
1557
|
+
"aria-label": (0, import_parameters.parameterLabel)(field),
|
|
1558
|
+
"aria-valuetext": (0, import_parameters.formatParameterNumber)(field, value),
|
|
1559
|
+
min: field.min,
|
|
1560
|
+
max: field.max,
|
|
1561
|
+
step,
|
|
1562
|
+
value,
|
|
1563
|
+
disabled: disabled || field.readOnly,
|
|
1564
|
+
className: rangeClass,
|
|
1565
|
+
onPointerDown: () => {
|
|
1566
|
+
if (dragging.current) return;
|
|
1567
|
+
dragging.current = true;
|
|
1568
|
+
onGestureStart?.();
|
|
1569
|
+
},
|
|
1570
|
+
onPointerUp: end,
|
|
1571
|
+
onPointerCancel: end,
|
|
1572
|
+
onBlur: end,
|
|
1573
|
+
onChange: (e) => onChange((0, import_parameters.clampParameterNumber)(field, Number(e.target.value)))
|
|
1574
|
+
}
|
|
1575
|
+
),
|
|
1576
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "w-20 shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ParameterNumberInput, { ...props, id: props.id, "aria-label": `${(0, import_parameters.parameterLabel)(field)} value` }) })
|
|
1577
|
+
] });
|
|
1578
|
+
}
|
|
1579
|
+
function ParameterToggle({ field, value, onChange, disabled, id }) {
|
|
1580
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1581
|
+
"button",
|
|
1582
|
+
{
|
|
1583
|
+
type: "button",
|
|
1584
|
+
role: "switch",
|
|
1585
|
+
id,
|
|
1586
|
+
"aria-checked": value,
|
|
1587
|
+
"aria-label": (0, import_parameters.parameterLabel)(field),
|
|
1588
|
+
disabled: disabled || field.readOnly,
|
|
1589
|
+
onClick: () => onChange(!value),
|
|
1590
|
+
className: `relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:opacity-50 ${value ? "bg-primary" : "bg-muted"}`,
|
|
1591
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1592
|
+
"span",
|
|
1593
|
+
{
|
|
1594
|
+
className: `pointer-events-none block h-4 w-4 rounded-full bg-background shadow transition-transform ${value ? "translate-x-4" : "translate-x-0.5"}`
|
|
1595
|
+
}
|
|
1596
|
+
)
|
|
1597
|
+
}
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
function ParameterSelect({ field, value, onChange, disabled, id }) {
|
|
1601
|
+
const options = (0, import_parameters.getChoiceOptions)(field);
|
|
1602
|
+
const off = disabled || field.readOnly;
|
|
1603
|
+
if (field.control === "segmented") {
|
|
1604
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { role: "radiogroup", "aria-label": (0, import_parameters.parameterLabel)(field), className: "inline-flex flex-wrap rounded-md border border-input bg-muted p-0.5", children: options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1605
|
+
"button",
|
|
1606
|
+
{
|
|
1607
|
+
type: "button",
|
|
1608
|
+
role: "radio",
|
|
1609
|
+
"aria-checked": o.value === value,
|
|
1610
|
+
title: o.description,
|
|
1611
|
+
disabled: off,
|
|
1612
|
+
onClick: () => onChange(o.value),
|
|
1613
|
+
className: `rounded px-2 py-0.5 text-xs disabled:opacity-50 ${o.value === value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
|
|
1614
|
+
children: (0, import_parameters.choiceOptionLabel)(o)
|
|
1615
|
+
},
|
|
1616
|
+
String(o.value)
|
|
1617
|
+
)) });
|
|
1618
|
+
}
|
|
1619
|
+
const index = options.findIndex((o) => o.value === value);
|
|
1620
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1621
|
+
"select",
|
|
1622
|
+
{
|
|
1623
|
+
id,
|
|
1624
|
+
"aria-label": (0, import_parameters.parameterLabel)(field),
|
|
1625
|
+
value: String(index),
|
|
1626
|
+
disabled: off,
|
|
1627
|
+
onChange: (e) => {
|
|
1628
|
+
const option = options[Number(e.target.value)];
|
|
1629
|
+
if (option) onChange(option.value);
|
|
1630
|
+
},
|
|
1631
|
+
className: parameterInputClass,
|
|
1632
|
+
children: options.map((o, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: String(i), title: o.description, children: (0, import_parameters.choiceOptionLabel)(o) }, String(o.value)))
|
|
1633
|
+
}
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd, disabled, id }) {
|
|
1637
|
+
const picking = (0, import_react14.useRef)(false);
|
|
1638
|
+
const alpha = !!field.alpha;
|
|
1639
|
+
const hex = (0, import_parameters.normalizeHexColor)(value, true) ?? "#000000ff";
|
|
1640
|
+
const rgb = hex.slice(0, 7);
|
|
1641
|
+
const a = hex.slice(7);
|
|
1642
|
+
const off = disabled || field.readOnly;
|
|
1643
|
+
const start = () => {
|
|
1644
|
+
if (picking.current) return;
|
|
1645
|
+
picking.current = true;
|
|
1646
|
+
onGestureStart?.();
|
|
1647
|
+
};
|
|
1648
|
+
const end = () => {
|
|
1649
|
+
if (!picking.current) return;
|
|
1650
|
+
picking.current = false;
|
|
1651
|
+
onGestureEnd?.();
|
|
1652
|
+
};
|
|
1653
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "space-y-1.5", children: [
|
|
1654
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1655
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "relative h-7 w-9 shrink-0 overflow-hidden rounded-md border border-input bg-[repeating-conic-gradient(#d4d4d8_0_25%,#fff_0_50%)] bg-[length:8px_8px]", children: [
|
|
1656
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "absolute inset-0", style: { background: alpha ? hex : rgb } }),
|
|
1657
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1658
|
+
"input",
|
|
1659
|
+
{
|
|
1660
|
+
type: "color",
|
|
1661
|
+
"aria-label": (0, import_parameters.parameterLabel)(field),
|
|
1662
|
+
value: rgb,
|
|
1663
|
+
disabled: off,
|
|
1664
|
+
className: "absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed",
|
|
1665
|
+
onChange: (e) => {
|
|
1666
|
+
start();
|
|
1667
|
+
onChange(alpha ? `${e.target.value}${a}` : e.target.value);
|
|
1668
|
+
},
|
|
1669
|
+
onBlur: end
|
|
1670
|
+
}
|
|
1671
|
+
)
|
|
1672
|
+
] }),
|
|
1673
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1674
|
+
ParameterCommitInput,
|
|
1675
|
+
{
|
|
1676
|
+
id,
|
|
1677
|
+
"aria-label": `${(0, import_parameters.parameterLabel)(field)} hex`,
|
|
1678
|
+
value: alpha ? hex : rgb,
|
|
1679
|
+
disabled: off,
|
|
1680
|
+
spellCheck: false,
|
|
1681
|
+
className: `${parameterInputClass} font-mono`,
|
|
1682
|
+
onCommit: (text) => {
|
|
1683
|
+
const next = (0, import_parameters.normalizeHexColor)(text, alpha);
|
|
1684
|
+
if (next) onChange(next);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
)
|
|
1688
|
+
] }),
|
|
1689
|
+
alpha && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1690
|
+
"input",
|
|
1691
|
+
{
|
|
1692
|
+
type: "range",
|
|
1693
|
+
"aria-label": `${(0, import_parameters.parameterLabel)(field)} opacity`,
|
|
1694
|
+
min: 0,
|
|
1695
|
+
max: 255,
|
|
1696
|
+
step: 1,
|
|
1697
|
+
value: parseInt(a, 16),
|
|
1698
|
+
disabled: off,
|
|
1699
|
+
className: rangeClass,
|
|
1700
|
+
onPointerDown: start,
|
|
1701
|
+
onPointerUp: end,
|
|
1702
|
+
onPointerCancel: end,
|
|
1703
|
+
onBlur: end,
|
|
1704
|
+
onChange: (e) => onChange(`${rgb}${Number(e.target.value).toString(16).padStart(2, "0")}`)
|
|
1705
|
+
}
|
|
1706
|
+
),
|
|
1707
|
+
field.swatches && field.swatches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex flex-wrap gap-1", children: field.swatches.map((s) => {
|
|
1708
|
+
const swatch = (0, import_parameters.normalizeHexColor)(s, alpha);
|
|
1709
|
+
if (!swatch) return null;
|
|
1710
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1711
|
+
"button",
|
|
1712
|
+
{
|
|
1713
|
+
type: "button",
|
|
1714
|
+
title: swatch,
|
|
1715
|
+
"aria-label": `${(0, import_parameters.parameterLabel)(field)} ${swatch}`,
|
|
1716
|
+
"aria-pressed": swatch === (alpha ? hex : rgb),
|
|
1717
|
+
disabled: off,
|
|
1718
|
+
onClick: () => onChange(swatch),
|
|
1719
|
+
className: "h-5 w-5 rounded border border-input aria-pressed:ring-1 aria-pressed:ring-primary",
|
|
1720
|
+
style: { background: swatch }
|
|
1721
|
+
},
|
|
1722
|
+
s
|
|
1723
|
+
);
|
|
1724
|
+
}) })
|
|
1725
|
+
] });
|
|
1726
|
+
}
|
|
1727
|
+
function ParameterVector({ field, value, onChange, disabled, id }) {
|
|
1728
|
+
const size = (0, import_parameters.vectorSize)(field);
|
|
1729
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "grid gap-1.5", style: { gridTemplateColumns: `repeat(${size}, minmax(0, 1fr))` }, children: Array.from({ length: size }, (_, i) => {
|
|
1730
|
+
const component = (0, import_parameters.vectorComponentField)(field, i);
|
|
1731
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex min-w-0 items-center gap-1", children: [
|
|
1732
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-3 shrink-0 text-[10px] uppercase text-muted-foreground", children: component.label }),
|
|
1733
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1734
|
+
ParameterNumberInput,
|
|
1735
|
+
{
|
|
1736
|
+
field: component,
|
|
1737
|
+
value: value[i] ?? 0,
|
|
1738
|
+
id: i === 0 ? id : void 0,
|
|
1739
|
+
"aria-label": `${(0, import_parameters.parameterLabel)(field)} ${component.label}`,
|
|
1740
|
+
disabled: disabled || field.readOnly,
|
|
1741
|
+
onChange: (n) => onChange(Array.from({ length: size }, (_2, j) => j === i ? n : value[j] ?? 0))
|
|
1742
|
+
}
|
|
1743
|
+
)
|
|
1744
|
+
] }, i);
|
|
1745
|
+
}) });
|
|
1746
|
+
}
|
|
1747
|
+
function ParameterImage({ field, value, onChange, disabled, id, resolveFile }) {
|
|
1748
|
+
const off = disabled || field.readOnly;
|
|
1749
|
+
const name = (0, import_parameters.parameterLabel)(field);
|
|
1750
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "space-y-1.5", children: [
|
|
1751
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1752
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex h-12 w-16 shrink-0 items-center justify-center overflow-hidden rounded-md border border-input bg-muted", children: value ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("img", { src: value, alt: name, className: "h-full w-full object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "None" }) }),
|
|
1753
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
1754
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1755
|
+
ParameterCommitInput,
|
|
1756
|
+
{
|
|
1757
|
+
id,
|
|
1758
|
+
"aria-label": `${name} URL`,
|
|
1759
|
+
placeholder: "Image URL",
|
|
1760
|
+
value: value.startsWith("data:") ? "" : value,
|
|
1761
|
+
disabled: off,
|
|
1762
|
+
onCommit: (text) => onChange(text.trim())
|
|
1763
|
+
}
|
|
1764
|
+
),
|
|
1765
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex gap-1", children: [
|
|
1766
|
+
resolveFile && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: `${parameterButtonClass} cursor-pointer ${off ? "pointer-events-none opacity-50" : ""}`, children: [
|
|
1767
|
+
"Choose file\u2026",
|
|
1768
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1769
|
+
"input",
|
|
1770
|
+
{
|
|
1771
|
+
type: "file",
|
|
1772
|
+
className: "sr-only",
|
|
1773
|
+
accept: field.accept ?? "image/*",
|
|
1774
|
+
disabled: off,
|
|
1775
|
+
"aria-label": `${name} file`,
|
|
1776
|
+
onChange: async (e) => {
|
|
1777
|
+
const file = e.target.files?.[0];
|
|
1778
|
+
e.target.value = "";
|
|
1779
|
+
if (file) onChange(await resolveFile(file));
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
)
|
|
1783
|
+
] }),
|
|
1784
|
+
value && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: parameterButtonClass, disabled: off, onClick: () => onChange(""), "aria-label": `Clear ${name}`, children: "Clear" })
|
|
1785
|
+
] })
|
|
1786
|
+
] })
|
|
1787
|
+
] }),
|
|
1788
|
+
field.options && field.options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex flex-wrap gap-1", children: field.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1789
|
+
"button",
|
|
1790
|
+
{
|
|
1791
|
+
type: "button",
|
|
1792
|
+
title: o.label ?? o.url,
|
|
1793
|
+
"aria-label": o.label ?? o.url,
|
|
1794
|
+
"aria-pressed": o.url === value,
|
|
1795
|
+
disabled: off,
|
|
1796
|
+
onClick: () => onChange(o.url),
|
|
1797
|
+
className: "h-9 w-12 overflow-hidden rounded border border-input aria-pressed:ring-2 aria-pressed:ring-primary",
|
|
1798
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("img", { src: o.url, alt: "", className: "h-full w-full object-cover" })
|
|
1799
|
+
},
|
|
1800
|
+
o.url
|
|
1801
|
+
)) })
|
|
1802
|
+
] });
|
|
1803
|
+
}
|
|
1804
|
+
function ParameterText({ field, value, onChange, disabled, id }) {
|
|
1805
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1806
|
+
ParameterCommitInput,
|
|
1807
|
+
{
|
|
1808
|
+
id,
|
|
1809
|
+
"aria-label": (0, import_parameters.parameterLabel)(field),
|
|
1810
|
+
multiline: field.multiline,
|
|
1811
|
+
placeholder: field.placeholder,
|
|
1812
|
+
maxLength: field.maxLength,
|
|
1813
|
+
value,
|
|
1814
|
+
disabled: disabled || field.readOnly,
|
|
1815
|
+
onCommit: onChange
|
|
1816
|
+
}
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
function ParameterFieldControl({ field, value, onChange, resolveFile, ...rest }) {
|
|
1820
|
+
const set = onChange;
|
|
1821
|
+
switch (field.type) {
|
|
1822
|
+
case "number":
|
|
1823
|
+
case "int":
|
|
1824
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ParameterSlider, { field, value, onChange: set, ...rest });
|
|
1825
|
+
case "boolean":
|
|
1826
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ParameterToggle, { field, value, onChange: set, ...rest });
|
|
1827
|
+
case "choice":
|
|
1828
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ParameterSelect, { field, value, onChange: set, ...rest });
|
|
1829
|
+
case "color":
|
|
1830
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ParameterColor, { field, value, onChange: set, ...rest });
|
|
1831
|
+
case "vector":
|
|
1832
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ParameterVector, { field, value, onChange: set, ...rest });
|
|
1833
|
+
case "image":
|
|
1834
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ParameterImage, { field, value, onChange: set, resolveFile, ...rest });
|
|
1835
|
+
case "text":
|
|
1836
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ParameterText, { field, value, onChange: set, ...rest });
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
// src/effects/EffectParameters.tsx
|
|
1841
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1842
|
+
function EffectParameters({ description, values, onChange, onReset, keyframed, onPreset, disabled = false, className = "" }) {
|
|
1843
|
+
const fields = (0, import_react15.useMemo)(() => (0, import_effects3.effectParameterFields)(description), [description]);
|
|
1844
|
+
const idBase = (0, import_react15.useId)();
|
|
1845
|
+
const gesture = import_react15.default.useRef(null);
|
|
1846
|
+
const count = import_react15.default.useRef(0);
|
|
1847
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `space-y-2 ${className}`, "data-effect-parameters": description.id, children: [
|
|
1848
|
+
onPreset && description.presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
1849
|
+
"select",
|
|
1850
|
+
{
|
|
1851
|
+
className: "w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs",
|
|
1852
|
+
"aria-label": `${description.name} presets`,
|
|
1853
|
+
value: "",
|
|
1854
|
+
disabled,
|
|
1855
|
+
onChange: (e) => {
|
|
1856
|
+
const preset = description.presets.find((p) => p.name === e.currentTarget.value);
|
|
1857
|
+
if (preset) onPreset({ ...description.defaults, ...preset.values });
|
|
1858
|
+
},
|
|
1859
|
+
children: [
|
|
1860
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "", children: "Presets\u2026" }),
|
|
1861
|
+
description.presets.map((p) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: p.name, children: p.name }, p.name))
|
|
1862
|
+
]
|
|
1863
|
+
}
|
|
1864
|
+
),
|
|
1865
|
+
fields.map((field) => {
|
|
1866
|
+
const label = (0, import_parameters2.parameterLabel)(field);
|
|
1867
|
+
const inputId = `${idBase}-${field.id}`;
|
|
1868
|
+
const keyed = keyframed?.has(field.id) ?? false;
|
|
1869
|
+
const value = (0, import_effects3.effectFieldValue)(field, values[field.id]);
|
|
1870
|
+
const changed = !keyed && !(0, import_parameters2.parameterValuesEqual)(value, field.default);
|
|
1871
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "space-y-1", "data-effect-control": field.id, children: [
|
|
1872
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
1873
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { htmlFor: inputId, className: "min-w-0 flex-1 truncate text-xs font-medium", title: field.description, children: label }),
|
|
1874
|
+
keyed && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "keyframed" }),
|
|
1875
|
+
onReset && changed && !disabled && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: "rounded px-1 text-[11px] text-muted-foreground hover:text-foreground", "aria-label": `Reset ${label}`, onClick: () => onReset(field.id), children: "\u21BA" })
|
|
1876
|
+
] }),
|
|
1877
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1878
|
+
ParameterFieldControl,
|
|
1879
|
+
{
|
|
1880
|
+
field,
|
|
1881
|
+
value,
|
|
1882
|
+
id: inputId,
|
|
1883
|
+
disabled: disabled || keyed,
|
|
1884
|
+
onChange: (v) => onChange(field.id, v, gesture.current),
|
|
1885
|
+
onGestureStart: () => gesture.current = `${idBase}:${field.id}:${++count.current}`,
|
|
1886
|
+
onGestureEnd: () => gesture.current = null
|
|
1887
|
+
}
|
|
1888
|
+
)
|
|
1889
|
+
] }, field.id);
|
|
1890
|
+
}),
|
|
1891
|
+
!fields.length && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No controls." })
|
|
1892
|
+
] });
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
// src/paint-editor/PaintEffectsPanel.tsx
|
|
1896
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1897
|
+
function usePaintEffectsProvider(controller) {
|
|
1898
|
+
const [provider, setProvider] = (0, import_react16.useState)(controller.effectsProvider);
|
|
1899
|
+
(0, import_react16.useEffect)(() => {
|
|
1900
|
+
setProvider(controller.effectsProvider);
|
|
1901
|
+
return controller.effects.subscribe(setProvider);
|
|
1902
|
+
}, [controller]);
|
|
1903
|
+
return provider;
|
|
1904
|
+
}
|
|
1905
|
+
function usePaintEffectsTools(registry, controller, options = {}) {
|
|
1906
|
+
const { id, namespace, readOnly } = options;
|
|
1907
|
+
(0, import_react16.useEffect)(() => {
|
|
1908
|
+
if (!registry) return;
|
|
1909
|
+
return registry.register((0, import_paint_editor13.createPaintEffectsToolProvider)(controller, { id, namespace, readOnly }));
|
|
1910
|
+
}, [registry, controller, id, namespace, readOnly]);
|
|
1911
|
+
}
|
|
1912
|
+
function Controls({ provider, effect, name, values, apply }) {
|
|
1913
|
+
const { description, error } = useEffectDescription(provider, effect);
|
|
1914
|
+
const drags = (0, import_react16.useRef)(/* @__PURE__ */ new Set());
|
|
1915
|
+
if (!provider) return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-[11px] text-slate-400", children: [
|
|
1916
|
+
'No effects provider: "',
|
|
1917
|
+
name ?? effect,
|
|
1918
|
+
'" is kept as it was drawn.'
|
|
1919
|
+
] });
|
|
1920
|
+
if (error) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-[11px] text-amber-400", children: error });
|
|
1921
|
+
if (!description) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-[11px] text-slate-400", children: "Loading\u2026" });
|
|
1922
|
+
const commit = (gesture) => {
|
|
1923
|
+
if (!gesture || drags.current.has(gesture)) return !gesture;
|
|
1924
|
+
drags.current.add(gesture);
|
|
1925
|
+
return true;
|
|
1926
|
+
};
|
|
1927
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1928
|
+
EffectParameters,
|
|
1929
|
+
{
|
|
1930
|
+
description,
|
|
1931
|
+
values,
|
|
1932
|
+
onChange: (k, v, gesture) => apply({ [k]: v }, commit(gesture)),
|
|
1933
|
+
onReset: (k) => apply({ [k]: null }, true),
|
|
1934
|
+
onPreset: (v) => apply({ ...v }, true)
|
|
1935
|
+
}
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
var BROWSERS = {
|
|
1939
|
+
paper: { uses: ["generator"], title: "Effect paper" },
|
|
1940
|
+
layer: { uses: ["generator", "overlay", "title"], title: "Effect layer" },
|
|
1941
|
+
fill: { uses: ["generator", "overlay", "title"], title: "Fill the layer" },
|
|
1942
|
+
light: { uses: ["filter"], title: "Light on the paint" }
|
|
1943
|
+
};
|
|
1944
|
+
var PaintEffectsPanel = ({ controller, className }) => {
|
|
1945
|
+
const state = usePaintEditorState(controller);
|
|
1946
|
+
const provider = usePaintEffectsProvider(controller);
|
|
1947
|
+
const [error, attempt] = useAttempt();
|
|
1948
|
+
const [browse, setBrowse] = (0, import_react16.useState)(null);
|
|
1949
|
+
const [busy, setBusy] = (0, import_react16.useState)(false);
|
|
1950
|
+
const paper = controller.getPaperEffect();
|
|
1951
|
+
const activeId = state.document.activeLayerId;
|
|
1952
|
+
const layerEffect = activeId ? controller.getEffectLayer(activeId) : void 0;
|
|
1953
|
+
const active = activeId ? controller.getLayer(activeId) : void 0;
|
|
1954
|
+
const run = (work) => attempt(() => {
|
|
1955
|
+
setBusy(true);
|
|
1956
|
+
return work().finally(() => setBusy(false));
|
|
1957
|
+
});
|
|
1958
|
+
const pick = (id) => {
|
|
1959
|
+
const kind = browse;
|
|
1960
|
+
setBrowse(null);
|
|
1961
|
+
if (kind === "paper") run(() => controller.setPaperEffect(id));
|
|
1962
|
+
else if (kind === "layer") run(() => controller.addEffectLayer(id));
|
|
1963
|
+
else if (kind === "fill") run(() => controller.fillLayerWithEffect(id));
|
|
1964
|
+
else if (kind === "light" && activeId) run(() => controller.applyLightEffect(activeId, id));
|
|
1965
|
+
};
|
|
1966
|
+
const toggle = (b) => setBrowse(browse === b ? null : b);
|
|
1967
|
+
const pixel = active?.type === "pixel";
|
|
1968
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: `${panel} ${className ?? ""}`, "data-testid": "paint-effects-panel", "aria-busy": busy, children: [
|
|
1969
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
1970
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "flex-1 font-medium text-slate-300", children: "Effects" }),
|
|
1971
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: button, disabled: !provider, "aria-pressed": browse === "paper", onClick: () => toggle("paper"), children: "Paper\u2026" }),
|
|
1972
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: button, disabled: !provider, "aria-pressed": browse === "layer", onClick: () => toggle("layer"), children: "Layer\u2026" }),
|
|
1973
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: button, disabled: !provider || !pixel, "aria-pressed": browse === "fill", onClick: () => toggle("fill"), children: "Fill\u2026" }),
|
|
1974
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: button, disabled: !provider || !pixel, "aria-pressed": browse === "light", onClick: () => toggle("light"), title: "Bake a light, glow or grade into the active layer's dried paint", children: "Light\u2026" })
|
|
1975
|
+
] }),
|
|
1976
|
+
!provider && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-[11px] text-slate-400", children: "No effects provider is registered. Effects in the painting are kept." }),
|
|
1977
|
+
browse && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EffectsBrowser, { provider, uses: BROWSERS[browse].uses, title: BROWSERS[browse].title, onPick: (e) => pick(e.id) }),
|
|
1978
|
+
paper && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-col gap-1 rounded border border-slate-700 p-1", "data-effect-part": "paper", children: [
|
|
1979
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
1980
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "flex-1 truncate", children: [
|
|
1981
|
+
"Paper: ",
|
|
1982
|
+
paper.name ?? paper.effect
|
|
1983
|
+
] }),
|
|
1984
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: button, "aria-label": "Remove the effect paper", onClick: () => run(() => controller.setPaperEffect(null)), children: "\u2715" })
|
|
1985
|
+
] }),
|
|
1986
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1", children: [
|
|
1987
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-14 text-[11px] text-slate-400", children: "Opacity" }),
|
|
1988
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1989
|
+
"input",
|
|
1990
|
+
{
|
|
1991
|
+
type: "range",
|
|
1992
|
+
min: 0,
|
|
1993
|
+
max: 1,
|
|
1994
|
+
step: 0.01,
|
|
1995
|
+
className: "flex-1",
|
|
1996
|
+
"aria-label": "Effect paper opacity",
|
|
1997
|
+
defaultValue: paper.opacity,
|
|
1998
|
+
onPointerUp: (e) => {
|
|
1999
|
+
const opacity = Number(e.currentTarget.value);
|
|
2000
|
+
run(() => controller.updatePaperEffect({ opacity }));
|
|
2001
|
+
},
|
|
2002
|
+
onKeyUp: (e) => {
|
|
2003
|
+
const opacity = Number(e.currentTarget.value);
|
|
2004
|
+
run(() => controller.updatePaperEffect({ opacity }));
|
|
2005
|
+
}
|
|
2006
|
+
},
|
|
2007
|
+
`o${paper.opacity}`
|
|
2008
|
+
)
|
|
2009
|
+
] }),
|
|
2010
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1", children: [
|
|
2011
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-14 text-[11px] text-slate-400", children: "Blend" }),
|
|
2012
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("select", { className: "flex-1 rounded bg-slate-800 px-1 py-0.5 text-[11px]", "aria-label": "Effect paper blend", value: paper.blend, onChange: (e) => {
|
|
2013
|
+
const blend = e.currentTarget.value;
|
|
2014
|
+
run(() => controller.updatePaperEffect({ blend }));
|
|
2015
|
+
}, children: import_paint_editor13.PAPER_EFFECT_BLENDS.map((b) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: b, children: b }, b)) })
|
|
2016
|
+
] }),
|
|
2017
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Controls, { provider, effect: paper.effect, name: paper.name, values: paper.values, apply: (values, commit) => run(() => controller.updatePaperEffect({ values }, commit)) })
|
|
2018
|
+
] }),
|
|
2019
|
+
layerEffect && activeId && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-col gap-1 rounded border border-slate-700 p-1", "data-effect-part": "layer", children: [
|
|
2020
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
2021
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "flex-1 truncate", children: [
|
|
2022
|
+
"Layer: ",
|
|
2023
|
+
layerEffect.name ?? layerEffect.effect
|
|
2024
|
+
] }),
|
|
2025
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: button, title: "Keep the pixels as a plain layer", onClick: () => attempt(() => controller.rasterizeEffectLayer(activeId)), children: "Rasterize" })
|
|
2026
|
+
] }),
|
|
2027
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Controls, { provider, effect: layerEffect.effect, name: layerEffect.name, values: layerEffect.values, apply: (values, commit) => run(() => controller.updateEffectLayer(activeId, { values }, commit)) })
|
|
2028
|
+
] }),
|
|
2029
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ErrorLine, { error })
|
|
2030
|
+
] });
|
|
2031
|
+
};
|
|
2032
|
+
|
|
2033
|
+
// src/paint-editor/PaintEditor.tsx
|
|
2034
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1326
2035
|
var PaintEditor = ({ controller, fileName, height = 720, presence, className }) => {
|
|
1327
|
-
const [tool, setTool] = (0,
|
|
1328
|
-
const [tab, setTab] = (0,
|
|
1329
|
-
const [error, setError] = (0,
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
2036
|
+
const [tool, setTool] = (0, import_react17.useState)("brush");
|
|
2037
|
+
const [tab, setTab] = (0, import_react17.useState)("color");
|
|
2038
|
+
const [error, setError] = (0, import_react17.useState)(null);
|
|
2039
|
+
const effects = usePaintEffectsProvider(controller);
|
|
2040
|
+
const tabs = effects ? ["color", "wet", "paper", "layers", "effects"] : ["color", "wet", "paper", "layers"];
|
|
2041
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.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: [
|
|
2042
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PaintToolbar, { controller, tool, onToolChange: setTool, fileName }),
|
|
2043
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-h-0 flex-1 gap-2", children: [
|
|
2044
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(BrushPanel, { controller, className: "w-60 shrink-0 overflow-y-auto pr-1" }),
|
|
2045
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
2046
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PaintCanvas, { controller, tool, onError: setError, presence, className: "flex-1 rounded-lg" }),
|
|
2047
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { role: "alert", className: "mt-1 text-[11px] text-rose-400", children: error })
|
|
1337
2048
|
] }),
|
|
1338
|
-
/* @__PURE__ */ (0,
|
|
1339
|
-
/* @__PURE__ */ (0,
|
|
1340
|
-
tab === "color" && /* @__PURE__ */ (0,
|
|
1341
|
-
tab === "wet" && /* @__PURE__ */ (0,
|
|
1342
|
-
tab === "paper" && /* @__PURE__ */ (0,
|
|
1343
|
-
tab === "layers" && /* @__PURE__ */ (0,
|
|
1344
|
-
tab === "layers" && /* @__PURE__ */ (0,
|
|
2049
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex w-64 shrink-0 flex-col gap-2 overflow-y-auto pr-1", children: [
|
|
2050
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex gap-1", role: "tablist", "aria-label": "Paint panels", children: tabs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime17.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" : t === "layers" ? "Layers" : "Effects" }, t)) }),
|
|
2051
|
+
tab === "color" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ColorPanel, { controller }),
|
|
2052
|
+
tab === "wet" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(WetPanel, { controller }),
|
|
2053
|
+
tab === "paper" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PaperPanel, { controller }),
|
|
2054
|
+
tab === "layers" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PaintLayersPanel, { controller }),
|
|
2055
|
+
tab === "layers" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(StickerPanel, { controller }),
|
|
2056
|
+
tab === "effects" && effects && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PaintEffectsPanel, { controller })
|
|
1345
2057
|
] })
|
|
1346
2058
|
] })
|
|
1347
2059
|
] });
|
|
@@ -1352,6 +2064,7 @@ var PaintEditor = ({ controller, fileName, height = 720, presence, className })
|
|
|
1352
2064
|
ColorPanel,
|
|
1353
2065
|
PaintCanvas,
|
|
1354
2066
|
PaintEditor,
|
|
2067
|
+
PaintEffectsPanel,
|
|
1355
2068
|
PaintGradientEditor,
|
|
1356
2069
|
PaintLayersPanel,
|
|
1357
2070
|
PaintPresenceLayer,
|
|
@@ -1362,6 +2075,8 @@ var PaintEditor = ({ controller, fileName, height = 720, presence, className })
|
|
|
1362
2075
|
WetPanel,
|
|
1363
2076
|
usePaintDirector,
|
|
1364
2077
|
usePaintEditor,
|
|
1365
|
-
usePaintEditorState
|
|
2078
|
+
usePaintEditorState,
|
|
2079
|
+
usePaintEffectsProvider,
|
|
2080
|
+
usePaintEffectsTools
|
|
1366
2081
|
});
|
|
1367
2082
|
//# sourceMappingURL=index.cjs.map
|