@kimjansheden/payload-video-processor 0.1.1
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/README.md +164 -0
- package/dist/admin/VideoField.cjs +737 -0
- package/dist/admin/VideoField.cjs.map +1 -0
- package/dist/admin/VideoField.d.cts +44 -0
- package/dist/admin/VideoField.d.ts +44 -0
- package/dist/admin/VideoField.js +731 -0
- package/dist/admin/VideoField.js.map +1 -0
- package/dist/admin/client.d.cts +4 -0
- package/dist/admin/client.d.ts +4 -0
- package/dist/cli/start-worker.cjs +691 -0
- package/dist/cli/start-worker.cjs.map +1 -0
- package/dist/cli/start-worker.js +678 -0
- package/dist/cli/start-worker.js.map +1 -0
- package/dist/exports/client.cjs +737 -0
- package/dist/exports/client.cjs.map +1 -0
- package/dist/exports/client.js +731 -0
- package/dist/exports/client.js.map +1 -0
- package/dist/index.cjs +1141 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +1125 -0
- package/dist/index.js.map +1 -0
- package/dist/queue/worker.cjs +483 -0
- package/dist/queue/worker.cjs.map +1 -0
- package/dist/queue/worker.d.cts +2 -0
- package/dist/queue/worker.d.ts +2 -0
- package/dist/queue/worker.js +472 -0
- package/dist/queue/worker.js.map +1 -0
- package/dist/styles-GMHOOV63.css +8 -0
- package/dist/types-BjFcE25o.d.cts +78 -0
- package/dist/types-BjFcE25o.d.ts +78 -0
- package/package.json +91 -0
- package/types/index.d.ts +6 -0
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var ui = require('@payloadcms/ui');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
11
|
+
|
|
12
|
+
if (typeof document !== "undefined") {
|
|
13
|
+
void import('../styles-GMHOOV63.css');
|
|
14
|
+
}
|
|
15
|
+
var isVideoVariantFieldConfig = (value) => {
|
|
16
|
+
if (!value || typeof value !== "object") return false;
|
|
17
|
+
const candidate = value;
|
|
18
|
+
return typeof candidate.enqueuePath === "string" && typeof candidate.statusPath === "string" && typeof candidate.replaceOriginalPath === "string" && typeof candidate.removeVariantPath === "string" && typeof candidate.collectionSlug === "string" && typeof candidate.queueName === "string" && Boolean(candidate.presets) && typeof candidate.presets === "object";
|
|
19
|
+
};
|
|
20
|
+
var DEFAULT_CROP = {
|
|
21
|
+
x: 0,
|
|
22
|
+
y: 0,
|
|
23
|
+
width: 1,
|
|
24
|
+
height: 1
|
|
25
|
+
};
|
|
26
|
+
var resolveApiBase = (enqueuePath) => enqueuePath.replace(/\/video-queue\/enqueue$/, "");
|
|
27
|
+
var formatProgress = (value) => {
|
|
28
|
+
if (typeof value !== "number") return "0%";
|
|
29
|
+
return `${Math.round(value)}%`;
|
|
30
|
+
};
|
|
31
|
+
var readVariantPreset = (variant) => {
|
|
32
|
+
if (!variant || typeof variant !== "object") return void 0;
|
|
33
|
+
if (typeof variant.preset === "string" && variant.preset.trim().length > 0) {
|
|
34
|
+
return variant.preset.trim();
|
|
35
|
+
}
|
|
36
|
+
return void 0;
|
|
37
|
+
};
|
|
38
|
+
var readVariantId = (variant) => {
|
|
39
|
+
const rawId = variant?.id;
|
|
40
|
+
if (typeof rawId === "string" && rawId.trim().length > 0) {
|
|
41
|
+
return rawId.trim();
|
|
42
|
+
}
|
|
43
|
+
return void 0;
|
|
44
|
+
};
|
|
45
|
+
var resolveVariantIdentifier = (variant, fallback) => {
|
|
46
|
+
return readVariantId(variant) ?? readVariantPreset(variant) ?? fallback ?? "variant";
|
|
47
|
+
};
|
|
48
|
+
var formatBytes = (value) => {
|
|
49
|
+
if (!value || Number.isNaN(value)) return "-";
|
|
50
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
51
|
+
let size = value;
|
|
52
|
+
let unitIndex = 0;
|
|
53
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
54
|
+
size /= 1024;
|
|
55
|
+
unitIndex += 1;
|
|
56
|
+
}
|
|
57
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
58
|
+
};
|
|
59
|
+
var formatSeconds = (value) => {
|
|
60
|
+
if (!value || Number.isNaN(value)) return "-";
|
|
61
|
+
if (value < 60) {
|
|
62
|
+
return `${value.toFixed(1)} s`;
|
|
63
|
+
}
|
|
64
|
+
const minutes = Math.floor(value / 60);
|
|
65
|
+
const seconds = Math.round(value % 60);
|
|
66
|
+
return `${minutes} min ${seconds}s`;
|
|
67
|
+
};
|
|
68
|
+
var VideoField = (props) => {
|
|
69
|
+
const { useEffect, useMemo, useState, useCallback } = React__default.default;
|
|
70
|
+
const { field } = props;
|
|
71
|
+
const { id } = ui.useDocumentInfo();
|
|
72
|
+
const custom = field.custom ?? (isVideoVariantFieldConfig(props) ? props : void 0);
|
|
73
|
+
const presets = custom?.presets ?? {};
|
|
74
|
+
const apiBase = useMemo(
|
|
75
|
+
() => custom ? resolveApiBase(custom.enqueuePath) : "/api",
|
|
76
|
+
[custom]
|
|
77
|
+
);
|
|
78
|
+
const presetNames = useMemo(() => Object.keys(presets), [presets]);
|
|
79
|
+
const docId = useMemo(() => {
|
|
80
|
+
if (typeof id === "undefined" || id === null) return null;
|
|
81
|
+
const value = String(id);
|
|
82
|
+
if (!value || value === "create") return null;
|
|
83
|
+
return value;
|
|
84
|
+
}, [id]);
|
|
85
|
+
const [EasyCrop, setEasyCrop] = useState(
|
|
86
|
+
null
|
|
87
|
+
);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (typeof window !== "undefined" && !EasyCrop) {
|
|
90
|
+
import('react-easy-crop').then((module) => {
|
|
91
|
+
setEasyCrop(() => module.default);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}, [EasyCrop]);
|
|
95
|
+
const [selectedPreset, setSelectedPreset] = useState(
|
|
96
|
+
presetNames[0]
|
|
97
|
+
);
|
|
98
|
+
const [docData, setDocData] = useState(null);
|
|
99
|
+
const [loadingDoc, setLoadingDoc] = useState(false);
|
|
100
|
+
const [error, setError] = useState(null);
|
|
101
|
+
const [message, setMessage] = useState(null);
|
|
102
|
+
const [jobStatus, setJobStatus] = useState(null);
|
|
103
|
+
const [pollingJobId, setPollingJobId] = useState(null);
|
|
104
|
+
const [variants, setVariants] = useState([]);
|
|
105
|
+
const [previewKey, setPreviewKey] = useState(null);
|
|
106
|
+
const [replaceLoading, setReplaceLoading] = useState(null);
|
|
107
|
+
const [deleteLoading, setDeleteLoading] = useState(null);
|
|
108
|
+
const [cropState, setCropState] = useState({ x: 0, y: 0 });
|
|
109
|
+
const [zoom, setZoom] = useState(1);
|
|
110
|
+
const [cropSelection, setCropSelection] = useState(DEFAULT_CROP);
|
|
111
|
+
const messageClassName = message?.type === "error" ? "bg-rose-50 text-rose-700" : message?.type === "info" ? "bg-slate-50 text-slate-700" : "bg-emerald-50 text-emerald-700";
|
|
112
|
+
const sendEnqueueRequest = useCallback(
|
|
113
|
+
async ({
|
|
114
|
+
documentId,
|
|
115
|
+
presetName,
|
|
116
|
+
crop
|
|
117
|
+
}) => {
|
|
118
|
+
if (!custom) {
|
|
119
|
+
throw new Error("Video plugin is not configured.");
|
|
120
|
+
}
|
|
121
|
+
const response = await fetch(custom.enqueuePath, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
credentials: "include",
|
|
124
|
+
headers: {
|
|
125
|
+
"Content-Type": "application/json"
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
collection: custom.collectionSlug,
|
|
129
|
+
id: documentId,
|
|
130
|
+
preset: presetName,
|
|
131
|
+
crop
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
const payload = await response.json().catch(() => ({}));
|
|
136
|
+
throw new Error(
|
|
137
|
+
payload?.error ?? `Request failed (${response.status})`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return await response.json();
|
|
141
|
+
},
|
|
142
|
+
[custom]
|
|
143
|
+
);
|
|
144
|
+
const sendRemoveVariantRequest = useCallback(
|
|
145
|
+
async ({
|
|
146
|
+
documentId,
|
|
147
|
+
preset: preset2,
|
|
148
|
+
variantId,
|
|
149
|
+
variantIndex
|
|
150
|
+
}) => {
|
|
151
|
+
if (!custom) {
|
|
152
|
+
throw new Error("Video plugin is not configured.");
|
|
153
|
+
}
|
|
154
|
+
const response = await fetch(custom.removeVariantPath, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
credentials: "include",
|
|
157
|
+
headers: {
|
|
158
|
+
"Content-Type": "application/json"
|
|
159
|
+
},
|
|
160
|
+
body: JSON.stringify({
|
|
161
|
+
collection: custom.collectionSlug,
|
|
162
|
+
id: documentId,
|
|
163
|
+
preset: preset2,
|
|
164
|
+
variantId,
|
|
165
|
+
variantIndex
|
|
166
|
+
})
|
|
167
|
+
});
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
const payload = await response.json().catch(() => ({}));
|
|
170
|
+
throw new Error(
|
|
171
|
+
payload?.error ?? `Request failed (${response.status})`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return await response.json();
|
|
175
|
+
},
|
|
176
|
+
[custom]
|
|
177
|
+
);
|
|
178
|
+
const sendReplaceOriginalRequest = useCallback(
|
|
179
|
+
async ({ documentId, preset: preset2 }) => {
|
|
180
|
+
if (!custom) {
|
|
181
|
+
throw new Error("Video plugin is not configured.");
|
|
182
|
+
}
|
|
183
|
+
const response = await fetch(custom.replaceOriginalPath, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
credentials: "include",
|
|
186
|
+
headers: {
|
|
187
|
+
"Content-Type": "application/json"
|
|
188
|
+
},
|
|
189
|
+
body: JSON.stringify({
|
|
190
|
+
collection: custom.collectionSlug,
|
|
191
|
+
id: documentId,
|
|
192
|
+
preset: preset2
|
|
193
|
+
})
|
|
194
|
+
});
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
const payload = await response.json().catch(() => ({}));
|
|
197
|
+
throw new Error(
|
|
198
|
+
payload?.error ?? `Request failed (${response.status})`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return await response.json();
|
|
202
|
+
},
|
|
203
|
+
[custom]
|
|
204
|
+
);
|
|
205
|
+
const bridgeEnqueue = useCallback(
|
|
206
|
+
async ({ mediaId, preset: preset2, crop }) => {
|
|
207
|
+
if (!mediaId) {
|
|
208
|
+
throw new Error("A media id is required to enqueue transcoding.");
|
|
209
|
+
}
|
|
210
|
+
if (!preset2) {
|
|
211
|
+
throw new Error("A preset name is required to enqueue transcoding.");
|
|
212
|
+
}
|
|
213
|
+
const job = await sendEnqueueRequest({
|
|
214
|
+
documentId: mediaId,
|
|
215
|
+
presetName: preset2,
|
|
216
|
+
crop
|
|
217
|
+
});
|
|
218
|
+
const jobId = typeof job.id === "string" || typeof job.id === "number" ? String(job.id) : "";
|
|
219
|
+
if (!jobId) {
|
|
220
|
+
throw new Error("Unable to determine job id returned by the server.");
|
|
221
|
+
}
|
|
222
|
+
return { jobId };
|
|
223
|
+
},
|
|
224
|
+
[sendEnqueueRequest]
|
|
225
|
+
);
|
|
226
|
+
const bridgeRemoveVariant = useCallback(
|
|
227
|
+
async ({
|
|
228
|
+
mediaId,
|
|
229
|
+
preset: preset2,
|
|
230
|
+
variantId,
|
|
231
|
+
variantIndex
|
|
232
|
+
}) => {
|
|
233
|
+
if (!mediaId) {
|
|
234
|
+
throw new Error("A media id is required to remove a variant.");
|
|
235
|
+
}
|
|
236
|
+
const payload = await sendRemoveVariantRequest({
|
|
237
|
+
documentId: mediaId,
|
|
238
|
+
preset: preset2,
|
|
239
|
+
variantId,
|
|
240
|
+
variantIndex
|
|
241
|
+
});
|
|
242
|
+
if (docId === mediaId) {
|
|
243
|
+
const nextDoc = payload?.doc;
|
|
244
|
+
if (nextDoc && typeof nextDoc === "object") {
|
|
245
|
+
setDocData(nextDoc);
|
|
246
|
+
const docVariants = Array.isArray(nextDoc?.variants) ? nextDoc.variants : [];
|
|
247
|
+
setVariants(docVariants);
|
|
248
|
+
} else {
|
|
249
|
+
setVariants(
|
|
250
|
+
(current) => current.filter((variant, index) => {
|
|
251
|
+
if (typeof variantIndex === "number" && variantIndex === index) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
if (variantId && readVariantId(variant) === variantId) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
if (preset2 && readVariantPreset(variant) === preset2) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
})
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (typeof window !== "undefined") {
|
|
266
|
+
window.dispatchEvent(new CustomEvent("payload-video-plugin:change"));
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
[docId, sendRemoveVariantRequest]
|
|
270
|
+
);
|
|
271
|
+
const bridgeReplaceOriginal = useCallback(
|
|
272
|
+
async ({ mediaId, preset: preset2 }) => {
|
|
273
|
+
if (!mediaId) {
|
|
274
|
+
throw new Error("A media id is required to replace the original.");
|
|
275
|
+
}
|
|
276
|
+
const payload = await sendReplaceOriginalRequest({
|
|
277
|
+
documentId: mediaId,
|
|
278
|
+
preset: preset2
|
|
279
|
+
});
|
|
280
|
+
if (docId === mediaId) {
|
|
281
|
+
const nextDoc = payload?.doc;
|
|
282
|
+
if (nextDoc && typeof nextDoc === "object") {
|
|
283
|
+
setDocData(nextDoc);
|
|
284
|
+
const docVariants = Array.isArray(nextDoc?.variants) ? nextDoc.variants : [];
|
|
285
|
+
setVariants(docVariants);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (typeof window !== "undefined") {
|
|
289
|
+
window.dispatchEvent(new CustomEvent("payload-video-plugin:change"));
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
[docId, sendReplaceOriginalRequest]
|
|
293
|
+
);
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
if (typeof window === "undefined" || !custom) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const bridge = {
|
|
299
|
+
presets: presetNames,
|
|
300
|
+
enqueueTranscode: async (args) => {
|
|
301
|
+
const cropArg = args.crop;
|
|
302
|
+
const presetConfig = presets[args.preset];
|
|
303
|
+
const allowCrop = Boolean(presetConfig?.enableCrop);
|
|
304
|
+
return bridgeEnqueue({
|
|
305
|
+
mediaId: args.mediaId,
|
|
306
|
+
preset: args.preset,
|
|
307
|
+
crop: allowCrop ? cropArg : void 0
|
|
308
|
+
});
|
|
309
|
+
},
|
|
310
|
+
removeVariant: async (args) => bridgeRemoveVariant({
|
|
311
|
+
mediaId: args.mediaId,
|
|
312
|
+
preset: args.preset,
|
|
313
|
+
variantId: args.variantId,
|
|
314
|
+
variantIndex: args.variantIndex
|
|
315
|
+
}),
|
|
316
|
+
replaceOriginal: async (args) => bridgeReplaceOriginal({
|
|
317
|
+
mediaId: args.mediaId,
|
|
318
|
+
preset: args.preset
|
|
319
|
+
})
|
|
320
|
+
};
|
|
321
|
+
window.__PAYLOAD_VIDEO_PLUGIN__ = bridge;
|
|
322
|
+
window.dispatchEvent(new CustomEvent("payload-video-plugin:change"));
|
|
323
|
+
return () => {
|
|
324
|
+
if (window.__PAYLOAD_VIDEO_PLUGIN__?.enqueueTranscode === bridge.enqueueTranscode) {
|
|
325
|
+
delete window.__PAYLOAD_VIDEO_PLUGIN__;
|
|
326
|
+
window.dispatchEvent(new CustomEvent("payload-video-plugin:change"));
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}, [
|
|
330
|
+
bridgeEnqueue,
|
|
331
|
+
bridgeRemoveVariant,
|
|
332
|
+
bridgeReplaceOriginal,
|
|
333
|
+
custom,
|
|
334
|
+
presetNames,
|
|
335
|
+
presets
|
|
336
|
+
]);
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
if (presetNames.length > 0 && !selectedPreset) {
|
|
339
|
+
setSelectedPreset(presetNames[0]);
|
|
340
|
+
}
|
|
341
|
+
}, [presetNames, selectedPreset]);
|
|
342
|
+
useEffect(() => {
|
|
343
|
+
if (!docId || !custom) {
|
|
344
|
+
setDocData(null);
|
|
345
|
+
setVariants([]);
|
|
346
|
+
setMessage(null);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
let cancelled = false;
|
|
350
|
+
const fetchDocument = async () => {
|
|
351
|
+
try {
|
|
352
|
+
setLoadingDoc(true);
|
|
353
|
+
setError(null);
|
|
354
|
+
setMessage(null);
|
|
355
|
+
const response = await fetch(
|
|
356
|
+
`${apiBase}/${custom.collectionSlug}/${docId}`,
|
|
357
|
+
{
|
|
358
|
+
credentials: "include"
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
if (!response.ok) {
|
|
362
|
+
throw new Error(`Failed to load document (${response.status})`);
|
|
363
|
+
}
|
|
364
|
+
const payload = await response.json();
|
|
365
|
+
if (!cancelled) {
|
|
366
|
+
const nextDoc = payload.doc ?? payload;
|
|
367
|
+
setDocData(nextDoc);
|
|
368
|
+
const docVariants = Array.isArray(nextDoc?.variants) ? nextDoc.variants : [];
|
|
369
|
+
setVariants(docVariants);
|
|
370
|
+
}
|
|
371
|
+
} catch (fetchError) {
|
|
372
|
+
if (!cancelled) {
|
|
373
|
+
setError(
|
|
374
|
+
fetchError instanceof Error ? fetchError.message : "Failed to load document data."
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
} finally {
|
|
378
|
+
if (!cancelled) {
|
|
379
|
+
setLoadingDoc(false);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
void fetchDocument();
|
|
384
|
+
return () => {
|
|
385
|
+
cancelled = true;
|
|
386
|
+
};
|
|
387
|
+
}, [apiBase, custom, docId, jobStatus?.state]);
|
|
388
|
+
const enqueue = useCallback(async () => {
|
|
389
|
+
if (!custom || !docId || !selectedPreset) return;
|
|
390
|
+
try {
|
|
391
|
+
setError(null);
|
|
392
|
+
const allowCrop = Boolean(presets[selectedPreset]?.enableCrop);
|
|
393
|
+
const data = await sendEnqueueRequest({
|
|
394
|
+
documentId: docId,
|
|
395
|
+
presetName: selectedPreset,
|
|
396
|
+
crop: allowCrop ? cropSelection : void 0
|
|
397
|
+
});
|
|
398
|
+
setJobStatus(data);
|
|
399
|
+
setPollingJobId(String(data.id));
|
|
400
|
+
} catch (enqueueError) {
|
|
401
|
+
setError(
|
|
402
|
+
enqueueError instanceof Error ? enqueueError.message : "Failed to enqueue job."
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
}, [
|
|
406
|
+
custom,
|
|
407
|
+
cropSelection,
|
|
408
|
+
docId,
|
|
409
|
+
presets,
|
|
410
|
+
selectedPreset,
|
|
411
|
+
sendEnqueueRequest
|
|
412
|
+
]);
|
|
413
|
+
useEffect(() => {
|
|
414
|
+
if (!pollingJobId || !custom) return;
|
|
415
|
+
let active = true;
|
|
416
|
+
const interval = window.setInterval(async () => {
|
|
417
|
+
try {
|
|
418
|
+
const response = await fetch(`${custom.statusPath}/${pollingJobId}`, {
|
|
419
|
+
credentials: "include"
|
|
420
|
+
});
|
|
421
|
+
if (!response.ok) {
|
|
422
|
+
if (response.status === 404) {
|
|
423
|
+
if (active) {
|
|
424
|
+
setJobStatus(null);
|
|
425
|
+
setPollingJobId(null);
|
|
426
|
+
}
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
throw new Error(`Status request failed (${response.status})`);
|
|
430
|
+
}
|
|
431
|
+
const payload = await response.json();
|
|
432
|
+
if (active) {
|
|
433
|
+
setJobStatus(payload);
|
|
434
|
+
if (payload.state === "completed" || payload.state === "failed") {
|
|
435
|
+
setPollingJobId(null);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
} catch (statusError) {
|
|
439
|
+
if (active) {
|
|
440
|
+
setPollingJobId(null);
|
|
441
|
+
setError(
|
|
442
|
+
statusError instanceof Error ? statusError.message : "Status polling failed."
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}, 1500);
|
|
447
|
+
return () => {
|
|
448
|
+
active = false;
|
|
449
|
+
window.clearInterval(interval);
|
|
450
|
+
};
|
|
451
|
+
}, [custom, pollingJobId]);
|
|
452
|
+
const preset = selectedPreset ? presets[selectedPreset] : void 0;
|
|
453
|
+
const cropEnabled = Boolean(preset?.enableCrop);
|
|
454
|
+
useEffect(() => {
|
|
455
|
+
if (!cropEnabled) {
|
|
456
|
+
setCropSelection(DEFAULT_CROP);
|
|
457
|
+
setCropState({ x: 0, y: 0 });
|
|
458
|
+
setZoom(1);
|
|
459
|
+
}
|
|
460
|
+
}, [cropEnabled]);
|
|
461
|
+
const handleCropComplete = useCallback(
|
|
462
|
+
(area) => {
|
|
463
|
+
setCropSelection({
|
|
464
|
+
width: area.width / 100,
|
|
465
|
+
height: area.height / 100,
|
|
466
|
+
x: area.x / 100,
|
|
467
|
+
y: area.y / 100
|
|
468
|
+
});
|
|
469
|
+
},
|
|
470
|
+
[]
|
|
471
|
+
);
|
|
472
|
+
const handleTogglePreview = useCallback((key) => {
|
|
473
|
+
setPreviewKey((current) => current === key ? null : key);
|
|
474
|
+
}, []);
|
|
475
|
+
const handleReplaceOriginalVariant = useCallback(
|
|
476
|
+
async (variant) => {
|
|
477
|
+
if (!docId) {
|
|
478
|
+
setError("Document id is missing; save before replacing the original.");
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const preset2 = readVariantPreset(variant);
|
|
482
|
+
const identifier = resolveVariantIdentifier(variant, preset2 ?? "variant");
|
|
483
|
+
if (typeof window !== "undefined") {
|
|
484
|
+
const confirmation = window.confirm(
|
|
485
|
+
`Replace the original file with variant "${identifier}"?`
|
|
486
|
+
);
|
|
487
|
+
if (!confirmation) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
setError(null);
|
|
492
|
+
setMessage(null);
|
|
493
|
+
setReplaceLoading(identifier);
|
|
494
|
+
try {
|
|
495
|
+
const payload = await sendReplaceOriginalRequest({
|
|
496
|
+
documentId: docId,
|
|
497
|
+
preset: preset2
|
|
498
|
+
});
|
|
499
|
+
const updatedDoc = payload?.doc;
|
|
500
|
+
if (updatedDoc && typeof updatedDoc === "object") {
|
|
501
|
+
setDocData(updatedDoc);
|
|
502
|
+
const docVariants = Array.isArray(updatedDoc?.variants) ? updatedDoc.variants : [];
|
|
503
|
+
setVariants(docVariants);
|
|
504
|
+
} else {
|
|
505
|
+
setVariants(
|
|
506
|
+
(current) => current.filter(
|
|
507
|
+
(candidate) => readVariantPreset(candidate) !== readVariantPreset(variant)
|
|
508
|
+
)
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
setPreviewKey((current) => current === identifier ? null : current);
|
|
512
|
+
setMessage({ type: "success", text: "Original video replaced." });
|
|
513
|
+
if (typeof window !== "undefined") {
|
|
514
|
+
window.dispatchEvent(new CustomEvent("payload-video-plugin:change"));
|
|
515
|
+
}
|
|
516
|
+
} catch (replaceError) {
|
|
517
|
+
setError(
|
|
518
|
+
replaceError instanceof Error ? replaceError.message : "Failed to replace the original video."
|
|
519
|
+
);
|
|
520
|
+
} finally {
|
|
521
|
+
setReplaceLoading(null);
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
[docId, sendReplaceOriginalRequest]
|
|
525
|
+
);
|
|
526
|
+
const handleRemoveVariant = useCallback(
|
|
527
|
+
async (variant, index) => {
|
|
528
|
+
if (!docId) {
|
|
529
|
+
setError("Document id is missing; save before removing variants.");
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const fallbackIdentifier = `variant-${index + 1}`;
|
|
533
|
+
const identifier = resolveVariantIdentifier(variant, fallbackIdentifier);
|
|
534
|
+
if (typeof window !== "undefined") {
|
|
535
|
+
const confirmation = window.confirm(
|
|
536
|
+
`Remove variant "${identifier}"? This cannot be undone.`
|
|
537
|
+
);
|
|
538
|
+
if (!confirmation) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
setError(null);
|
|
543
|
+
setMessage(null);
|
|
544
|
+
setDeleteLoading(identifier);
|
|
545
|
+
try {
|
|
546
|
+
const payload = await sendRemoveVariantRequest({
|
|
547
|
+
documentId: docId,
|
|
548
|
+
preset: readVariantPreset(variant),
|
|
549
|
+
variantId: readVariantId(variant),
|
|
550
|
+
variantIndex: index
|
|
551
|
+
});
|
|
552
|
+
const updatedDoc = payload?.doc;
|
|
553
|
+
if (updatedDoc && typeof updatedDoc === "object") {
|
|
554
|
+
setDocData(updatedDoc);
|
|
555
|
+
const docVariants = Array.isArray(updatedDoc?.variants) ? updatedDoc.variants : [];
|
|
556
|
+
setVariants(docVariants);
|
|
557
|
+
} else {
|
|
558
|
+
setVariants(
|
|
559
|
+
(current) => current.filter(
|
|
560
|
+
(_candidate, candidateIndex) => candidateIndex !== index
|
|
561
|
+
)
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
setPreviewKey((current) => current === identifier ? null : current);
|
|
565
|
+
setMessage({ type: "success", text: "Variant removed." });
|
|
566
|
+
if (typeof window !== "undefined") {
|
|
567
|
+
window.dispatchEvent(new CustomEvent("payload-video-plugin:change"));
|
|
568
|
+
}
|
|
569
|
+
} catch (removeError) {
|
|
570
|
+
setError(
|
|
571
|
+
removeError instanceof Error ? removeError.message : "Failed to remove variant."
|
|
572
|
+
);
|
|
573
|
+
} finally {
|
|
574
|
+
setDeleteLoading(null);
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
[docId, sendRemoveVariantRequest]
|
|
578
|
+
);
|
|
579
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 rounded-xl border border-slate-200 bg-white/60 p-4 shadow-sm", children: [
|
|
580
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
581
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-slate-700", children: field.label ?? "Video processor" }),
|
|
582
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
583
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex flex-col text-xs font-medium text-slate-600", children: [
|
|
584
|
+
"Preset",
|
|
585
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
586
|
+
"select",
|
|
587
|
+
{
|
|
588
|
+
className: "mt-1 w-48 rounded-lg border border-slate-300 bg-white px-2 py-1 text-sm focus:border-slate-500 focus:outline-none",
|
|
589
|
+
value: selectedPreset ?? "",
|
|
590
|
+
onChange: (event) => setSelectedPreset(event.target.value),
|
|
591
|
+
children: presetNames.map((name) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: name, children: presets[name]?.label ?? name }, name))
|
|
592
|
+
}
|
|
593
|
+
)
|
|
594
|
+
] }),
|
|
595
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
596
|
+
"button",
|
|
597
|
+
{
|
|
598
|
+
className: "rounded-lg bg-slate-900 px-4 py-2 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:bg-slate-400",
|
|
599
|
+
type: "button",
|
|
600
|
+
disabled: !docId || !selectedPreset,
|
|
601
|
+
onClick: enqueue,
|
|
602
|
+
children: "Enqueue variant"
|
|
603
|
+
}
|
|
604
|
+
),
|
|
605
|
+
jobStatus ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-slate-600", children: [
|
|
606
|
+
"Job ",
|
|
607
|
+
jobStatus.id,
|
|
608
|
+
": ",
|
|
609
|
+
jobStatus.state,
|
|
610
|
+
" \xB7",
|
|
611
|
+
" ",
|
|
612
|
+
formatProgress(jobStatus.progress)
|
|
613
|
+
] }) : null,
|
|
614
|
+
custom?.queueName ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-slate-400", children: [
|
|
615
|
+
"Queue: ",
|
|
616
|
+
custom.queueName
|
|
617
|
+
] }) : null
|
|
618
|
+
] }),
|
|
619
|
+
!docId ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rounded-lg bg-amber-50 px-3 py-2 text-xs text-amber-700", children: "Save the document before enqueuing video variants." }) : null,
|
|
620
|
+
error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rounded-lg bg-rose-50 px-3 py-2 text-xs text-rose-700", children: error }) : null,
|
|
621
|
+
message ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: `rounded-lg px-3 py-2 text-xs ${messageClassName}`, children: message.text }) : null
|
|
622
|
+
] }),
|
|
623
|
+
cropEnabled && docData?.url ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
624
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Crop" }),
|
|
625
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "video-crop-wrapper", children: EasyCrop ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
626
|
+
EasyCrop,
|
|
627
|
+
{
|
|
628
|
+
video: docData.url,
|
|
629
|
+
crop: cropState,
|
|
630
|
+
zoom,
|
|
631
|
+
aspect: void 0,
|
|
632
|
+
onCropChange: setCropState,
|
|
633
|
+
onZoomChange: setZoom,
|
|
634
|
+
onCropComplete: handleCropComplete,
|
|
635
|
+
objectFit: "contain",
|
|
636
|
+
showGrid: true
|
|
637
|
+
}
|
|
638
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { children: "Loading cropper..." }) }),
|
|
639
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 text-xs text-slate-600", children: [
|
|
640
|
+
"Zoom",
|
|
641
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
642
|
+
"input",
|
|
643
|
+
{
|
|
644
|
+
type: "range",
|
|
645
|
+
min: 1,
|
|
646
|
+
max: 3,
|
|
647
|
+
step: 0.1,
|
|
648
|
+
value: zoom,
|
|
649
|
+
onChange: (event) => setZoom(Number(event.target.value)),
|
|
650
|
+
className: "w-48"
|
|
651
|
+
}
|
|
652
|
+
)
|
|
653
|
+
] })
|
|
654
|
+
] }) : null,
|
|
655
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 text-xs text-slate-600", children: [
|
|
656
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold uppercase tracking-wide text-slate-500", children: "Variants" }),
|
|
657
|
+
loadingDoc ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-slate-500", children: "Loading document metadata\u2026" }) : null,
|
|
658
|
+
variants.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rounded-lg bg-slate-100 px-3 py-2 text-xs text-slate-600", children: "No variants available yet. Enqueue a preset to generate a new version of the video." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-3", children: variants.map((variant, index) => {
|
|
659
|
+
const presetName = readVariantPreset(variant) ?? `Variant ${index + 1}`;
|
|
660
|
+
const identifier = resolveVariantIdentifier(
|
|
661
|
+
variant,
|
|
662
|
+
`variant-${index + 1}`
|
|
663
|
+
);
|
|
664
|
+
const url = typeof variant.url === "string" ? variant.url : void 0;
|
|
665
|
+
const previewing = previewKey === identifier;
|
|
666
|
+
const replacing = replaceLoading === identifier;
|
|
667
|
+
const deleting = deleteLoading === identifier;
|
|
668
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
669
|
+
"div",
|
|
670
|
+
{
|
|
671
|
+
className: "flex flex-col gap-2 rounded-lg border border-slate-200 bg-white px-3 py-2",
|
|
672
|
+
children: [
|
|
673
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
674
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
675
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-slate-700", children: presetName }),
|
|
676
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-slate-500", children: [
|
|
677
|
+
formatBytes(variant.size),
|
|
678
|
+
" \xB7",
|
|
679
|
+
" ",
|
|
680
|
+
formatSeconds(variant.duration)
|
|
681
|
+
] })
|
|
682
|
+
] }),
|
|
683
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
684
|
+
url ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
685
|
+
"button",
|
|
686
|
+
{
|
|
687
|
+
className: "rounded-lg border border-slate-300 px-3 py-1 text-xs font-medium text-slate-700 transition hover:border-slate-400 hover:text-slate-900 disabled:cursor-not-allowed disabled:opacity-60",
|
|
688
|
+
type: "button",
|
|
689
|
+
onClick: () => handleTogglePreview(identifier),
|
|
690
|
+
disabled: replacing || deleting,
|
|
691
|
+
children: previewing ? "Close preview" : "Preview"
|
|
692
|
+
}
|
|
693
|
+
) : null,
|
|
694
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
695
|
+
"button",
|
|
696
|
+
{
|
|
697
|
+
className: "rounded-lg bg-slate-900 px-3 py-1 text-xs font-semibold text-white disabled:cursor-not-allowed disabled:bg-slate-400",
|
|
698
|
+
type: "button",
|
|
699
|
+
onClick: () => void handleReplaceOriginalVariant(variant),
|
|
700
|
+
disabled: replacing || deleting,
|
|
701
|
+
children: replacing ? "Replacing\u2026" : "Replace original"
|
|
702
|
+
}
|
|
703
|
+
),
|
|
704
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
705
|
+
"button",
|
|
706
|
+
{
|
|
707
|
+
className: "rounded-lg border border-rose-300 px-3 py-1 text-xs font-semibold text-rose-700 transition hover:border-rose-400 hover:text-rose-900 disabled:cursor-not-allowed disabled:opacity-60",
|
|
708
|
+
type: "button",
|
|
709
|
+
onClick: () => void handleRemoveVariant(variant, index),
|
|
710
|
+
disabled: deleting || replacing,
|
|
711
|
+
children: deleting ? "Deleting\u2026" : "Delete"
|
|
712
|
+
}
|
|
713
|
+
)
|
|
714
|
+
] })
|
|
715
|
+
] }),
|
|
716
|
+
previewing && url ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-lg border border-slate-200", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
717
|
+
"video",
|
|
718
|
+
{
|
|
719
|
+
className: "w-full bg-black",
|
|
720
|
+
controls: true,
|
|
721
|
+
preload: "metadata",
|
|
722
|
+
src: url
|
|
723
|
+
}
|
|
724
|
+
) }) : null
|
|
725
|
+
]
|
|
726
|
+
},
|
|
727
|
+
`${identifier}-${index}`
|
|
728
|
+
);
|
|
729
|
+
}) })
|
|
730
|
+
] })
|
|
731
|
+
] });
|
|
732
|
+
};
|
|
733
|
+
var VideoField_default = VideoField;
|
|
734
|
+
|
|
735
|
+
module.exports = VideoField_default;
|
|
736
|
+
//# sourceMappingURL=VideoField.cjs.map
|
|
737
|
+
//# sourceMappingURL=VideoField.cjs.map
|