@pixerate/editor-react 0.78.0 → 0.79.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/animation-editor/index.cjs +679 -37
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +29 -3
- package/dist/animation-editor/index.d.ts +29 -3
- package/dist/animation-editor/index.js +128 -37
- package/dist/animation-editor/index.js.map +1 -1
- package/dist/{chunk-2EW42UXC.js → chunk-BVP43EWA.js} +2 -2
- package/dist/{chunk-FX3EREF5.js → chunk-WOQ3H7KA.js} +5 -5
- package/dist/chunk-WOQ3H7KA.js.map +1 -0
- package/dist/effects/index.cjs +4 -4
- package/dist/effects/index.cjs.map +1 -1
- package/dist/effects/index.d.cts +4 -2
- package/dist/effects/index.d.ts +4 -2
- package/dist/effects/index.js +1 -1
- package/dist/image-editor/index.cjs +4 -4
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.js +2 -2
- package/dist/index.cjs +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/vector/index.cjs +649 -9
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +27 -3
- package/dist/vector/index.d.ts +27 -3
- package/dist/vector/index.js +98 -9
- package/dist/vector/index.js.map +1 -1
- package/dist/video-editor/index.cjs +4 -4
- package/dist/video-editor/index.cjs.map +1 -1
- package/dist/video-editor/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-FX3EREF5.js.map +0 -1
- /package/dist/{chunk-2EW42UXC.js.map → chunk-BVP43EWA.js.map} +0 -0
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var animation_editor_exports = {};
|
|
32
32
|
__export(animation_editor_exports, {
|
|
33
33
|
AnimationEditor: () => AnimationEditor,
|
|
34
|
+
AnimationEffectsPanel: () => AnimationEffectsPanel,
|
|
34
35
|
AnimationStage: () => AnimationStage,
|
|
35
36
|
CommandInspector: () => CommandInspector,
|
|
36
37
|
CommandList: () => CommandList,
|
|
@@ -42,7 +43,8 @@ __export(animation_editor_exports, {
|
|
|
42
43
|
useAnimationEditor: () => useAnimationEditor,
|
|
43
44
|
useAnimationEditorController: () => useAnimationEditorController,
|
|
44
45
|
useAnimationEditorState: () => useAnimationEditorState,
|
|
45
|
-
useAnimationEditorTools: () => useAnimationEditorTools
|
|
46
|
+
useAnimationEditorTools: () => useAnimationEditorTools,
|
|
47
|
+
useAnimationEffectsTools: () => useAnimationEffectsTools
|
|
46
48
|
});
|
|
47
49
|
module.exports = __toCommonJS(animation_editor_exports);
|
|
48
50
|
|
|
@@ -90,6 +92,13 @@ function useAnimationEditorTools(registry, controller, options = {}) {
|
|
|
90
92
|
return registry.register((0, import_animation_editor.createAnimationEditorToolProvider)(controller, { id, namespace, readOnly, rasterize }));
|
|
91
93
|
}, [registry, controller, id, namespace, readOnly, rasterize]);
|
|
92
94
|
}
|
|
95
|
+
function useAnimationEffectsTools(registry, controller, options = {}) {
|
|
96
|
+
const { id, namespace, readOnly } = options;
|
|
97
|
+
(0, import_react.useEffect)(() => {
|
|
98
|
+
if (!registry) return;
|
|
99
|
+
return registry.register((0, import_animation_editor.createAnimationEffectsToolProvider)(controller, { id, namespace, readOnly }));
|
|
100
|
+
}, [registry, controller, id, namespace, readOnly]);
|
|
101
|
+
}
|
|
93
102
|
|
|
94
103
|
// src/animation-editor/AnimationStage.tsx
|
|
95
104
|
var import_react2 = require("react");
|
|
@@ -191,8 +200,8 @@ function ShotTimeline({ controller, className = "" }) {
|
|
|
191
200
|
}
|
|
192
201
|
|
|
193
202
|
// src/animation-editor/AnimationEditor.tsx
|
|
194
|
-
var
|
|
195
|
-
var
|
|
203
|
+
var import_react15 = require("react");
|
|
204
|
+
var import_animation_editor10 = require("@pixerate/editor/animation-editor");
|
|
196
205
|
|
|
197
206
|
// src/animation-editor/CommandInspector.tsx
|
|
198
207
|
var import_react4 = __toESM(require("react"), 1);
|
|
@@ -966,19 +975,649 @@ function ShotInspector({ controller, shot, className = "" }) {
|
|
|
966
975
|
] });
|
|
967
976
|
}
|
|
968
977
|
|
|
969
|
-
// src/
|
|
978
|
+
// src/effects/useEffects.ts
|
|
979
|
+
var import_react9 = require("react");
|
|
980
|
+
function useEffects(host, provider) {
|
|
981
|
+
(0, import_react9.useEffect)(() => {
|
|
982
|
+
if (!host || !provider) return;
|
|
983
|
+
return host.useEffects(provider);
|
|
984
|
+
}, [host, provider]);
|
|
985
|
+
}
|
|
986
|
+
function useEffectDescription(provider, effect) {
|
|
987
|
+
const [state, setState] = (0, import_react9.useState)({ key: "", description: null, error: null });
|
|
988
|
+
const key = provider && effect ? `${provider.id}:${effect}` : "";
|
|
989
|
+
(0, import_react9.useEffect)(() => {
|
|
990
|
+
if (!provider || !effect) return;
|
|
991
|
+
let live = true;
|
|
992
|
+
Promise.resolve().then(() => provider.describe(effect)).then(
|
|
993
|
+
(description) => live && setState({ key, description, error: null }),
|
|
994
|
+
(e) => live && setState({ key, description: null, error: e instanceof Error ? e.message : String(e) })
|
|
995
|
+
);
|
|
996
|
+
return () => {
|
|
997
|
+
live = false;
|
|
998
|
+
};
|
|
999
|
+
}, [provider, effect, key]);
|
|
1000
|
+
return state.key === key ? { description: state.description, error: state.error } : { description: null, error: null };
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/effects/EffectThumbnail.tsx
|
|
1004
|
+
var import_react10 = require("react");
|
|
1005
|
+
var import_effects = require("@pixerate/editor/effects");
|
|
970
1006
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1007
|
+
function EffectThumbnail({ provider, effect, width = 96, height = 54, className = "" }) {
|
|
1008
|
+
const ref = (0, import_react10.useRef)(null);
|
|
1009
|
+
const [failed, setFailed] = (0, import_react10.useState)(effect.kind === "motion");
|
|
1010
|
+
(0, import_react10.useEffect)(() => {
|
|
1011
|
+
if (effect.kind === "motion") return;
|
|
1012
|
+
let live = true;
|
|
1013
|
+
(0, import_effects.renderEffectThumbnail)(provider, effect, { width, height }).then(
|
|
1014
|
+
(pixels) => {
|
|
1015
|
+
const ctx = live ? ref.current?.getContext("2d") : null;
|
|
1016
|
+
if (!ctx || typeof ImageData === "undefined") return;
|
|
1017
|
+
ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels.data), pixels.width, pixels.height), 0, 0);
|
|
1018
|
+
},
|
|
1019
|
+
() => live && setFailed(true)
|
|
1020
|
+
);
|
|
1021
|
+
return () => {
|
|
1022
|
+
live = false;
|
|
1023
|
+
};
|
|
1024
|
+
}, [provider, effect, width, height]);
|
|
1025
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1026
|
+
"div",
|
|
1027
|
+
{
|
|
1028
|
+
className: `relative overflow-hidden rounded border border-border bg-[repeating-conic-gradient(#8884_0_25%,transparent_0_50%)] bg-[length:12px_12px] ${className}`,
|
|
1029
|
+
style: { aspectRatio: `${width} / ${height}` },
|
|
1030
|
+
"data-effect-thumbnail": effect.id,
|
|
1031
|
+
children: failed ? /* @__PURE__ */ (0, import_jsx_runtime10.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_runtime10.jsx)("canvas", { ref, width, height, className: "h-full w-full", "aria-hidden": "true" })
|
|
1032
|
+
}
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// src/effects/EffectsBrowser.tsx
|
|
1037
|
+
var import_react11 = require("react");
|
|
1038
|
+
var import_effects2 = require("@pixerate/editor/effects");
|
|
1039
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1040
|
+
function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, title = "Effects", className = "" }) {
|
|
1041
|
+
const [all, setAll] = (0, import_react11.useState)([]);
|
|
1042
|
+
const [query, setQuery] = (0, import_react11.useState)("");
|
|
1043
|
+
const [category, setCategory] = (0, import_react11.useState)("");
|
|
1044
|
+
const [use, setUse] = (0, import_react11.useState)("");
|
|
1045
|
+
const [error, setError] = (0, import_react11.useState)(null);
|
|
1046
|
+
(0, import_react11.useEffect)(() => {
|
|
1047
|
+
if (!provider) return setAll([]);
|
|
1048
|
+
let live = true;
|
|
1049
|
+
Promise.resolve().then(() => provider.list()).then(
|
|
1050
|
+
(list) => live && (setAll(list), setError(null)),
|
|
1051
|
+
(e) => live && setError(e instanceof Error ? e.message : String(e))
|
|
1052
|
+
);
|
|
1053
|
+
return () => {
|
|
1054
|
+
live = false;
|
|
1055
|
+
};
|
|
1056
|
+
}, [provider]);
|
|
1057
|
+
const [found, setFound] = (0, import_react11.useState)([]);
|
|
1058
|
+
(0, import_react11.useEffect)(() => {
|
|
1059
|
+
if (!provider) return setFound([]);
|
|
1060
|
+
let live = true;
|
|
1061
|
+
Promise.resolve().then(() => provider.search(query, { ...category ? { category } : {}, ...use ? { use } : {} })).then((list) => live && setFound(list), () => live && setFound([]));
|
|
1062
|
+
return () => {
|
|
1063
|
+
live = false;
|
|
1064
|
+
};
|
|
1065
|
+
}, [provider, query, category, use]);
|
|
1066
|
+
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)));
|
|
1067
|
+
const categories = (0, import_react11.useMemo)(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses, kinds]);
|
|
1068
|
+
const shown = found.filter(allowed);
|
|
1069
|
+
if (!provider)
|
|
1070
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.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." });
|
|
1071
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { className: `flex flex-col gap-2 ${className}`, "data-effects-browser": "", "aria-label": "Effects browser", children: [
|
|
1072
|
+
title && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { className: "text-xs font-semibold", children: title }),
|
|
1073
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
|
|
1074
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1075
|
+
"input",
|
|
1076
|
+
{
|
|
1077
|
+
type: "search",
|
|
1078
|
+
className: "min-w-0 flex-1 rounded border border-border bg-transparent px-1.5 py-0.5 text-xs",
|
|
1079
|
+
placeholder: "Search effects",
|
|
1080
|
+
"aria-label": "Search effects",
|
|
1081
|
+
value: query,
|
|
1082
|
+
onChange: (e) => setQuery(e.currentTarget.value)
|
|
1083
|
+
}
|
|
1084
|
+
),
|
|
1085
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.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: [
|
|
1086
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "", children: "All categories" }),
|
|
1087
|
+
categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: c, children: c }, c))
|
|
1088
|
+
] }),
|
|
1089
|
+
uses && uses.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime11.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: [
|
|
1090
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "", children: "Any use" }),
|
|
1091
|
+
uses.map((u) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: u, children: import_effects2.EFFECT_USE_LABELS[u] }, u))
|
|
1092
|
+
] })
|
|
1093
|
+
] }),
|
|
1094
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: error }),
|
|
1095
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.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_runtime11.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1096
|
+
"button",
|
|
1097
|
+
{
|
|
1098
|
+
type: "button",
|
|
1099
|
+
className: "flex w-full flex-col gap-1 rounded border border-transparent p-1 text-left hover:border-border focus-visible:border-border",
|
|
1100
|
+
title: e.description ?? e.name,
|
|
1101
|
+
"data-effect-id": e.id,
|
|
1102
|
+
onClick: () => onPick(e),
|
|
1103
|
+
children: [
|
|
1104
|
+
thumbnails && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(EffectThumbnail, { provider, effect: e }),
|
|
1105
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "truncate text-[11px] font-medium", children: e.name }),
|
|
1106
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "truncate text-[10px] text-muted-foreground", children: e.uses.map((u) => import_effects2.EFFECT_USE_LABELS[u]).join(" \xB7 ") })
|
|
1107
|
+
]
|
|
1108
|
+
}
|
|
1109
|
+
) }, e.id)) }),
|
|
1110
|
+
!shown.length && !error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects match." })
|
|
1111
|
+
] });
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// src/effects/EffectParameters.tsx
|
|
1115
|
+
var import_react13 = __toESM(require("react"), 1);
|
|
1116
|
+
var import_effects3 = require("@pixerate/editor/effects");
|
|
1117
|
+
var import_parameters2 = require("@pixerate/editor/parameters");
|
|
1118
|
+
|
|
1119
|
+
// src/parameters/fields.tsx
|
|
1120
|
+
var import_react12 = require("react");
|
|
1121
|
+
var import_parameters = require("@pixerate/editor/parameters");
|
|
1122
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1123
|
+
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";
|
|
1124
|
+
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";
|
|
1125
|
+
var rangeClass = "h-4 w-full min-w-0 flex-1 cursor-pointer accent-primary disabled:cursor-not-allowed disabled:opacity-50";
|
|
1126
|
+
function ParameterCommitInput({ value, onCommit, multiline, className, onKeyDown, ...rest }) {
|
|
1127
|
+
const commit = (el) => {
|
|
1128
|
+
if (el.value !== value) onCommit(el.value, el);
|
|
1129
|
+
};
|
|
1130
|
+
const keyDown = (e) => {
|
|
1131
|
+
onKeyDown?.(e);
|
|
1132
|
+
if (e.defaultPrevented) return;
|
|
1133
|
+
if (e.key === "Enter" && (!multiline || e.metaKey || e.ctrlKey)) {
|
|
1134
|
+
e.preventDefault();
|
|
1135
|
+
commit(e.currentTarget);
|
|
1136
|
+
}
|
|
1137
|
+
if (e.key === "Escape") e.currentTarget.value = value;
|
|
1138
|
+
};
|
|
1139
|
+
const props = {
|
|
1140
|
+
defaultValue: value,
|
|
1141
|
+
className: className ?? parameterInputClass,
|
|
1142
|
+
onBlur: (e) => commit(e.currentTarget),
|
|
1143
|
+
onKeyDown: keyDown,
|
|
1144
|
+
...rest
|
|
1145
|
+
};
|
|
1146
|
+
return multiline ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("textarea", { ...props, className: `${props.className} h-auto min-h-[3.5rem] py-1` }, value) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("input", { type: "text", ...props }, value);
|
|
1147
|
+
}
|
|
1148
|
+
var plainNumber = (field, value) => (0, import_parameters.formatParameterNumber)({ ...field, unit: void 0 }, value);
|
|
1149
|
+
function ParameterNumberInput({
|
|
1150
|
+
field,
|
|
1151
|
+
value,
|
|
1152
|
+
onChange,
|
|
1153
|
+
disabled,
|
|
1154
|
+
id,
|
|
1155
|
+
"aria-label": ariaLabel
|
|
1156
|
+
}) {
|
|
1157
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
|
|
1158
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1159
|
+
ParameterCommitInput,
|
|
1160
|
+
{
|
|
1161
|
+
id,
|
|
1162
|
+
inputMode: "decimal",
|
|
1163
|
+
"aria-label": ariaLabel ?? (0, import_parameters.parameterLabel)(field),
|
|
1164
|
+
value: plainNumber(field, value),
|
|
1165
|
+
disabled: disabled || field.readOnly,
|
|
1166
|
+
className: `${parameterInputClass} ${field.unit ? "pr-6" : ""}`,
|
|
1167
|
+
onCommit: (text, input4) => {
|
|
1168
|
+
const n = (0, import_parameters.parseParameterNumberInput)(field, text);
|
|
1169
|
+
if (n === null || n === value) input4.value = plainNumber(field, value);
|
|
1170
|
+
else onChange(n);
|
|
1171
|
+
},
|
|
1172
|
+
onKeyDown: (e) => {
|
|
1173
|
+
if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
|
|
1174
|
+
e.preventDefault();
|
|
1175
|
+
const step = (0, import_parameters.parameterStep)(field) * (e.shiftKey ? 10 : 1);
|
|
1176
|
+
onChange((0, import_parameters.clampParameterNumber)(field, value + (e.key === "ArrowUp" ? step : -step)));
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
),
|
|
1180
|
+
field.unit && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-[10px] text-muted-foreground", children: field.unit })
|
|
1181
|
+
] });
|
|
1182
|
+
}
|
|
1183
|
+
function ParameterSlider(props) {
|
|
1184
|
+
const { field, value, onChange, onGestureStart, onGestureEnd, disabled } = props;
|
|
1185
|
+
const dragging = (0, import_react12.useRef)(false);
|
|
1186
|
+
const end = () => {
|
|
1187
|
+
if (!dragging.current) return;
|
|
1188
|
+
dragging.current = false;
|
|
1189
|
+
onGestureEnd?.();
|
|
1190
|
+
};
|
|
1191
|
+
if (!(0, import_parameters.isSliderField)(field)) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ParameterNumberInput, { ...props });
|
|
1192
|
+
const step = (0, import_parameters.parameterStep)(field);
|
|
1193
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1194
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1195
|
+
"input",
|
|
1196
|
+
{
|
|
1197
|
+
type: "range",
|
|
1198
|
+
"aria-label": (0, import_parameters.parameterLabel)(field),
|
|
1199
|
+
"aria-valuetext": (0, import_parameters.formatParameterNumber)(field, value),
|
|
1200
|
+
min: field.min,
|
|
1201
|
+
max: field.max,
|
|
1202
|
+
step,
|
|
1203
|
+
value,
|
|
1204
|
+
disabled: disabled || field.readOnly,
|
|
1205
|
+
className: rangeClass,
|
|
1206
|
+
onPointerDown: () => {
|
|
1207
|
+
if (dragging.current) return;
|
|
1208
|
+
dragging.current = true;
|
|
1209
|
+
onGestureStart?.();
|
|
1210
|
+
},
|
|
1211
|
+
onPointerUp: end,
|
|
1212
|
+
onPointerCancel: end,
|
|
1213
|
+
onBlur: end,
|
|
1214
|
+
onChange: (e) => onChange((0, import_parameters.clampParameterNumber)(field, Number(e.target.value)))
|
|
1215
|
+
}
|
|
1216
|
+
),
|
|
1217
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "w-20 shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ParameterNumberInput, { ...props, id: props.id, "aria-label": `${(0, import_parameters.parameterLabel)(field)} value` }) })
|
|
1218
|
+
] });
|
|
1219
|
+
}
|
|
1220
|
+
function ParameterToggle({ field, value, onChange, disabled, id }) {
|
|
1221
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1222
|
+
"button",
|
|
1223
|
+
{
|
|
1224
|
+
type: "button",
|
|
1225
|
+
role: "switch",
|
|
1226
|
+
id,
|
|
1227
|
+
"aria-checked": value,
|
|
1228
|
+
"aria-label": (0, import_parameters.parameterLabel)(field),
|
|
1229
|
+
disabled: disabled || field.readOnly,
|
|
1230
|
+
onClick: () => onChange(!value),
|
|
1231
|
+
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"}`,
|
|
1232
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1233
|
+
"span",
|
|
1234
|
+
{
|
|
1235
|
+
className: `pointer-events-none block h-4 w-4 rounded-full bg-background shadow transition-transform ${value ? "translate-x-4" : "translate-x-0.5"}`
|
|
1236
|
+
}
|
|
1237
|
+
)
|
|
1238
|
+
}
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
function ParameterSelect({ field, value, onChange, disabled, id }) {
|
|
1242
|
+
const options = (0, import_parameters.getChoiceOptions)(field);
|
|
1243
|
+
const off = disabled || field.readOnly;
|
|
1244
|
+
if (field.control === "segmented") {
|
|
1245
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.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_runtime12.jsx)(
|
|
1246
|
+
"button",
|
|
1247
|
+
{
|
|
1248
|
+
type: "button",
|
|
1249
|
+
role: "radio",
|
|
1250
|
+
"aria-checked": o.value === value,
|
|
1251
|
+
title: o.description,
|
|
1252
|
+
disabled: off,
|
|
1253
|
+
onClick: () => onChange(o.value),
|
|
1254
|
+
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"}`,
|
|
1255
|
+
children: (0, import_parameters.choiceOptionLabel)(o)
|
|
1256
|
+
},
|
|
1257
|
+
String(o.value)
|
|
1258
|
+
)) });
|
|
1259
|
+
}
|
|
1260
|
+
const index = options.findIndex((o) => o.value === value);
|
|
1261
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1262
|
+
"select",
|
|
1263
|
+
{
|
|
1264
|
+
id,
|
|
1265
|
+
"aria-label": (0, import_parameters.parameterLabel)(field),
|
|
1266
|
+
value: String(index),
|
|
1267
|
+
disabled: off,
|
|
1268
|
+
onChange: (e) => {
|
|
1269
|
+
const option = options[Number(e.target.value)];
|
|
1270
|
+
if (option) onChange(option.value);
|
|
1271
|
+
},
|
|
1272
|
+
className: parameterInputClass,
|
|
1273
|
+
children: options.map((o, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: String(i), title: o.description, children: (0, import_parameters.choiceOptionLabel)(o) }, String(o.value)))
|
|
1274
|
+
}
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd, disabled, id }) {
|
|
1278
|
+
const picking = (0, import_react12.useRef)(false);
|
|
1279
|
+
const alpha = !!field.alpha;
|
|
1280
|
+
const hex = (0, import_parameters.normalizeHexColor)(value, true) ?? "#000000ff";
|
|
1281
|
+
const rgb = hex.slice(0, 7);
|
|
1282
|
+
const a = hex.slice(7);
|
|
1283
|
+
const off = disabled || field.readOnly;
|
|
1284
|
+
const start = () => {
|
|
1285
|
+
if (picking.current) return;
|
|
1286
|
+
picking.current = true;
|
|
1287
|
+
onGestureStart?.();
|
|
1288
|
+
};
|
|
1289
|
+
const end = () => {
|
|
1290
|
+
if (!picking.current) return;
|
|
1291
|
+
picking.current = false;
|
|
1292
|
+
onGestureEnd?.();
|
|
1293
|
+
};
|
|
1294
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-1.5", children: [
|
|
1295
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1296
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.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: [
|
|
1297
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "absolute inset-0", style: { background: alpha ? hex : rgb } }),
|
|
1298
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1299
|
+
"input",
|
|
1300
|
+
{
|
|
1301
|
+
type: "color",
|
|
1302
|
+
"aria-label": (0, import_parameters.parameterLabel)(field),
|
|
1303
|
+
value: rgb,
|
|
1304
|
+
disabled: off,
|
|
1305
|
+
className: "absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed",
|
|
1306
|
+
onChange: (e) => {
|
|
1307
|
+
start();
|
|
1308
|
+
onChange(alpha ? `${e.target.value}${a}` : e.target.value);
|
|
1309
|
+
},
|
|
1310
|
+
onBlur: end
|
|
1311
|
+
}
|
|
1312
|
+
)
|
|
1313
|
+
] }),
|
|
1314
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1315
|
+
ParameterCommitInput,
|
|
1316
|
+
{
|
|
1317
|
+
id,
|
|
1318
|
+
"aria-label": `${(0, import_parameters.parameterLabel)(field)} hex`,
|
|
1319
|
+
value: alpha ? hex : rgb,
|
|
1320
|
+
disabled: off,
|
|
1321
|
+
spellCheck: false,
|
|
1322
|
+
className: `${parameterInputClass} font-mono`,
|
|
1323
|
+
onCommit: (text) => {
|
|
1324
|
+
const next = (0, import_parameters.normalizeHexColor)(text, alpha);
|
|
1325
|
+
if (next) onChange(next);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
)
|
|
1329
|
+
] }),
|
|
1330
|
+
alpha && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1331
|
+
"input",
|
|
1332
|
+
{
|
|
1333
|
+
type: "range",
|
|
1334
|
+
"aria-label": `${(0, import_parameters.parameterLabel)(field)} opacity`,
|
|
1335
|
+
min: 0,
|
|
1336
|
+
max: 255,
|
|
1337
|
+
step: 1,
|
|
1338
|
+
value: parseInt(a, 16),
|
|
1339
|
+
disabled: off,
|
|
1340
|
+
className: rangeClass,
|
|
1341
|
+
onPointerDown: start,
|
|
1342
|
+
onPointerUp: end,
|
|
1343
|
+
onPointerCancel: end,
|
|
1344
|
+
onBlur: end,
|
|
1345
|
+
onChange: (e) => onChange(`${rgb}${Number(e.target.value).toString(16).padStart(2, "0")}`)
|
|
1346
|
+
}
|
|
1347
|
+
),
|
|
1348
|
+
field.swatches && field.swatches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex flex-wrap gap-1", children: field.swatches.map((s) => {
|
|
1349
|
+
const swatch = (0, import_parameters.normalizeHexColor)(s, alpha);
|
|
1350
|
+
if (!swatch) return null;
|
|
1351
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1352
|
+
"button",
|
|
1353
|
+
{
|
|
1354
|
+
type: "button",
|
|
1355
|
+
title: swatch,
|
|
1356
|
+
"aria-label": `${(0, import_parameters.parameterLabel)(field)} ${swatch}`,
|
|
1357
|
+
"aria-pressed": swatch === (alpha ? hex : rgb),
|
|
1358
|
+
disabled: off,
|
|
1359
|
+
onClick: () => onChange(swatch),
|
|
1360
|
+
className: "h-5 w-5 rounded border border-input aria-pressed:ring-1 aria-pressed:ring-primary",
|
|
1361
|
+
style: { background: swatch }
|
|
1362
|
+
},
|
|
1363
|
+
s
|
|
1364
|
+
);
|
|
1365
|
+
}) })
|
|
1366
|
+
] });
|
|
1367
|
+
}
|
|
1368
|
+
function ParameterVector({ field, value, onChange, disabled, id }) {
|
|
1369
|
+
const size = (0, import_parameters.vectorSize)(field);
|
|
1370
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "grid gap-1.5", style: { gridTemplateColumns: `repeat(${size}, minmax(0, 1fr))` }, children: Array.from({ length: size }, (_, i) => {
|
|
1371
|
+
const component = (0, import_parameters.vectorComponentField)(field, i);
|
|
1372
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex min-w-0 items-center gap-1", children: [
|
|
1373
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-3 shrink-0 text-[10px] uppercase text-muted-foreground", children: component.label }),
|
|
1374
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1375
|
+
ParameterNumberInput,
|
|
1376
|
+
{
|
|
1377
|
+
field: component,
|
|
1378
|
+
value: value[i] ?? 0,
|
|
1379
|
+
id: i === 0 ? id : void 0,
|
|
1380
|
+
"aria-label": `${(0, import_parameters.parameterLabel)(field)} ${component.label}`,
|
|
1381
|
+
disabled: disabled || field.readOnly,
|
|
1382
|
+
onChange: (n) => onChange(Array.from({ length: size }, (_2, j) => j === i ? n : value[j] ?? 0))
|
|
1383
|
+
}
|
|
1384
|
+
)
|
|
1385
|
+
] }, i);
|
|
1386
|
+
}) });
|
|
1387
|
+
}
|
|
1388
|
+
function ParameterImage({ field, value, onChange, disabled, id, resolveFile }) {
|
|
1389
|
+
const off = disabled || field.readOnly;
|
|
1390
|
+
const name = (0, import_parameters.parameterLabel)(field);
|
|
1391
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-1.5", children: [
|
|
1392
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1393
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.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_runtime12.jsx)("img", { src: value, alt: name, className: "h-full w-full object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "None" }) }),
|
|
1394
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
1395
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1396
|
+
ParameterCommitInput,
|
|
1397
|
+
{
|
|
1398
|
+
id,
|
|
1399
|
+
"aria-label": `${name} URL`,
|
|
1400
|
+
placeholder: "Image URL",
|
|
1401
|
+
value: value.startsWith("data:") ? "" : value,
|
|
1402
|
+
disabled: off,
|
|
1403
|
+
onCommit: (text) => onChange(text.trim())
|
|
1404
|
+
}
|
|
1405
|
+
),
|
|
1406
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-1", children: [
|
|
1407
|
+
resolveFile && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: `${parameterButtonClass} cursor-pointer ${off ? "pointer-events-none opacity-50" : ""}`, children: [
|
|
1408
|
+
"Choose file\u2026",
|
|
1409
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1410
|
+
"input",
|
|
1411
|
+
{
|
|
1412
|
+
type: "file",
|
|
1413
|
+
className: "sr-only",
|
|
1414
|
+
accept: field.accept ?? "image/*",
|
|
1415
|
+
disabled: off,
|
|
1416
|
+
"aria-label": `${name} file`,
|
|
1417
|
+
onChange: async (e) => {
|
|
1418
|
+
const file = e.target.files?.[0];
|
|
1419
|
+
e.target.value = "";
|
|
1420
|
+
if (file) onChange(await resolveFile(file));
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
)
|
|
1424
|
+
] }),
|
|
1425
|
+
value && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: parameterButtonClass, disabled: off, onClick: () => onChange(""), "aria-label": `Clear ${name}`, children: "Clear" })
|
|
1426
|
+
] })
|
|
1427
|
+
] })
|
|
1428
|
+
] }),
|
|
1429
|
+
field.options && field.options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex flex-wrap gap-1", children: field.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1430
|
+
"button",
|
|
1431
|
+
{
|
|
1432
|
+
type: "button",
|
|
1433
|
+
title: o.label ?? o.url,
|
|
1434
|
+
"aria-label": o.label ?? o.url,
|
|
1435
|
+
"aria-pressed": o.url === value,
|
|
1436
|
+
disabled: off,
|
|
1437
|
+
onClick: () => onChange(o.url),
|
|
1438
|
+
className: "h-9 w-12 overflow-hidden rounded border border-input aria-pressed:ring-2 aria-pressed:ring-primary",
|
|
1439
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("img", { src: o.url, alt: "", className: "h-full w-full object-cover" })
|
|
1440
|
+
},
|
|
1441
|
+
o.url
|
|
1442
|
+
)) })
|
|
1443
|
+
] });
|
|
1444
|
+
}
|
|
1445
|
+
function ParameterText({ field, value, onChange, disabled, id }) {
|
|
1446
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1447
|
+
ParameterCommitInput,
|
|
1448
|
+
{
|
|
1449
|
+
id,
|
|
1450
|
+
"aria-label": (0, import_parameters.parameterLabel)(field),
|
|
1451
|
+
multiline: field.multiline,
|
|
1452
|
+
placeholder: field.placeholder,
|
|
1453
|
+
maxLength: field.maxLength,
|
|
1454
|
+
value,
|
|
1455
|
+
disabled: disabled || field.readOnly,
|
|
1456
|
+
onCommit: onChange
|
|
1457
|
+
}
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
function ParameterFieldControl({ field, value, onChange, resolveFile, ...rest }) {
|
|
1461
|
+
const set = onChange;
|
|
1462
|
+
switch (field.type) {
|
|
1463
|
+
case "number":
|
|
1464
|
+
case "int":
|
|
1465
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ParameterSlider, { field, value, onChange: set, ...rest });
|
|
1466
|
+
case "boolean":
|
|
1467
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ParameterToggle, { field, value, onChange: set, ...rest });
|
|
1468
|
+
case "choice":
|
|
1469
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ParameterSelect, { field, value, onChange: set, ...rest });
|
|
1470
|
+
case "color":
|
|
1471
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ParameterColor, { field, value, onChange: set, ...rest });
|
|
1472
|
+
case "vector":
|
|
1473
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ParameterVector, { field, value, onChange: set, ...rest });
|
|
1474
|
+
case "image":
|
|
1475
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ParameterImage, { field, value, onChange: set, resolveFile, ...rest });
|
|
1476
|
+
case "text":
|
|
1477
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ParameterText, { field, value, onChange: set, ...rest });
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// src/effects/EffectParameters.tsx
|
|
1482
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1483
|
+
function EffectParameters({ description, values, onChange, onReset, keyframed, onPreset, disabled = false, className = "" }) {
|
|
1484
|
+
const fields = (0, import_react13.useMemo)(() => (0, import_effects3.effectParameterFields)(description), [description]);
|
|
1485
|
+
const idBase = (0, import_react13.useId)();
|
|
1486
|
+
const gesture = import_react13.default.useRef(null);
|
|
1487
|
+
const count = import_react13.default.useRef(0);
|
|
1488
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `space-y-2 ${className}`, "data-effect-parameters": description.id, children: [
|
|
1489
|
+
onPreset && description.presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1490
|
+
"select",
|
|
1491
|
+
{
|
|
1492
|
+
className: "w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs",
|
|
1493
|
+
"aria-label": `${description.name} presets`,
|
|
1494
|
+
value: "",
|
|
1495
|
+
disabled,
|
|
1496
|
+
onChange: (e) => {
|
|
1497
|
+
const preset = description.presets.find((p) => p.name === e.currentTarget.value);
|
|
1498
|
+
if (preset) onPreset({ ...description.defaults, ...preset.values });
|
|
1499
|
+
},
|
|
1500
|
+
children: [
|
|
1501
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "", children: "Presets\u2026" }),
|
|
1502
|
+
description.presets.map((p) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: p.name, children: p.name }, p.name))
|
|
1503
|
+
]
|
|
1504
|
+
}
|
|
1505
|
+
),
|
|
1506
|
+
fields.map((field) => {
|
|
1507
|
+
const label = (0, import_parameters2.parameterLabel)(field);
|
|
1508
|
+
const inputId = `${idBase}-${field.id}`;
|
|
1509
|
+
const keyed = keyframed?.has(field.id) ?? false;
|
|
1510
|
+
const value = (0, import_effects3.effectFieldValue)(field, values[field.id]);
|
|
1511
|
+
const changed = !keyed && !(0, import_parameters2.parameterValuesEqual)(value, field.default);
|
|
1512
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-1", "data-effect-control": field.id, children: [
|
|
1513
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
1514
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("label", { htmlFor: inputId, className: "min-w-0 flex-1 truncate text-xs font-medium", title: field.description, children: label }),
|
|
1515
|
+
keyed && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "keyframed" }),
|
|
1516
|
+
onReset && changed && !disabled && /* @__PURE__ */ (0, import_jsx_runtime13.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" })
|
|
1517
|
+
] }),
|
|
1518
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1519
|
+
ParameterFieldControl,
|
|
1520
|
+
{
|
|
1521
|
+
field,
|
|
1522
|
+
value,
|
|
1523
|
+
id: inputId,
|
|
1524
|
+
disabled: disabled || keyed,
|
|
1525
|
+
onChange: (v) => onChange(field.id, v, gesture.current),
|
|
1526
|
+
onGestureStart: () => gesture.current = `${idBase}:${field.id}:${++count.current}`,
|
|
1527
|
+
onGestureEnd: () => gesture.current = null
|
|
1528
|
+
}
|
|
1529
|
+
)
|
|
1530
|
+
] }, field.id);
|
|
1531
|
+
}),
|
|
1532
|
+
!fields.length && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No controls." })
|
|
1533
|
+
] });
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// src/animation-editor/AnimationEffectsPanel.tsx
|
|
1537
|
+
var import_react14 = require("react");
|
|
1538
|
+
var import_animation_editor9 = require("@pixerate/editor/animation-editor");
|
|
1539
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1540
|
+
var btn4 = "rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40";
|
|
1541
|
+
function Controls({ controller, instance, effect, values }) {
|
|
1542
|
+
const provider = controller.effectsProvider;
|
|
1543
|
+
const { description, error } = useEffectDescription(provider, effect);
|
|
1544
|
+
const [message, setMessage] = (0, import_react14.useState)(null);
|
|
1545
|
+
const report = (r) => void r.then((x) => setMessage(x.ok ? null : x.error));
|
|
1546
|
+
if (!provider) return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider: its commands are kept as they are." });
|
|
1547
|
+
if (error) return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-[11px] text-amber-500", children: error });
|
|
1548
|
+
if (!description) return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "Loading\u2026" });
|
|
1549
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
|
|
1550
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1551
|
+
EffectParameters,
|
|
1552
|
+
{
|
|
1553
|
+
description,
|
|
1554
|
+
values,
|
|
1555
|
+
onChange: (k, v, gesture) => report(controller.updateMotionEffect(instance, { [k]: v }, { merge: gesture ?? void 0 })),
|
|
1556
|
+
onReset: (k) => report(controller.updateMotionEffect(instance, { [k]: null })),
|
|
1557
|
+
onPreset: (v) => report(controller.updateMotionEffect(instance, v))
|
|
1558
|
+
}
|
|
1559
|
+
),
|
|
1560
|
+
message && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
|
|
1561
|
+
] });
|
|
1562
|
+
}
|
|
1563
|
+
function AnimationEffectsPanel({ controller, node, className = "" }) {
|
|
1564
|
+
const state = useAnimationEditorState(controller);
|
|
1565
|
+
const provider = state.effectsProvider;
|
|
1566
|
+
const nodes = (0, import_react14.useMemo)(() => [...(0, import_animation_editor9.indexMotionNodes)(state.scene.artwork.nodes).keys()], [state.scene.artwork]);
|
|
1567
|
+
const [chosen, setChosen] = (0, import_react14.useState)("");
|
|
1568
|
+
const target = node && nodes.includes(node) ? node : chosen && nodes.includes(chosen) ? chosen : "";
|
|
1569
|
+
const [browse, setBrowse] = (0, import_react14.useState)(null);
|
|
1570
|
+
const [open, setOpen] = (0, import_react14.useState)(null);
|
|
1571
|
+
const [message, setMessage] = (0, import_react14.useState)(null);
|
|
1572
|
+
const report = (r) => void Promise.resolve(r).then((x) => setMessage(x.ok ? x.notes.length ? x.notes.join(" ") : null : x.error));
|
|
1573
|
+
const applied = (0, import_react14.useMemo)(() => controller.appliedEffects(), [controller, state.scene]);
|
|
1574
|
+
const disabled = state.readOnly;
|
|
1575
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("section", { className: `flex flex-col gap-2 text-xs ${className}`, "aria-label": "Effects", "data-animation-effects": "", children: [
|
|
1576
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
1577
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "flex-1 font-semibold", children: "Effects" }),
|
|
1578
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("select", { className: "rounded border border-border bg-transparent px-1 py-0.5 text-[11px]", "aria-label": "Node to animate", value: target, disabled: !!node, onChange: (e) => setChosen(e.currentTarget.value), children: [
|
|
1579
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "", children: "Node\u2026" }),
|
|
1580
|
+
nodes.map((id) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: id, children: id }, id))
|
|
1581
|
+
] }),
|
|
1582
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: btn4, disabled: disabled || !provider || !target, "aria-pressed": browse === "motion", onClick: () => setBrowse(browse === "motion" ? null : "motion"), children: "Animate\u2026" }),
|
|
1583
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: btn4, disabled: disabled || !provider, "aria-pressed": browse === "vector", onClick: () => setBrowse(browse === "vector" ? null : "vector"), children: "Vector effect\u2026" })
|
|
1584
|
+
] }),
|
|
1585
|
+
!provider && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider is registered. Commands effects made are kept." }),
|
|
1586
|
+
browse === "motion" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(EffectsBrowser, { provider, uses: ["motion"], kinds: ["motion"], title: `Motion for ${target}`, onPick: (e) => (setBrowse(null), report(controller.applyMotionEffect(e.id, [target]))) }),
|
|
1587
|
+
browse === "vector" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(EffectsBrowser, { provider, kinds: ["vector"], title: "Vector effects", onPick: (e) => (setBrowse(null), report(controller.addVectorEffect(e.id))) }),
|
|
1588
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("ol", { className: "space-y-1", children: applied.map((e) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("li", { className: "rounded border border-border p-1", "data-motion-effect": e.instance, children: [
|
|
1589
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
1590
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "button", className: "min-w-0 flex-1 truncate text-left", "aria-expanded": open === e.instance, onClick: () => setOpen(open === e.instance ? null : e.instance), children: [
|
|
1591
|
+
e.name ?? e.effect,
|
|
1592
|
+
" ",
|
|
1593
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "text-muted-foreground", children: [
|
|
1594
|
+
"\xB7 ",
|
|
1595
|
+
e.kind === "vector" ? "picture" : e.targets.join(", ")
|
|
1596
|
+
] })
|
|
1597
|
+
] }),
|
|
1598
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: btn4, disabled, "aria-label": `Remove ${e.name ?? e.effect}`, onClick: () => report(controller.removeMotionEffect(e.instance)), children: "\u2715" })
|
|
1599
|
+
] }),
|
|
1600
|
+
open === e.instance && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "mt-1", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Controls, { controller, instance: e.instance, effect: e.effect, values: e.values }) })
|
|
1601
|
+
] }, e.instance)) }),
|
|
1602
|
+
!applied.length && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No provider effects in this scene." }),
|
|
1603
|
+
message && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { role: "status", className: "text-[11px] text-muted-foreground", "data-effects-message": "", children: message })
|
|
1604
|
+
] });
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
// src/animation-editor/AnimationEditor.tsx
|
|
1608
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
971
1609
|
var ONION = { before: 3, after: 1 };
|
|
972
|
-
var
|
|
973
|
-
function AnimationEditor({ controller, values, onionSkin, motionPaths, showIssues = true, editable = true, className = "" }) {
|
|
1610
|
+
var btn5 = "rounded border border-border px-2 py-0.5 text-xs disabled:opacity-40";
|
|
1611
|
+
function AnimationEditor({ controller, values, onionSkin, motionPaths, showIssues = true, editable = true, effects, className = "" }) {
|
|
974
1612
|
const state = useAnimationEditorState(controller);
|
|
1613
|
+
useEffects(controller, effects);
|
|
975
1614
|
const { issues, scene, selection, readOnly } = state;
|
|
976
|
-
const [picked, setPicked] = (0,
|
|
977
|
-
const [onion, setOnion] = (0,
|
|
978
|
-
const [paths, setPaths] = (0,
|
|
1615
|
+
const [picked, setPicked] = (0, import_react15.useState)(null);
|
|
1616
|
+
const [onion, setOnion] = (0, import_react15.useState)(false);
|
|
1617
|
+
const [paths, setPaths] = (0, import_react15.useState)(false);
|
|
979
1618
|
const command = selection?.command !== void 0 ? scene.shots.find((s) => s.id === selection.shot)?.run[selection.command] : void 0;
|
|
980
1619
|
const targets = command && "targets" in command ? command.targets : [];
|
|
981
|
-
const nodes = (0,
|
|
1620
|
+
const nodes = (0, import_react15.useMemo)(() => (0, import_animation_editor10.indexMotionNodes)(scene.artwork.nodes), [scene.artwork]);
|
|
982
1621
|
const pickedValid = picked && nodes.has(picked) ? picked : null;
|
|
983
1622
|
const onPick = (id, event) => {
|
|
984
1623
|
if (event.shiftKey && id && command && "targets" in command && selection?.command !== void 0 && !readOnly) {
|
|
@@ -988,7 +1627,7 @@ function AnimationEditor({ controller, values, onionSkin, motionPaths, showIssue
|
|
|
988
1627
|
}
|
|
989
1628
|
setPicked(id);
|
|
990
1629
|
};
|
|
991
|
-
const stage = /* @__PURE__ */ (0,
|
|
1630
|
+
const stage = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
992
1631
|
AnimationStage,
|
|
993
1632
|
{
|
|
994
1633
|
controller,
|
|
@@ -1000,14 +1639,14 @@ function AnimationEditor({ controller, values, onionSkin, motionPaths, showIssue
|
|
|
1000
1639
|
className: "rounded-md border border-border bg-muted/40"
|
|
1001
1640
|
}
|
|
1002
1641
|
);
|
|
1003
|
-
const issueList = showIssues && issues.length > 0 && /* @__PURE__ */ (0,
|
|
1642
|
+
const issueList = showIssues && issues.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("ul", { className: "space-y-0.5 text-xs", "aria-label": "Animation issues", children: issues.map((issue, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("li", { className: issue.severity === "error" ? "text-red-500" : issue.severity === "warning" ? "text-amber-500" : "text-muted-foreground", children: (0, import_animation_editor10.motionIssueLine)(issue) }, i)) });
|
|
1004
1643
|
if (!editable)
|
|
1005
|
-
return /* @__PURE__ */ (0,
|
|
1644
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `flex flex-col gap-2 ${className}`, "data-animation-editor": "", children: [
|
|
1006
1645
|
stage,
|
|
1007
|
-
/* @__PURE__ */ (0,
|
|
1646
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PlayheadScrubber, { controller }),
|
|
1008
1647
|
issueList
|
|
1009
1648
|
] });
|
|
1010
|
-
return /* @__PURE__ */ (0,
|
|
1649
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
1011
1650
|
"div",
|
|
1012
1651
|
{
|
|
1013
1652
|
className: `grid gap-3 lg:grid-cols-[minmax(0,1fr)_20rem] ${className}`,
|
|
@@ -1020,39 +1659,40 @@ function AnimationEditor({ controller, values, onionSkin, motionPaths, showIssue
|
|
|
1020
1659
|
else controller.undo();
|
|
1021
1660
|
},
|
|
1022
1661
|
children: [
|
|
1023
|
-
/* @__PURE__ */ (0,
|
|
1662
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex min-w-0 flex-col gap-2", children: [
|
|
1024
1663
|
stage,
|
|
1025
|
-
/* @__PURE__ */ (0,
|
|
1026
|
-
/* @__PURE__ */ (0,
|
|
1027
|
-
/* @__PURE__ */ (0,
|
|
1028
|
-
/* @__PURE__ */ (0,
|
|
1029
|
-
onionSkin === void 0 && /* @__PURE__ */ (0,
|
|
1030
|
-
/* @__PURE__ */ (0,
|
|
1664
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PlayheadScrubber, { controller }),
|
|
1665
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [
|
|
1666
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: btn5, onClick: () => controller.undo(), disabled: !state.canUndo, title: state.canUndo ? `Undo ${state.undoLabel}` : "Nothing to undo", "data-undo": "", children: "Undo" }),
|
|
1667
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: btn5, onClick: () => controller.redo(), disabled: !state.canRedo, title: state.canRedo ? `Redo ${state.redoLabel}` : "Nothing to redo", "data-redo": "", children: "Redo" }),
|
|
1668
|
+
onionSkin === void 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1", children: [
|
|
1669
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("input", { type: "checkbox", checked: onion, onChange: (e) => setOnion(e.target.checked) }),
|
|
1031
1670
|
" Onion skin"
|
|
1032
1671
|
] }),
|
|
1033
|
-
motionPaths === void 0 && /* @__PURE__ */ (0,
|
|
1034
|
-
/* @__PURE__ */ (0,
|
|
1672
|
+
motionPaths === void 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1", children: [
|
|
1673
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("input", { type: "checkbox", checked: paths, onChange: (e) => setPaths(e.target.checked) }),
|
|
1035
1674
|
" Motion paths"
|
|
1036
1675
|
] }),
|
|
1037
|
-
/* @__PURE__ */ (0,
|
|
1676
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ml-auto", "data-picked-element": pickedValid ?? void 0, children: pickedValid ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
1038
1677
|
"Picked ",
|
|
1039
|
-
/* @__PURE__ */ (0,
|
|
1678
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("code", { className: "text-rose-500", children: pickedValid }),
|
|
1040
1679
|
" ",
|
|
1041
|
-
/* @__PURE__ */ (0,
|
|
1680
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: "underline", onClick: () => setPicked(null), children: "clear" })
|
|
1042
1681
|
] }) : "Click the stage to pick an element; shift-click to toggle it as a target." })
|
|
1043
1682
|
] }),
|
|
1044
1683
|
issueList
|
|
1045
1684
|
] }),
|
|
1046
|
-
/* @__PURE__ */ (0,
|
|
1047
|
-
/* @__PURE__ */ (0,
|
|
1048
|
-
/* @__PURE__ */ (0,
|
|
1049
|
-
/* @__PURE__ */ (0,
|
|
1050
|
-
|
|
1051
|
-
/* @__PURE__ */ (0,
|
|
1685
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex min-w-0 flex-col gap-3", children: [
|
|
1686
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CommandList, { controller, targets: pickedValid ? [pickedValid] : void 0 }),
|
|
1687
|
+
(state.effectsProvider || controller.appliedEffects().length > 0) && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AnimationEffectsPanel, { controller, node: pickedValid, className: "border-t border-border pt-2" }),
|
|
1688
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CommandInspector, { controller, picked: pickedValid, className: "border-t border-border pt-2" }),
|
|
1689
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("details", { open: true, className: "border-t border-border pt-2", children: [
|
|
1690
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("summary", { className: "cursor-pointer text-xs font-medium", children: "Shot" }),
|
|
1691
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ShotInspector, { controller, className: "mt-1" })
|
|
1052
1692
|
] }),
|
|
1053
|
-
/* @__PURE__ */ (0,
|
|
1054
|
-
/* @__PURE__ */ (0,
|
|
1055
|
-
/* @__PURE__ */ (0,
|
|
1693
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("details", { className: "border-t border-border pt-2", children: [
|
|
1694
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("summary", { className: "cursor-pointer text-xs font-medium", children: "Scene settings" }),
|
|
1695
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SceneSettings, { controller, className: "mt-1" })
|
|
1056
1696
|
] })
|
|
1057
1697
|
] })
|
|
1058
1698
|
]
|
|
@@ -1062,6 +1702,7 @@ function AnimationEditor({ controller, values, onionSkin, motionPaths, showIssue
|
|
|
1062
1702
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1063
1703
|
0 && (module.exports = {
|
|
1064
1704
|
AnimationEditor,
|
|
1705
|
+
AnimationEffectsPanel,
|
|
1065
1706
|
AnimationStage,
|
|
1066
1707
|
CommandInspector,
|
|
1067
1708
|
CommandList,
|
|
@@ -1073,6 +1714,7 @@ function AnimationEditor({ controller, values, onionSkin, motionPaths, showIssue
|
|
|
1073
1714
|
useAnimationEditor,
|
|
1074
1715
|
useAnimationEditorController,
|
|
1075
1716
|
useAnimationEditorState,
|
|
1076
|
-
useAnimationEditorTools
|
|
1717
|
+
useAnimationEditorTools,
|
|
1718
|
+
useAnimationEffectsTools
|
|
1077
1719
|
});
|
|
1078
1720
|
//# sourceMappingURL=index.cjs.map
|