@pixerate/editor-react 0.76.0 → 0.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/PaintGradientEditorView-CCPCY5Ib.d.cts +14 -0
- package/dist/PaintGradientEditorView-CCPCY5Ib.d.ts +14 -0
- package/dist/canvas/index.cjs +662 -561
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +36 -5
- package/dist/canvas/index.d.ts +36 -5
- package/dist/canvas/index.js +232 -129
- package/dist/canvas/index.js.map +1 -1
- package/dist/{chunk-LWMUV5TC.js → chunk-2EW42UXC.js} +639 -461
- package/dist/chunk-2EW42UXC.js.map +1 -0
- package/dist/chunk-FX3EREF5.js +206 -0
- package/dist/chunk-FX3EREF5.js.map +1 -0
- package/dist/chunk-MO354USR.js +126 -0
- package/dist/chunk-MO354USR.js.map +1 -0
- package/dist/chunk-ZQZAKUUM.js +416 -0
- package/dist/chunk-ZQZAKUUM.js.map +1 -0
- package/dist/effects/index.cjs +606 -0
- package/dist/effects/index.cjs.map +1 -0
- package/dist/effects/index.d.cts +80 -0
- package/dist/effects/index.d.ts +80 -0
- package/dist/effects/index.js +16 -0
- package/dist/effects/index.js.map +1 -0
- package/dist/image-editor/index.cjs +1388 -534
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +36 -3
- package/dist/image-editor/index.d.ts +36 -3
- package/dist/image-editor/index.js +10 -1
- package/dist/index.cjs +1806 -952
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/paint-editor/index.cjs +438 -289
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +8 -3
- package/dist/paint-editor/index.d.ts +8 -3
- package/dist/paint-editor/index.js +215 -187
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/parameters/index.js +55 -439
- package/dist/parameters/index.js.map +1 -1
- package/dist/video-editor/index.cjs +732 -17
- package/dist/video-editor/index.cjs.map +1 -1
- package/dist/video-editor/index.d.cts +31 -3
- package/dist/video-editor/index.d.ts +31 -3
- package/dist/video-editor/index.js +184 -17
- package/dist/video-editor/index.js.map +1 -1
- package/package.json +8 -3
- package/dist/chunk-LWMUV5TC.js.map +0 -1
|
@@ -34,6 +34,7 @@ __export(video_editor_exports, {
|
|
|
34
34
|
MediaBin: () => MediaBin,
|
|
35
35
|
TranscriptPanel: () => TranscriptPanel,
|
|
36
36
|
VideoEditor: () => VideoEditor,
|
|
37
|
+
VideoEffectsPanel: () => VideoEffectsPanel,
|
|
37
38
|
VideoPreview: () => VideoPreview,
|
|
38
39
|
VideoScopes: () => VideoScopes,
|
|
39
40
|
VideoTimeline: () => VideoTimeline,
|
|
@@ -41,7 +42,8 @@ __export(video_editor_exports, {
|
|
|
41
42
|
useVideoEditor: () => useVideoEditor,
|
|
42
43
|
useVideoEditorController: () => useVideoEditorController,
|
|
43
44
|
useVideoEditorState: () => useVideoEditorState,
|
|
44
|
-
useVideoEditorTools: () => useVideoEditorTools
|
|
45
|
+
useVideoEditorTools: () => useVideoEditorTools,
|
|
46
|
+
useVideoEffectsTools: () => useVideoEffectsTools
|
|
45
47
|
});
|
|
46
48
|
module.exports = __toCommonJS(video_editor_exports);
|
|
47
49
|
|
|
@@ -91,6 +93,13 @@ function useVideoEditorTools(registry, controller, options = {}) {
|
|
|
91
93
|
return registry.register((0, import_video_editor.createVideoEditorToolProvider)(controller, { id, namespace, readOnly, rasterize, framePixels, framePixelsTimeout }));
|
|
92
94
|
}, [registry, controller, id, namespace, readOnly, rasterize, framePixels, framePixelsTimeout]);
|
|
93
95
|
}
|
|
96
|
+
function useVideoEffectsTools(registry, controller, options = {}) {
|
|
97
|
+
const { id, namespace, readOnly } = options;
|
|
98
|
+
(0, import_react.useEffect)(() => {
|
|
99
|
+
if (!registry) return;
|
|
100
|
+
return registry.register((0, import_video_editor.createVideoEffectsToolProvider)(controller, { id, namespace, readOnly }));
|
|
101
|
+
}, [registry, controller, id, namespace, readOnly]);
|
|
102
|
+
}
|
|
94
103
|
|
|
95
104
|
// src/video-editor/VideoPreview.tsx
|
|
96
105
|
var import_react2 = require("react");
|
|
@@ -830,6 +839,7 @@ function ClipInspector({ controller, className = "" }) {
|
|
|
830
839
|
] }) }),
|
|
831
840
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "Muted", name: "muted", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("input", { type: "checkbox", className: "justify-self-start", checked: !!clip.muted, disabled, "aria-label": "Muted", onChange: (e) => set("muted", e.target.checked || void 0, e.target.checked ? "Mute clip" : "Unmute clip") }) })
|
|
832
841
|
] }),
|
|
842
|
+
clip.type === "effect" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "Effect", name: "effect", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "truncate text-xs", title: "Change its controls in the Effects panel", children: clip.name ?? clip.effect }) }),
|
|
833
843
|
clip.type === "color" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "Colour", name: "color", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Commit, { value: clip.color, "aria-label": "Colour", disabled, onCommit: (raw) => raw.trim() && set("color", raw.trim(), "Change colour") }) }),
|
|
834
844
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "Transition in", name: "transition", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
835
845
|
"select",
|
|
@@ -840,14 +850,15 @@ function ClipInspector({ controller, className = "" }) {
|
|
|
840
850
|
"aria-label": "Transition",
|
|
841
851
|
onChange: (e) => {
|
|
842
852
|
const type = e.target.value;
|
|
853
|
+
const { effect: _effect, ...rest } = transition ?? { duration: DEFAULT_TRANSITION };
|
|
843
854
|
controller.apply(
|
|
844
|
-
{ op: "set_transition", clip: clip.id, transition: type ? { ...
|
|
855
|
+
{ op: "set_transition", clip: clip.id, transition: type ? { ...rest, type, duration: transition?.duration ?? DEFAULT_TRANSITION } : null },
|
|
845
856
|
type ? "Set transition" : "Remove transition"
|
|
846
857
|
);
|
|
847
858
|
},
|
|
848
859
|
children: [
|
|
849
860
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "", children: "None" }),
|
|
850
|
-
import_video_editor6.VIDEO_TRANSITION_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: t, children: import_video_editor6.VIDEO_TRANSITION_LABELS[t] }, t))
|
|
861
|
+
import_video_editor6.VIDEO_TRANSITION_TYPES.filter((t) => t !== "effect" || transition?.type === "effect").map((t) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: t, children: t === "effect" ? `Effect: ${transition?.effect?.name ?? transition?.effect?.effect}` : import_video_editor6.VIDEO_TRANSITION_LABELS[t] }, t))
|
|
851
862
|
]
|
|
852
863
|
}
|
|
853
864
|
) }),
|
|
@@ -1148,30 +1159,732 @@ function TranscriptPanel({ controller, track, editable = true, className = "" })
|
|
|
1148
1159
|
}
|
|
1149
1160
|
|
|
1150
1161
|
// src/video-editor/VideoEditor.tsx
|
|
1162
|
+
var import_react14 = require("react");
|
|
1163
|
+
|
|
1164
|
+
// src/effects/useEffects.ts
|
|
1151
1165
|
var import_react8 = require("react");
|
|
1166
|
+
function useEffects(host, provider) {
|
|
1167
|
+
(0, import_react8.useEffect)(() => {
|
|
1168
|
+
if (!host || !provider) return;
|
|
1169
|
+
return host.useEffects(provider);
|
|
1170
|
+
}, [host, provider]);
|
|
1171
|
+
}
|
|
1172
|
+
function useEffectDescription(provider, effect) {
|
|
1173
|
+
const [state, setState] = (0, import_react8.useState)({ key: "", description: null, error: null });
|
|
1174
|
+
const key = provider && effect ? `${provider.id}:${effect}` : "";
|
|
1175
|
+
(0, import_react8.useEffect)(() => {
|
|
1176
|
+
if (!provider || !effect) return;
|
|
1177
|
+
let live = true;
|
|
1178
|
+
Promise.resolve().then(() => provider.describe(effect)).then(
|
|
1179
|
+
(description) => live && setState({ key, description, error: null }),
|
|
1180
|
+
(e) => live && setState({ key, description: null, error: e instanceof Error ? e.message : String(e) })
|
|
1181
|
+
);
|
|
1182
|
+
return () => {
|
|
1183
|
+
live = false;
|
|
1184
|
+
};
|
|
1185
|
+
}, [provider, effect, key]);
|
|
1186
|
+
return state.key === key ? { description: state.description, error: state.error } : { description: null, error: null };
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// src/effects/EffectThumbnail.tsx
|
|
1190
|
+
var import_react9 = require("react");
|
|
1191
|
+
var import_effects = require("@pixerate/editor/effects");
|
|
1152
1192
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1153
|
-
function
|
|
1193
|
+
function EffectThumbnail({ provider, effect, width = 96, height = 54, className = "" }) {
|
|
1194
|
+
const ref = (0, import_react9.useRef)(null);
|
|
1195
|
+
const [failed, setFailed] = (0, import_react9.useState)(effect.kind === "motion");
|
|
1196
|
+
(0, import_react9.useEffect)(() => {
|
|
1197
|
+
if (effect.kind === "motion") return;
|
|
1198
|
+
let live = true;
|
|
1199
|
+
(0, import_effects.renderEffectThumbnail)(provider, effect, { width, height }).then(
|
|
1200
|
+
(pixels) => {
|
|
1201
|
+
const ctx = live ? ref.current?.getContext("2d") : null;
|
|
1202
|
+
if (!ctx || typeof ImageData === "undefined") return;
|
|
1203
|
+
ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels.data), pixels.width, pixels.height), 0, 0);
|
|
1204
|
+
},
|
|
1205
|
+
() => live && setFailed(true)
|
|
1206
|
+
);
|
|
1207
|
+
return () => {
|
|
1208
|
+
live = false;
|
|
1209
|
+
};
|
|
1210
|
+
}, [provider, effect, width, height]);
|
|
1211
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1212
|
+
"div",
|
|
1213
|
+
{
|
|
1214
|
+
className: `relative overflow-hidden rounded border border-border bg-[repeating-conic-gradient(#8884_0_25%,transparent_0_50%)] bg-[length:12px_12px] ${className}`,
|
|
1215
|
+
style: { aspectRatio: `${width} / ${height}` },
|
|
1216
|
+
"data-effect-thumbnail": effect.id,
|
|
1217
|
+
children: failed ? /* @__PURE__ */ (0, import_jsx_runtime8.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_runtime8.jsx)("canvas", { ref, width, height, className: "h-full w-full", "aria-hidden": "true" })
|
|
1218
|
+
}
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// src/effects/EffectsBrowser.tsx
|
|
1223
|
+
var import_react10 = require("react");
|
|
1224
|
+
var import_effects2 = require("@pixerate/editor/effects");
|
|
1225
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1226
|
+
function EffectsBrowser({ provider, uses, onPick, thumbnails = true, title = "Effects", className = "" }) {
|
|
1227
|
+
const [all, setAll] = (0, import_react10.useState)([]);
|
|
1228
|
+
const [query, setQuery] = (0, import_react10.useState)("");
|
|
1229
|
+
const [category, setCategory] = (0, import_react10.useState)("");
|
|
1230
|
+
const [use, setUse] = (0, import_react10.useState)("");
|
|
1231
|
+
const [error, setError] = (0, import_react10.useState)(null);
|
|
1232
|
+
(0, import_react10.useEffect)(() => {
|
|
1233
|
+
if (!provider) return setAll([]);
|
|
1234
|
+
let live = true;
|
|
1235
|
+
Promise.resolve().then(() => provider.list()).then(
|
|
1236
|
+
(list) => live && (setAll(list), setError(null)),
|
|
1237
|
+
(e) => live && setError(e instanceof Error ? e.message : String(e))
|
|
1238
|
+
);
|
|
1239
|
+
return () => {
|
|
1240
|
+
live = false;
|
|
1241
|
+
};
|
|
1242
|
+
}, [provider]);
|
|
1243
|
+
const [found, setFound] = (0, import_react10.useState)([]);
|
|
1244
|
+
(0, import_react10.useEffect)(() => {
|
|
1245
|
+
if (!provider) return setFound([]);
|
|
1246
|
+
let live = true;
|
|
1247
|
+
Promise.resolve().then(() => provider.search(query, { ...category ? { category } : {}, ...use ? { use } : {} })).then((list) => live && setFound(list), () => live && setFound([]));
|
|
1248
|
+
return () => {
|
|
1249
|
+
live = false;
|
|
1250
|
+
};
|
|
1251
|
+
}, [provider, query, category, use]);
|
|
1252
|
+
const allowed = (e) => (uses ? e.kind !== "motion" || uses.includes("motion") : e.kind !== "motion") && (!uses || e.uses.some((u) => uses.includes(u)));
|
|
1253
|
+
const categories = (0, import_react10.useMemo)(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses]);
|
|
1254
|
+
const shown = found.filter(allowed);
|
|
1255
|
+
if (!provider)
|
|
1256
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.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." });
|
|
1257
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: `flex flex-col gap-2 ${className}`, "data-effects-browser": "", "aria-label": "Effects browser", children: [
|
|
1258
|
+
title && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { className: "text-xs font-semibold", children: title }),
|
|
1259
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
|
|
1260
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1261
|
+
"input",
|
|
1262
|
+
{
|
|
1263
|
+
type: "search",
|
|
1264
|
+
className: "min-w-0 flex-1 rounded border border-border bg-transparent px-1.5 py-0.5 text-xs",
|
|
1265
|
+
placeholder: "Search effects",
|
|
1266
|
+
"aria-label": "Search effects",
|
|
1267
|
+
value: query,
|
|
1268
|
+
onChange: (e) => setQuery(e.currentTarget.value)
|
|
1269
|
+
}
|
|
1270
|
+
),
|
|
1271
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.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: [
|
|
1272
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "", children: "All categories" }),
|
|
1273
|
+
categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: c, children: c }, c))
|
|
1274
|
+
] }),
|
|
1275
|
+
uses && uses.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime9.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: [
|
|
1276
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "", children: "Any use" }),
|
|
1277
|
+
uses.map((u) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: u, children: import_effects2.EFFECT_USE_LABELS[u] }, u))
|
|
1278
|
+
] })
|
|
1279
|
+
] }),
|
|
1280
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: error }),
|
|
1281
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { className: "grid grid-cols-[repeat(auto-fill,minmax(6.5rem,1fr))] gap-1.5", role: "list", children: shown.map((e) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1282
|
+
"button",
|
|
1283
|
+
{
|
|
1284
|
+
type: "button",
|
|
1285
|
+
className: "flex w-full flex-col gap-1 rounded border border-transparent p-1 text-left hover:border-border focus-visible:border-border",
|
|
1286
|
+
title: e.description ?? e.name,
|
|
1287
|
+
"data-effect-id": e.id,
|
|
1288
|
+
onClick: () => onPick(e),
|
|
1289
|
+
children: [
|
|
1290
|
+
thumbnails && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EffectThumbnail, { provider, effect: e }),
|
|
1291
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "truncate text-[11px] font-medium", children: e.name }),
|
|
1292
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "truncate text-[10px] text-muted-foreground", children: e.uses.map((u) => import_effects2.EFFECT_USE_LABELS[u]).join(" \xB7 ") })
|
|
1293
|
+
]
|
|
1294
|
+
}
|
|
1295
|
+
) }, e.id)) }),
|
|
1296
|
+
!shown.length && !error && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects match." })
|
|
1297
|
+
] });
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// src/effects/EffectParameters.tsx
|
|
1301
|
+
var import_react12 = __toESM(require("react"), 1);
|
|
1302
|
+
var import_effects3 = require("@pixerate/editor/effects");
|
|
1303
|
+
var import_parameters2 = require("@pixerate/editor/parameters");
|
|
1304
|
+
|
|
1305
|
+
// src/parameters/fields.tsx
|
|
1306
|
+
var import_react11 = require("react");
|
|
1307
|
+
var import_parameters = require("@pixerate/editor/parameters");
|
|
1308
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1309
|
+
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";
|
|
1310
|
+
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";
|
|
1311
|
+
var rangeClass = "h-4 w-full min-w-0 flex-1 cursor-pointer accent-primary disabled:cursor-not-allowed disabled:opacity-50";
|
|
1312
|
+
function ParameterCommitInput({ value, onCommit, multiline, className, onKeyDown, ...rest }) {
|
|
1313
|
+
const commit = (el) => {
|
|
1314
|
+
if (el.value !== value) onCommit(el.value, el);
|
|
1315
|
+
};
|
|
1316
|
+
const keyDown = (e) => {
|
|
1317
|
+
onKeyDown?.(e);
|
|
1318
|
+
if (e.defaultPrevented) return;
|
|
1319
|
+
if (e.key === "Enter" && (!multiline || e.metaKey || e.ctrlKey)) {
|
|
1320
|
+
e.preventDefault();
|
|
1321
|
+
commit(e.currentTarget);
|
|
1322
|
+
}
|
|
1323
|
+
if (e.key === "Escape") e.currentTarget.value = value;
|
|
1324
|
+
};
|
|
1325
|
+
const props = {
|
|
1326
|
+
defaultValue: value,
|
|
1327
|
+
className: className ?? parameterInputClass,
|
|
1328
|
+
onBlur: (e) => commit(e.currentTarget),
|
|
1329
|
+
onKeyDown: keyDown,
|
|
1330
|
+
...rest
|
|
1331
|
+
};
|
|
1332
|
+
return multiline ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("textarea", { ...props, className: `${props.className} h-auto min-h-[3.5rem] py-1` }, value) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { type: "text", ...props }, value);
|
|
1333
|
+
}
|
|
1334
|
+
var plainNumber = (field2, value) => (0, import_parameters.formatParameterNumber)({ ...field2, unit: void 0 }, value);
|
|
1335
|
+
function ParameterNumberInput({
|
|
1336
|
+
field: field2,
|
|
1337
|
+
value,
|
|
1338
|
+
onChange,
|
|
1339
|
+
disabled,
|
|
1340
|
+
id,
|
|
1341
|
+
"aria-label": ariaLabel
|
|
1342
|
+
}) {
|
|
1343
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
|
|
1344
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1345
|
+
ParameterCommitInput,
|
|
1346
|
+
{
|
|
1347
|
+
id,
|
|
1348
|
+
inputMode: "decimal",
|
|
1349
|
+
"aria-label": ariaLabel ?? (0, import_parameters.parameterLabel)(field2),
|
|
1350
|
+
value: plainNumber(field2, value),
|
|
1351
|
+
disabled: disabled || field2.readOnly,
|
|
1352
|
+
className: `${parameterInputClass} ${field2.unit ? "pr-6" : ""}`,
|
|
1353
|
+
onCommit: (text, input) => {
|
|
1354
|
+
const n = (0, import_parameters.parseParameterNumberInput)(field2, text);
|
|
1355
|
+
if (n === null || n === value) input.value = plainNumber(field2, value);
|
|
1356
|
+
else onChange(n);
|
|
1357
|
+
},
|
|
1358
|
+
onKeyDown: (e) => {
|
|
1359
|
+
if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
|
|
1360
|
+
e.preventDefault();
|
|
1361
|
+
const step = (0, import_parameters.parameterStep)(field2) * (e.shiftKey ? 10 : 1);
|
|
1362
|
+
onChange((0, import_parameters.clampParameterNumber)(field2, value + (e.key === "ArrowUp" ? step : -step)));
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
),
|
|
1366
|
+
field2.unit && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-[10px] text-muted-foreground", children: field2.unit })
|
|
1367
|
+
] });
|
|
1368
|
+
}
|
|
1369
|
+
function ParameterSlider(props) {
|
|
1370
|
+
const { field: field2, value, onChange, onGestureStart, onGestureEnd, disabled } = props;
|
|
1371
|
+
const dragging = (0, import_react11.useRef)(false);
|
|
1372
|
+
const end = () => {
|
|
1373
|
+
if (!dragging.current) return;
|
|
1374
|
+
dragging.current = false;
|
|
1375
|
+
onGestureEnd?.();
|
|
1376
|
+
};
|
|
1377
|
+
if (!(0, import_parameters.isSliderField)(field2)) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ParameterNumberInput, { ...props });
|
|
1378
|
+
const step = (0, import_parameters.parameterStep)(field2);
|
|
1379
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1380
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1381
|
+
"input",
|
|
1382
|
+
{
|
|
1383
|
+
type: "range",
|
|
1384
|
+
"aria-label": (0, import_parameters.parameterLabel)(field2),
|
|
1385
|
+
"aria-valuetext": (0, import_parameters.formatParameterNumber)(field2, value),
|
|
1386
|
+
min: field2.min,
|
|
1387
|
+
max: field2.max,
|
|
1388
|
+
step,
|
|
1389
|
+
value,
|
|
1390
|
+
disabled: disabled || field2.readOnly,
|
|
1391
|
+
className: rangeClass,
|
|
1392
|
+
onPointerDown: () => {
|
|
1393
|
+
if (dragging.current) return;
|
|
1394
|
+
dragging.current = true;
|
|
1395
|
+
onGestureStart?.();
|
|
1396
|
+
},
|
|
1397
|
+
onPointerUp: end,
|
|
1398
|
+
onPointerCancel: end,
|
|
1399
|
+
onBlur: end,
|
|
1400
|
+
onChange: (e) => onChange((0, import_parameters.clampParameterNumber)(field2, Number(e.target.value)))
|
|
1401
|
+
}
|
|
1402
|
+
),
|
|
1403
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "w-20 shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ParameterNumberInput, { ...props, id: props.id, "aria-label": `${(0, import_parameters.parameterLabel)(field2)} value` }) })
|
|
1404
|
+
] });
|
|
1405
|
+
}
|
|
1406
|
+
function ParameterToggle({ field: field2, value, onChange, disabled, id }) {
|
|
1407
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1408
|
+
"button",
|
|
1409
|
+
{
|
|
1410
|
+
type: "button",
|
|
1411
|
+
role: "switch",
|
|
1412
|
+
id,
|
|
1413
|
+
"aria-checked": value,
|
|
1414
|
+
"aria-label": (0, import_parameters.parameterLabel)(field2),
|
|
1415
|
+
disabled: disabled || field2.readOnly,
|
|
1416
|
+
onClick: () => onChange(!value),
|
|
1417
|
+
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"}`,
|
|
1418
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1419
|
+
"span",
|
|
1420
|
+
{
|
|
1421
|
+
className: `pointer-events-none block h-4 w-4 rounded-full bg-background shadow transition-transform ${value ? "translate-x-4" : "translate-x-0.5"}`
|
|
1422
|
+
}
|
|
1423
|
+
)
|
|
1424
|
+
}
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
function ParameterSelect({ field: field2, value, onChange, disabled, id }) {
|
|
1428
|
+
const options = (0, import_parameters.getChoiceOptions)(field2);
|
|
1429
|
+
const off = disabled || field2.readOnly;
|
|
1430
|
+
if (field2.control === "segmented") {
|
|
1431
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { role: "radiogroup", "aria-label": (0, import_parameters.parameterLabel)(field2), className: "inline-flex flex-wrap rounded-md border border-input bg-muted p-0.5", children: options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1432
|
+
"button",
|
|
1433
|
+
{
|
|
1434
|
+
type: "button",
|
|
1435
|
+
role: "radio",
|
|
1436
|
+
"aria-checked": o.value === value,
|
|
1437
|
+
title: o.description,
|
|
1438
|
+
disabled: off,
|
|
1439
|
+
onClick: () => onChange(o.value),
|
|
1440
|
+
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"}`,
|
|
1441
|
+
children: (0, import_parameters.choiceOptionLabel)(o)
|
|
1442
|
+
},
|
|
1443
|
+
String(o.value)
|
|
1444
|
+
)) });
|
|
1445
|
+
}
|
|
1446
|
+
const index = options.findIndex((o) => o.value === value);
|
|
1447
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1448
|
+
"select",
|
|
1449
|
+
{
|
|
1450
|
+
id,
|
|
1451
|
+
"aria-label": (0, import_parameters.parameterLabel)(field2),
|
|
1452
|
+
value: String(index),
|
|
1453
|
+
disabled: off,
|
|
1454
|
+
onChange: (e) => {
|
|
1455
|
+
const option = options[Number(e.target.value)];
|
|
1456
|
+
if (option) onChange(option.value);
|
|
1457
|
+
},
|
|
1458
|
+
className: parameterInputClass,
|
|
1459
|
+
children: options.map((o, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: String(i), title: o.description, children: (0, import_parameters.choiceOptionLabel)(o) }, String(o.value)))
|
|
1460
|
+
}
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
function ParameterColor({ field: field2, value, onChange, onGestureStart, onGestureEnd, disabled, id }) {
|
|
1464
|
+
const picking = (0, import_react11.useRef)(false);
|
|
1465
|
+
const alpha = !!field2.alpha;
|
|
1466
|
+
const hex = (0, import_parameters.normalizeHexColor)(value, true) ?? "#000000ff";
|
|
1467
|
+
const rgb = hex.slice(0, 7);
|
|
1468
|
+
const a = hex.slice(7);
|
|
1469
|
+
const off = disabled || field2.readOnly;
|
|
1470
|
+
const start = () => {
|
|
1471
|
+
if (picking.current) return;
|
|
1472
|
+
picking.current = true;
|
|
1473
|
+
onGestureStart?.();
|
|
1474
|
+
};
|
|
1475
|
+
const end = () => {
|
|
1476
|
+
if (!picking.current) return;
|
|
1477
|
+
picking.current = false;
|
|
1478
|
+
onGestureEnd?.();
|
|
1479
|
+
};
|
|
1480
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-1.5", children: [
|
|
1481
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1482
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.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: [
|
|
1483
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "absolute inset-0", style: { background: alpha ? hex : rgb } }),
|
|
1484
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1485
|
+
"input",
|
|
1486
|
+
{
|
|
1487
|
+
type: "color",
|
|
1488
|
+
"aria-label": (0, import_parameters.parameterLabel)(field2),
|
|
1489
|
+
value: rgb,
|
|
1490
|
+
disabled: off,
|
|
1491
|
+
className: "absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed",
|
|
1492
|
+
onChange: (e) => {
|
|
1493
|
+
start();
|
|
1494
|
+
onChange(alpha ? `${e.target.value}${a}` : e.target.value);
|
|
1495
|
+
},
|
|
1496
|
+
onBlur: end
|
|
1497
|
+
}
|
|
1498
|
+
)
|
|
1499
|
+
] }),
|
|
1500
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1501
|
+
ParameterCommitInput,
|
|
1502
|
+
{
|
|
1503
|
+
id,
|
|
1504
|
+
"aria-label": `${(0, import_parameters.parameterLabel)(field2)} hex`,
|
|
1505
|
+
value: alpha ? hex : rgb,
|
|
1506
|
+
disabled: off,
|
|
1507
|
+
spellCheck: false,
|
|
1508
|
+
className: `${parameterInputClass} font-mono`,
|
|
1509
|
+
onCommit: (text) => {
|
|
1510
|
+
const next = (0, import_parameters.normalizeHexColor)(text, alpha);
|
|
1511
|
+
if (next) onChange(next);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
)
|
|
1515
|
+
] }),
|
|
1516
|
+
alpha && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1517
|
+
"input",
|
|
1518
|
+
{
|
|
1519
|
+
type: "range",
|
|
1520
|
+
"aria-label": `${(0, import_parameters.parameterLabel)(field2)} opacity`,
|
|
1521
|
+
min: 0,
|
|
1522
|
+
max: 255,
|
|
1523
|
+
step: 1,
|
|
1524
|
+
value: parseInt(a, 16),
|
|
1525
|
+
disabled: off,
|
|
1526
|
+
className: rangeClass,
|
|
1527
|
+
onPointerDown: start,
|
|
1528
|
+
onPointerUp: end,
|
|
1529
|
+
onPointerCancel: end,
|
|
1530
|
+
onBlur: end,
|
|
1531
|
+
onChange: (e) => onChange(`${rgb}${Number(e.target.value).toString(16).padStart(2, "0")}`)
|
|
1532
|
+
}
|
|
1533
|
+
),
|
|
1534
|
+
field2.swatches && field2.swatches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap gap-1", children: field2.swatches.map((s) => {
|
|
1535
|
+
const swatch = (0, import_parameters.normalizeHexColor)(s, alpha);
|
|
1536
|
+
if (!swatch) return null;
|
|
1537
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1538
|
+
"button",
|
|
1539
|
+
{
|
|
1540
|
+
type: "button",
|
|
1541
|
+
title: swatch,
|
|
1542
|
+
"aria-label": `${(0, import_parameters.parameterLabel)(field2)} ${swatch}`,
|
|
1543
|
+
"aria-pressed": swatch === (alpha ? hex : rgb),
|
|
1544
|
+
disabled: off,
|
|
1545
|
+
onClick: () => onChange(swatch),
|
|
1546
|
+
className: "h-5 w-5 rounded border border-input aria-pressed:ring-1 aria-pressed:ring-primary",
|
|
1547
|
+
style: { background: swatch }
|
|
1548
|
+
},
|
|
1549
|
+
s
|
|
1550
|
+
);
|
|
1551
|
+
}) })
|
|
1552
|
+
] });
|
|
1553
|
+
}
|
|
1554
|
+
function ParameterVector({ field: field2, value, onChange, disabled, id }) {
|
|
1555
|
+
const size = (0, import_parameters.vectorSize)(field2);
|
|
1556
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "grid gap-1.5", style: { gridTemplateColumns: `repeat(${size}, minmax(0, 1fr))` }, children: Array.from({ length: size }, (_, i) => {
|
|
1557
|
+
const component = (0, import_parameters.vectorComponentField)(field2, i);
|
|
1558
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "flex min-w-0 items-center gap-1", children: [
|
|
1559
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "w-3 shrink-0 text-[10px] uppercase text-muted-foreground", children: component.label }),
|
|
1560
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1561
|
+
ParameterNumberInput,
|
|
1562
|
+
{
|
|
1563
|
+
field: component,
|
|
1564
|
+
value: value[i] ?? 0,
|
|
1565
|
+
id: i === 0 ? id : void 0,
|
|
1566
|
+
"aria-label": `${(0, import_parameters.parameterLabel)(field2)} ${component.label}`,
|
|
1567
|
+
disabled: disabled || field2.readOnly,
|
|
1568
|
+
onChange: (n) => onChange(Array.from({ length: size }, (_2, j) => j === i ? n : value[j] ?? 0))
|
|
1569
|
+
}
|
|
1570
|
+
)
|
|
1571
|
+
] }, i);
|
|
1572
|
+
}) });
|
|
1573
|
+
}
|
|
1574
|
+
function ParameterImage({ field: field2, value, onChange, disabled, id, resolveFile }) {
|
|
1575
|
+
const off = disabled || field2.readOnly;
|
|
1576
|
+
const name = (0, import_parameters.parameterLabel)(field2);
|
|
1577
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-1.5", children: [
|
|
1578
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1579
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.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_runtime10.jsx)("img", { src: value, alt: name, className: "h-full w-full object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "None" }) }),
|
|
1580
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
1581
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1582
|
+
ParameterCommitInput,
|
|
1583
|
+
{
|
|
1584
|
+
id,
|
|
1585
|
+
"aria-label": `${name} URL`,
|
|
1586
|
+
placeholder: "Image URL",
|
|
1587
|
+
value: value.startsWith("data:") ? "" : value,
|
|
1588
|
+
disabled: off,
|
|
1589
|
+
onCommit: (text) => onChange(text.trim())
|
|
1590
|
+
}
|
|
1591
|
+
),
|
|
1592
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-1", children: [
|
|
1593
|
+
resolveFile && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: `${parameterButtonClass} cursor-pointer ${off ? "pointer-events-none opacity-50" : ""}`, children: [
|
|
1594
|
+
"Choose file\u2026",
|
|
1595
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1596
|
+
"input",
|
|
1597
|
+
{
|
|
1598
|
+
type: "file",
|
|
1599
|
+
className: "sr-only",
|
|
1600
|
+
accept: field2.accept ?? "image/*",
|
|
1601
|
+
disabled: off,
|
|
1602
|
+
"aria-label": `${name} file`,
|
|
1603
|
+
onChange: async (e) => {
|
|
1604
|
+
const file = e.target.files?.[0];
|
|
1605
|
+
e.target.value = "";
|
|
1606
|
+
if (file) onChange(await resolveFile(file));
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
)
|
|
1610
|
+
] }),
|
|
1611
|
+
value && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: parameterButtonClass, disabled: off, onClick: () => onChange(""), "aria-label": `Clear ${name}`, children: "Clear" })
|
|
1612
|
+
] })
|
|
1613
|
+
] })
|
|
1614
|
+
] }),
|
|
1615
|
+
field2.options && field2.options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap gap-1", children: field2.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1616
|
+
"button",
|
|
1617
|
+
{
|
|
1618
|
+
type: "button",
|
|
1619
|
+
title: o.label ?? o.url,
|
|
1620
|
+
"aria-label": o.label ?? o.url,
|
|
1621
|
+
"aria-pressed": o.url === value,
|
|
1622
|
+
disabled: off,
|
|
1623
|
+
onClick: () => onChange(o.url),
|
|
1624
|
+
className: "h-9 w-12 overflow-hidden rounded border border-input aria-pressed:ring-2 aria-pressed:ring-primary",
|
|
1625
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("img", { src: o.url, alt: "", className: "h-full w-full object-cover" })
|
|
1626
|
+
},
|
|
1627
|
+
o.url
|
|
1628
|
+
)) })
|
|
1629
|
+
] });
|
|
1630
|
+
}
|
|
1631
|
+
function ParameterText({ field: field2, value, onChange, disabled, id }) {
|
|
1632
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1633
|
+
ParameterCommitInput,
|
|
1634
|
+
{
|
|
1635
|
+
id,
|
|
1636
|
+
"aria-label": (0, import_parameters.parameterLabel)(field2),
|
|
1637
|
+
multiline: field2.multiline,
|
|
1638
|
+
placeholder: field2.placeholder,
|
|
1639
|
+
maxLength: field2.maxLength,
|
|
1640
|
+
value,
|
|
1641
|
+
disabled: disabled || field2.readOnly,
|
|
1642
|
+
onCommit: onChange
|
|
1643
|
+
}
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
function ParameterFieldControl({ field: field2, value, onChange, resolveFile, ...rest }) {
|
|
1647
|
+
const set = onChange;
|
|
1648
|
+
switch (field2.type) {
|
|
1649
|
+
case "number":
|
|
1650
|
+
case "int":
|
|
1651
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ParameterSlider, { field: field2, value, onChange: set, ...rest });
|
|
1652
|
+
case "boolean":
|
|
1653
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ParameterToggle, { field: field2, value, onChange: set, ...rest });
|
|
1654
|
+
case "choice":
|
|
1655
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ParameterSelect, { field: field2, value, onChange: set, ...rest });
|
|
1656
|
+
case "color":
|
|
1657
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ParameterColor, { field: field2, value, onChange: set, ...rest });
|
|
1658
|
+
case "vector":
|
|
1659
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ParameterVector, { field: field2, value, onChange: set, ...rest });
|
|
1660
|
+
case "image":
|
|
1661
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ParameterImage, { field: field2, value, onChange: set, resolveFile, ...rest });
|
|
1662
|
+
case "text":
|
|
1663
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ParameterText, { field: field2, value, onChange: set, ...rest });
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// src/effects/EffectParameters.tsx
|
|
1668
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1669
|
+
function EffectParameters({ description, values, onChange, onReset, keyframed, onPreset, disabled = false, className = "" }) {
|
|
1670
|
+
const fields = (0, import_react12.useMemo)(() => (0, import_effects3.effectParameterFields)(description), [description]);
|
|
1671
|
+
const idBase = (0, import_react12.useId)();
|
|
1672
|
+
const gesture = import_react12.default.useRef(null);
|
|
1673
|
+
const count = import_react12.default.useRef(0);
|
|
1674
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `space-y-2 ${className}`, "data-effect-parameters": description.id, children: [
|
|
1675
|
+
onPreset && description.presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1676
|
+
"select",
|
|
1677
|
+
{
|
|
1678
|
+
className: "w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs",
|
|
1679
|
+
"aria-label": `${description.name} presets`,
|
|
1680
|
+
value: "",
|
|
1681
|
+
disabled,
|
|
1682
|
+
onChange: (e) => {
|
|
1683
|
+
const preset = description.presets.find((p) => p.name === e.currentTarget.value);
|
|
1684
|
+
if (preset) onPreset({ ...description.defaults, ...preset.values });
|
|
1685
|
+
},
|
|
1686
|
+
children: [
|
|
1687
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "", children: "Presets\u2026" }),
|
|
1688
|
+
description.presets.map((p) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: p.name, children: p.name }, p.name))
|
|
1689
|
+
]
|
|
1690
|
+
}
|
|
1691
|
+
),
|
|
1692
|
+
fields.map((field2) => {
|
|
1693
|
+
const label = (0, import_parameters2.parameterLabel)(field2);
|
|
1694
|
+
const inputId = `${idBase}-${field2.id}`;
|
|
1695
|
+
const keyed = keyframed?.has(field2.id) ?? false;
|
|
1696
|
+
const value = (0, import_effects3.effectFieldValue)(field2, values[field2.id]);
|
|
1697
|
+
const changed = !keyed && !(0, import_parameters2.parameterValuesEqual)(value, field2.default);
|
|
1698
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-1", "data-effect-control": field2.id, children: [
|
|
1699
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
1700
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("label", { htmlFor: inputId, className: "min-w-0 flex-1 truncate text-xs font-medium", title: field2.description, children: label }),
|
|
1701
|
+
keyed && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "keyframed" }),
|
|
1702
|
+
onReset && changed && !disabled && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "rounded px-1 text-[11px] text-muted-foreground hover:text-foreground", "aria-label": `Reset ${label}`, onClick: () => onReset(field2.id), children: "\u21BA" })
|
|
1703
|
+
] }),
|
|
1704
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1705
|
+
ParameterFieldControl,
|
|
1706
|
+
{
|
|
1707
|
+
field: field2,
|
|
1708
|
+
value,
|
|
1709
|
+
id: inputId,
|
|
1710
|
+
disabled: disabled || keyed,
|
|
1711
|
+
onChange: (v) => onChange(field2.id, v, gesture.current),
|
|
1712
|
+
onGestureStart: () => gesture.current = `${idBase}:${field2.id}:${++count.current}`,
|
|
1713
|
+
onGestureEnd: () => gesture.current = null
|
|
1714
|
+
}
|
|
1715
|
+
)
|
|
1716
|
+
] }, field2.id);
|
|
1717
|
+
}),
|
|
1718
|
+
!fields.length && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No controls." })
|
|
1719
|
+
] });
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
// src/video-editor/VideoEffectsPanel.tsx
|
|
1723
|
+
var import_react13 = require("react");
|
|
1724
|
+
var import_video_editor9 = require("@pixerate/editor/video-editor");
|
|
1725
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1726
|
+
var btn5 = "rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40";
|
|
1727
|
+
var keyedOf = (values) => new Set(Object.entries(values ?? {}).filter(([, v]) => (0, import_video_editor9.isVideoKeyframes)(v)).map(([k]) => k));
|
|
1728
|
+
function EffectControls({
|
|
1729
|
+
provider,
|
|
1730
|
+
effect,
|
|
1731
|
+
name,
|
|
1732
|
+
values,
|
|
1733
|
+
disabled,
|
|
1734
|
+
onChange,
|
|
1735
|
+
onPreset
|
|
1736
|
+
}) {
|
|
1737
|
+
const { description, error } = useEffectDescription(provider, effect);
|
|
1738
|
+
if (!provider) return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-[11px] text-muted-foreground", children: [
|
|
1739
|
+
'No effects provider: "',
|
|
1740
|
+
name ?? effect,
|
|
1741
|
+
'" is kept and shown as a placeholder.'
|
|
1742
|
+
] });
|
|
1743
|
+
if (error) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-[11px] text-amber-500", children: error });
|
|
1744
|
+
if (!description) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "Loading\u2026" });
|
|
1745
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EffectParameters, { description, values: values ?? {}, keyframed: keyedOf(values), disabled, onChange, onReset: (k) => onChange(k, description.defaults[k], null), onPreset });
|
|
1746
|
+
}
|
|
1747
|
+
function VideoEffectsPanel({ controller, className = "" }) {
|
|
1748
|
+
const state = useVideoEditorState(controller);
|
|
1749
|
+
const provider = state.effectsProvider;
|
|
1750
|
+
const [browse, setBrowse] = (0, import_react13.useState)(null);
|
|
1751
|
+
const [message, setMessage] = (0, import_react13.useState)(null);
|
|
1752
|
+
const [open, setOpen] = (0, import_react13.useState)(null);
|
|
1753
|
+
const selected = state.selection[0];
|
|
1754
|
+
const at = selected ? (0, import_video_editor9.findVideoClip)(state.project, selected) : null;
|
|
1755
|
+
const clip = at?.clip ?? null;
|
|
1756
|
+
const disabled = state.readOnly;
|
|
1757
|
+
const report = (r) => void Promise.resolve(r).then((x) => setMessage(x.ok ? null : x.error));
|
|
1758
|
+
const addEffectClip = (effect) => {
|
|
1759
|
+
const track = at && at.track.kind === "video" ? at.track.id : state.project.tracks.find((t) => t.kind === "video")?.id;
|
|
1760
|
+
if (!track) return setMessage("Add a video track first.");
|
|
1761
|
+
report(controller.addEffectClip(track, effect, { start: Math.round(state.time), mode: "overwrite" }).then((r) => (r.ok && r.clip && controller.select([r.clip]), r)));
|
|
1762
|
+
};
|
|
1763
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("section", { className: `flex flex-col gap-2 rounded-md border border-border p-2 text-xs ${className}`, "aria-label": "Effects", "data-video-effects": "", children: [
|
|
1764
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
1765
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h3", { className: "flex-1 font-semibold", children: "Effects" }),
|
|
1766
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: btn5, disabled: disabled || !provider, onClick: () => setBrowse(browse === "generator" ? null : "generator"), "aria-pressed": browse === "generator", children: "Add effect clip" })
|
|
1767
|
+
] }),
|
|
1768
|
+
!provider && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider is registered. Effects in the project are kept and drawn as placeholders." }),
|
|
1769
|
+
browse === "generator" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EffectsBrowser, { provider, uses: ["generator", "title", "overlay"], title: "Generators and titles", onPick: (e) => (setBrowse(null), addEffectClip(e.id)) }),
|
|
1770
|
+
!clip && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "Select a clip to see its effects." }),
|
|
1771
|
+
clip && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
1772
|
+
clip.type === "effect" && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-1", "data-effect-part": "generator", children: [
|
|
1773
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "font-medium", children: clip.name ?? clip.effect }),
|
|
1774
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1775
|
+
EffectControls,
|
|
1776
|
+
{
|
|
1777
|
+
provider,
|
|
1778
|
+
effect: clip.effect,
|
|
1779
|
+
name: clip.name,
|
|
1780
|
+
values: clip.values,
|
|
1781
|
+
disabled,
|
|
1782
|
+
onChange: (k, v, gesture) => report(controller.updateEffectValues(clip.id, "generator", { [k]: v }, { merge: gesture ?? void 0 })),
|
|
1783
|
+
onPreset: (values) => report(controller.updateEffectValues(clip.id, "generator", values))
|
|
1784
|
+
}
|
|
1785
|
+
)
|
|
1786
|
+
] }),
|
|
1787
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-1", "data-effect-part": "transition", children: [
|
|
1788
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
1789
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "flex-1 font-medium", children: "Transition in" }),
|
|
1790
|
+
clip.transition?.type === "effect" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: btn5, disabled, onClick: () => report(controller.apply({ op: "set_transition", clip: clip.id, transition: null }, "Remove transition")), children: "Remove" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: btn5, disabled: disabled || !provider, "aria-pressed": browse === "transition", onClick: () => setBrowse(browse === "transition" ? null : "transition"), children: "Effect transition\u2026" })
|
|
1791
|
+
] }),
|
|
1792
|
+
browse === "transition" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EffectsBrowser, { provider, uses: ["transition"], title: "Transitions", onPick: (e) => (setBrowse(null), report(controller.setEffectTransition(clip.id, e.id))) }),
|
|
1793
|
+
clip.transition?.type === "effect" && clip.transition.effect && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1794
|
+
EffectControls,
|
|
1795
|
+
{
|
|
1796
|
+
provider,
|
|
1797
|
+
effect: clip.transition.effect.effect,
|
|
1798
|
+
name: clip.transition.effect.name,
|
|
1799
|
+
values: clip.transition.effect.values,
|
|
1800
|
+
disabled,
|
|
1801
|
+
onChange: (k, v, gesture) => report(controller.updateEffectValues(clip.id, "transition", { [k]: v }, { merge: gesture ?? void 0 })),
|
|
1802
|
+
onPreset: (values) => report(controller.updateEffectValues(clip.id, "transition", values))
|
|
1803
|
+
}
|
|
1804
|
+
)
|
|
1805
|
+
] }),
|
|
1806
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-1", "data-effect-part": "stack", children: [
|
|
1807
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
1808
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "flex-1 font-medium", children: "On this clip" }),
|
|
1809
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: btn5, disabled: disabled || !provider, "aria-pressed": browse === "clip", onClick: () => setBrowse(browse === "clip" ? null : "clip"), children: "Add effect\u2026" })
|
|
1810
|
+
] }),
|
|
1811
|
+
browse === "clip" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EffectsBrowser, { provider, uses: ["filter", "overlay"], title: "Filters and overlays", onPick: (e) => (setBrowse(null), report(controller.applyClipEffect(clip.id, e.id))) }),
|
|
1812
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ol", { className: "space-y-1", children: (clip.effects ?? []).map((e, i, all) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { className: "rounded border border-border p-1", "data-clip-effect": e.id, children: [
|
|
1813
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
1814
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("input", { type: "checkbox", checked: e.enabled !== false, disabled, "aria-label": `${e.name ?? e.effect} on`, onChange: (ev) => report(controller.updateClipEffect(clip.id, e.id, { enabled: ev.currentTarget.checked })) }),
|
|
1815
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "min-w-0 flex-1 truncate text-left", "aria-expanded": open === e.id, onClick: () => setOpen(open === e.id ? null : e.id), children: e.name ?? e.effect }),
|
|
1816
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: btn5, disabled: disabled || i === 0, "aria-label": "Move up", onClick: () => report(controller.updateClipEffect(clip.id, e.id, { index: i - 1 })), children: "\u2191" }),
|
|
1817
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: btn5, disabled: disabled || i === all.length - 1, "aria-label": "Move down", onClick: () => report(controller.updateClipEffect(clip.id, e.id, { index: i + 1 })), children: "\u2193" }),
|
|
1818
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: btn5, disabled, "aria-label": `Remove ${e.name ?? e.effect}`, onClick: () => report(controller.removeClipEffect(clip.id, e.id)), children: "\u2715" })
|
|
1819
|
+
] }),
|
|
1820
|
+
open === e.id && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mt-1 space-y-2", children: [
|
|
1821
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex items-center gap-1", children: [
|
|
1822
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-14 text-[11px]", children: "Opacity" }),
|
|
1823
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1824
|
+
"input",
|
|
1825
|
+
{
|
|
1826
|
+
type: "range",
|
|
1827
|
+
min: 0,
|
|
1828
|
+
max: 1,
|
|
1829
|
+
step: 0.01,
|
|
1830
|
+
className: "flex-1",
|
|
1831
|
+
disabled: disabled || (0, import_video_editor9.isVideoKeyframes)(e.opacity),
|
|
1832
|
+
value: typeof e.opacity === "number" ? e.opacity : 1,
|
|
1833
|
+
"aria-label": `${e.name ?? e.effect} opacity`,
|
|
1834
|
+
onChange: (ev) => report(controller.updateClipEffect(clip.id, e.id, { opacity: Number(ev.currentTarget.value) }, { merge: `opacity:${clip.id}:${e.id}` }))
|
|
1835
|
+
}
|
|
1836
|
+
)
|
|
1837
|
+
] }),
|
|
1838
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1839
|
+
EffectControls,
|
|
1840
|
+
{
|
|
1841
|
+
provider,
|
|
1842
|
+
effect: e.effect,
|
|
1843
|
+
name: e.name,
|
|
1844
|
+
values: e.values,
|
|
1845
|
+
disabled,
|
|
1846
|
+
onChange: (k, v, gesture) => report(controller.updateClipEffect(clip.id, e.id, { values: { [k]: v } }, { merge: gesture ?? void 0 })),
|
|
1847
|
+
onPreset: (values) => report(controller.updateClipEffect(clip.id, e.id, { values }))
|
|
1848
|
+
}
|
|
1849
|
+
)
|
|
1850
|
+
] })
|
|
1851
|
+
] }, e.id)) }),
|
|
1852
|
+
!clip.effects?.length && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects on this clip." })
|
|
1853
|
+
] })
|
|
1854
|
+
] }),
|
|
1855
|
+
message && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
|
|
1856
|
+
] });
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
// src/video-editor/VideoEditor.tsx
|
|
1860
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1861
|
+
function VideoEditor({ controller: given, options, editable = true, pxPerSecond, onPxPerSecondChange, resolution, showIssues = true, showTranscript = true, showScopes = false, effects, showEffects, className = "" }) {
|
|
1154
1862
|
const { controller, state } = useVideoEditor(given ?? options ?? {});
|
|
1155
|
-
(
|
|
1863
|
+
useEffects(controller, effects);
|
|
1864
|
+
const effectsShown = showEffects ?? !!(state.effectsProvider || state.project.tracks.some((t) => t.clips.some((c) => c.type === "effect" || c.effects?.length || c.transition?.type === "effect")));
|
|
1865
|
+
(0, import_react14.useEffect)(() => {
|
|
1156
1866
|
if (editable) return;
|
|
1157
1867
|
const was = controller.getState().readOnly;
|
|
1158
1868
|
controller.setReadOnly(true);
|
|
1159
1869
|
return () => controller.setReadOnly(was);
|
|
1160
1870
|
}, [controller, editable]);
|
|
1161
1871
|
const { issues } = state;
|
|
1162
|
-
const issueList = showIssues && issues.length > 0 && /* @__PURE__ */ (0,
|
|
1163
|
-
return /* @__PURE__ */ (0,
|
|
1164
|
-
/* @__PURE__ */ (0,
|
|
1165
|
-
editable && /* @__PURE__ */ (0,
|
|
1166
|
-
/* @__PURE__ */ (0,
|
|
1167
|
-
/* @__PURE__ */ (0,
|
|
1168
|
-
/* @__PURE__ */ (0,
|
|
1169
|
-
showScopes && /* @__PURE__ */ (0,
|
|
1872
|
+
const issueList = showIssues && issues.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("ul", { className: "space-y-0.5 text-xs", "aria-label": "Video issues", "data-video-issues": "", children: issues.map((issue, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("li", { className: issue.severity === "error" ? "text-red-500" : issue.severity === "warning" ? "text-amber-500" : "text-muted-foreground", "data-issue": issue.code, children: issue.message }, i)) });
|
|
1873
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `flex flex-col gap-3 ${className}`, "data-video-editor": "", children: [
|
|
1874
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `grid gap-3 ${editable ? "lg:grid-cols-[16rem_minmax(0,1fr)_18rem]" : ""}`, children: [
|
|
1875
|
+
editable && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MediaBin, { controller, className: "min-w-0" }),
|
|
1876
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex min-w-0 flex-col gap-2", children: [
|
|
1877
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VideoPreview, { controller, resolution, className: "rounded-md border border-border" }),
|
|
1878
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VideoTransport, { controller, editable }),
|
|
1879
|
+
showScopes && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VideoScopes, { controller })
|
|
1170
1880
|
] }),
|
|
1171
|
-
editable && /* @__PURE__ */ (0,
|
|
1881
|
+
editable && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex min-w-0 flex-col gap-3", children: [
|
|
1882
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ClipInspector, { controller, className: "min-w-0" }),
|
|
1883
|
+
effectsShown && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VideoEffectsPanel, { controller, className: "min-w-0" })
|
|
1884
|
+
] })
|
|
1172
1885
|
] }),
|
|
1173
|
-
/* @__PURE__ */ (0,
|
|
1174
|
-
showTranscript && /* @__PURE__ */ (0,
|
|
1886
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VideoTimeline, { controller, editable, pxPerSecond, onPxPerSecondChange }),
|
|
1887
|
+
showTranscript && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TranscriptPanel, { controller, editable }),
|
|
1175
1888
|
issueList
|
|
1176
1889
|
] });
|
|
1177
1890
|
}
|
|
@@ -1181,6 +1894,7 @@ function VideoEditor({ controller: given, options, editable = true, pxPerSecond,
|
|
|
1181
1894
|
MediaBin,
|
|
1182
1895
|
TranscriptPanel,
|
|
1183
1896
|
VideoEditor,
|
|
1897
|
+
VideoEffectsPanel,
|
|
1184
1898
|
VideoPreview,
|
|
1185
1899
|
VideoScopes,
|
|
1186
1900
|
VideoTimeline,
|
|
@@ -1188,6 +1902,7 @@ function VideoEditor({ controller: given, options, editable = true, pxPerSecond,
|
|
|
1188
1902
|
useVideoEditor,
|
|
1189
1903
|
useVideoEditorController,
|
|
1190
1904
|
useVideoEditorState,
|
|
1191
|
-
useVideoEditorTools
|
|
1905
|
+
useVideoEditorTools,
|
|
1906
|
+
useVideoEffectsTools
|
|
1192
1907
|
});
|
|
1193
1908
|
//# sourceMappingURL=index.cjs.map
|