@frkntmbs/strapi-plugin-video-optimizer 1.0.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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +286 -0
  3. package/admin/custom.d.ts +8 -0
  4. package/admin/src/buildVersion.ts +3 -0
  5. package/admin/src/components/AssetOptimizationLabel.tsx +61 -0
  6. package/admin/src/components/BridgeProviders.tsx +123 -0
  7. package/admin/src/components/MediaLibraryCacheBridge.tsx +24 -0
  8. package/admin/src/components/MediaLibraryCardActionsBridge.tsx +249 -0
  9. package/admin/src/components/MediaLibraryJobWatcher.tsx +136 -0
  10. package/admin/src/components/MediaLibraryProgressBridge.tsx +97 -0
  11. package/admin/src/components/OptimizationChoicePicker.tsx +65 -0
  12. package/admin/src/components/OptimizationResizeFields.tsx +120 -0
  13. package/admin/src/components/OptimizationVideoFields.tsx +217 -0
  14. package/admin/src/components/UploadEnhancerBridge.tsx +205 -0
  15. package/admin/src/components/upload/PendingAssetStep.tsx +97 -0
  16. package/admin/src/defaultGlobalSettings.ts +32 -0
  17. package/admin/src/hooks/useDefaultOptimizationMode.ts +24 -0
  18. package/admin/src/hooks/useUploadWithOptimizer.ts +45 -0
  19. package/admin/src/index.ts +84 -0
  20. package/admin/src/pages/SettingsPage.tsx +208 -0
  21. package/admin/src/pluginId.ts +79 -0
  22. package/admin/src/translations/en.json +74 -0
  23. package/admin/src/translations/tr.json +74 -0
  24. package/admin/src/utils/adminFetch.ts +57 -0
  25. package/admin/src/utils/captureQueryClient.ts +34 -0
  26. package/admin/src/utils/debugMediaLibraryProgress.ts +70 -0
  27. package/admin/src/utils/extractAssetDimensions.ts +22 -0
  28. package/admin/src/utils/initJobPoller.ts +173 -0
  29. package/admin/src/utils/initMediaLibraryCardActions.ts +308 -0
  30. package/admin/src/utils/initMediaLibraryProgress.ts +219 -0
  31. package/admin/src/utils/initUploadEnhancer.ts +447 -0
  32. package/admin/src/utils/invalidateMediaLibrary.ts +203 -0
  33. package/admin/src/utils/jobProgressStore.ts +113 -0
  34. package/admin/src/utils/mediaLibraryCardMatch.ts +414 -0
  35. package/admin/src/utils/mediaLibraryCardStore.ts +223 -0
  36. package/admin/src/utils/mediaLibraryQueryBridge.ts +113 -0
  37. package/admin/src/utils/mediaLibraryRoute.ts +9 -0
  38. package/admin/src/utils/optimizationFields.ts +17 -0
  39. package/admin/src/utils/probeVideoDimensions.ts +94 -0
  40. package/admin/src/utils/uploadAssetStore.ts +670 -0
  41. package/admin/tsconfig.json +8 -0
  42. package/dist/admin/SettingsPage-CN2fR83m.js +150 -0
  43. package/dist/admin/SettingsPage-D6e536P0.mjs +150 -0
  44. package/dist/admin/en-CqM903j3.js +77 -0
  45. package/dist/admin/en-CsHicGzL.mjs +77 -0
  46. package/dist/admin/index-BjWoS0YU.js +2542 -0
  47. package/dist/admin/index-Cs_uiChW.mjs +2541 -0
  48. package/dist/admin/index-DOuHOS2G.js +8799 -0
  49. package/dist/admin/index-rAmxCQz6.mjs +8781 -0
  50. package/dist/admin/index.js +4 -0
  51. package/dist/admin/index.mjs +4 -0
  52. package/dist/admin/tr-Y0-ANilh.mjs +77 -0
  53. package/dist/admin/tr-muzHkdC4.js +77 -0
  54. package/dist/server/index.js +1538 -0
  55. package/dist/server/index.mjs +1533 -0
  56. package/package.json +100 -0
  57. package/server/index.js +1 -0
  58. package/server/src/bootstrap.ts +377 -0
  59. package/server/src/buildVersion.ts +1 -0
  60. package/server/src/config/defaults.ts +91 -0
  61. package/server/src/config/index.ts +51 -0
  62. package/server/src/constants.ts +83 -0
  63. package/server/src/controllers/index.ts +7 -0
  64. package/server/src/controllers/job.ts +102 -0
  65. package/server/src/controllers/preference.ts +206 -0
  66. package/server/src/index.ts +15 -0
  67. package/server/src/register.ts +19 -0
  68. package/server/src/routes/index.ts +103 -0
  69. package/server/src/services/index.ts +9 -0
  70. package/server/src/services/job-queue.ts +663 -0
  71. package/server/src/services/optimizer.ts +284 -0
  72. package/server/src/services/preference.ts +172 -0
  73. package/server/src/utils/request-context.ts +7 -0
  74. package/server/src/utils/upload-preferences-context.ts +202 -0
  75. package/server/tsconfig.json +8 -0
  76. package/strapi-admin.js +7 -0
  77. package/strapi-server.js +7 -0
@@ -0,0 +1,670 @@
1
+ import { DEFAULT_GLOBAL_SETTINGS, mergeGlobalSettings } from '../defaultGlobalSettings';
2
+ import type {
3
+ AssetOptimizationPreference,
4
+ GlobalOptimizationSettings,
5
+ OptimizationChoice,
6
+ OptimizationSettings,
7
+ } from '../pluginId';
8
+ import { isVideoFileName } from '../pluginId';
9
+ import { PLUGIN_BUILD_MARKER } from '../buildVersion';
10
+ import { wakeJobPoller } from './initJobPoller';
11
+
12
+ export interface UploadAssetEntry {
13
+ assetId: string;
14
+ assetName: string;
15
+ width?: number;
16
+ height?: number;
17
+ actionsContainer: HTMLElement;
18
+ footerHost?: HTMLElement;
19
+ }
20
+
21
+ let globalSettings: GlobalOptimizationSettings = { ...DEFAULT_GLOBAL_SETTINGS };
22
+ const assetPreferencesById = new Map<string, AssetOptimizationPreference>();
23
+ const assetNamesById = new Map<string, string>();
24
+ const assetDimensionsById = new Map<string, { width: number; height: number }>();
25
+ const assetPreferencesByFileKey = new Map<string, AssetOptimizationPreference>();
26
+ const committedPreferencesByAssetId = new Map<string, AssetOptimizationPreference>();
27
+ const committedPreferencesByName = new Map<string, AssetOptimizationPreference>();
28
+ const committedAssetIdByName = new Map<string, string>();
29
+
30
+ let cards: UploadAssetEntry[] = [];
31
+ let cardsSnapshot: UploadAssetEntry[] = [];
32
+ let listeners = new Set<() => void>();
33
+ let dialogElement: HTMLElement | null = null;
34
+ let editingAssetId: string | null = null;
35
+ let draftPreference: AssetOptimizationPreference | null = null;
36
+
37
+ const STABLE_EMPTY_DRAFT: AssetOptimizationPreference = Object.freeze({
38
+ choice: 'original',
39
+ custom: undefined,
40
+ });
41
+
42
+ let fetchPatched = false;
43
+ let xhrPatched = false;
44
+
45
+ export const buildFileKey = (name: string, size: number, lastModified: number) =>
46
+ `${name}::${size}::${lastModified}`;
47
+
48
+ const notify = () => {
49
+ listeners.forEach((listener) => listener());
50
+ };
51
+
52
+ export const subscribeUploadAssets = (listener: () => void) => {
53
+ listeners.add(listener);
54
+ return () => listeners.delete(listener);
55
+ };
56
+
57
+ export const getUploadAssetCards = () => cardsSnapshot;
58
+
59
+ export const getGlobalSettings = () => globalSettings;
60
+
61
+ export const setGlobalSettings = (settings: GlobalOptimizationSettings) => {
62
+ globalSettings = settings;
63
+ notify();
64
+ };
65
+
66
+ export const createCustomFromGlobal = (): OptimizationSettings => ({
67
+ defaultFormat: globalSettings.defaultFormat,
68
+ videoCodec: globalSettings.videoCodec,
69
+ crf: globalSettings.crf,
70
+ preset: globalSettings.preset,
71
+ maxWidth: 0,
72
+ maxHeight: 0,
73
+ audioMode: globalSettings.audioMode,
74
+ audioBitrate: globalSettings.audioBitrate,
75
+ });
76
+
77
+ export const createCustomForAsset = (assetId: string): OptimizationSettings => {
78
+ const dimensions = assetDimensionsById.get(assetId);
79
+ const base = createCustomFromGlobal();
80
+
81
+ return {
82
+ ...base,
83
+ maxWidth: dimensions?.width ?? base.maxWidth,
84
+ maxHeight: dimensions?.height ?? base.maxHeight,
85
+ };
86
+ };
87
+
88
+ export const resolveCustomSettingsForAsset = (
89
+ assetId: string,
90
+ current?: OptimizationSettings
91
+ ): OptimizationSettings => {
92
+ const seeded = createCustomForAsset(assetId);
93
+ const base = current ?? seeded;
94
+
95
+ return {
96
+ ...base,
97
+ maxWidth: base.maxWidth > 0 ? base.maxWidth : seeded.maxWidth,
98
+ maxHeight: base.maxHeight > 0 ? base.maxHeight : seeded.maxHeight,
99
+ };
100
+ };
101
+
102
+ export const getSourceDimensionsForAsset = (assetId: string) => assetDimensionsById.get(assetId);
103
+
104
+ export const updateAssetDimensions = (
105
+ assetId: string,
106
+ dimensions: { width: number; height: number }
107
+ ) => {
108
+ assetDimensionsById.set(assetId, dimensions);
109
+
110
+ const index = cards.findIndex((entry) => entry.assetId === assetId);
111
+
112
+ if (index >= 0) {
113
+ cards[index] = {
114
+ ...cards[index],
115
+ width: dimensions.width,
116
+ height: dimensions.height,
117
+ };
118
+ cardsSnapshot = cards.slice();
119
+ }
120
+
121
+ notify();
122
+ };
123
+
124
+ export const getAssetDimensions = (assetId: string) => assetDimensionsById.get(assetId);
125
+
126
+ export const createDefaultPreference = (): AssetOptimizationPreference => {
127
+ const choice = globalSettings.defaultChoice;
128
+
129
+ return {
130
+ choice,
131
+ custom: choice === 'custom' ? createCustomFromGlobal() : undefined,
132
+ };
133
+ };
134
+
135
+ export const getAssetPreference = (assetId: string): AssetOptimizationPreference => {
136
+ return assetPreferencesById.get(assetId) ?? createDefaultPreference();
137
+ };
138
+
139
+ export const setAssetPreference = (assetId: string, preference: AssetOptimizationPreference) => {
140
+ assetPreferencesById.set(assetId, preference);
141
+ rememberCommittedPreference(assetId, assetNamesById.get(assetId), preference);
142
+ notify();
143
+ };
144
+
145
+ export const registerAsset = (
146
+ assetId: string,
147
+ assetName: string,
148
+ dimensions?: { width: number; height: number }
149
+ ) => {
150
+ if (!assetPreferencesById.has(assetId)) {
151
+ assetPreferencesById.set(assetId, createDefaultPreference());
152
+ }
153
+
154
+ assetNamesById.set(assetId, assetName);
155
+
156
+ if (dimensions?.width && dimensions?.height) {
157
+ assetDimensionsById.set(assetId, dimensions);
158
+ }
159
+ };
160
+
161
+ export const getUploadDialogElement = () => dialogElement;
162
+
163
+ export const setUploadDialogElement = (element: HTMLElement | null) => {
164
+ dialogElement = element;
165
+ notify();
166
+ };
167
+
168
+ export const getEditingAssetId = () => editingAssetId;
169
+
170
+ export const getDraftPreference = (): AssetOptimizationPreference => {
171
+ if (draftPreference) {
172
+ return draftPreference;
173
+ }
174
+
175
+ return STABLE_EMPTY_DRAFT;
176
+ };
177
+
178
+ export const openAssetEditor = (assetId: string) => {
179
+ editingAssetId = assetId;
180
+ draftPreference = structuredClone(getAssetPreference(assetId));
181
+
182
+ if (draftPreference.choice === 'custom') {
183
+ draftPreference.custom = resolveCustomSettingsForAsset(assetId, draftPreference.custom);
184
+ }
185
+
186
+ notify();
187
+ };
188
+
189
+ export const closeAssetEditor = () => {
190
+ editingAssetId = null;
191
+ draftPreference = null;
192
+ notify();
193
+ };
194
+
195
+ export const setDraftChoice = (choice: OptimizationChoice) => {
196
+ if (!draftPreference) {
197
+ draftPreference = createDefaultPreference();
198
+ }
199
+
200
+ draftPreference = {
201
+ choice,
202
+ custom:
203
+ choice === 'custom'
204
+ ? resolveCustomSettingsForAsset(editingAssetId ?? '', draftPreference.custom)
205
+ : undefined,
206
+ };
207
+ notify();
208
+ };
209
+
210
+ export const setDraftCustom = (custom: OptimizationSettings) => {
211
+ if (!draftPreference) {
212
+ draftPreference = createDefaultPreference();
213
+ }
214
+
215
+ draftPreference = {
216
+ choice: 'custom',
217
+ custom,
218
+ };
219
+ notify();
220
+ };
221
+
222
+ export const saveAssetEditor = () => {
223
+ if (editingAssetId && draftPreference) {
224
+ setAssetPreference(editingAssetId, structuredClone(draftPreference));
225
+ }
226
+ closeAssetEditor();
227
+ };
228
+
229
+ const cardsEqual = (left: UploadAssetEntry[], right: UploadAssetEntry[]) => {
230
+ if (left.length !== right.length) {
231
+ return false;
232
+ }
233
+
234
+ return left.every(
235
+ (entry, index) =>
236
+ entry.assetId === right[index]?.assetId &&
237
+ entry.assetName === right[index]?.assetName &&
238
+ entry.width === right[index]?.width &&
239
+ entry.height === right[index]?.height &&
240
+ entry.actionsContainer === right[index]?.actionsContainer &&
241
+ entry.footerHost === right[index]?.footerHost
242
+ );
243
+ };
244
+
245
+ export const setUploadAssetCards = (nextCards: UploadAssetEntry[]) => {
246
+ if (cardsEqual(cards, nextCards)) {
247
+ return;
248
+ }
249
+
250
+ cards = nextCards;
251
+ cardsSnapshot = nextCards.slice();
252
+ notify();
253
+ };
254
+
255
+ export const clearUploadSession = () => {
256
+ for (const [assetId, preference] of assetPreferencesById.entries()) {
257
+ rememberCommittedPreference(assetId, assetNamesById.get(assetId), preference);
258
+ }
259
+
260
+ assetPreferencesById.clear();
261
+ assetNamesById.clear();
262
+ assetDimensionsById.clear();
263
+ cards = [];
264
+ cardsSnapshot = [];
265
+ editingAssetId = null;
266
+ draftPreference = null;
267
+ dialogElement = null;
268
+ notify();
269
+ };
270
+
271
+ const findPreferenceByAssetName = (
272
+ name: string,
273
+ file: File
274
+ ): AssetOptimizationPreference | undefined => {
275
+ for (const [assetId, assetName] of assetNamesById.entries()) {
276
+ if (!namesMatch(assetName, name) && !namesMatch(assetName, file.name)) {
277
+ continue;
278
+ }
279
+
280
+ const preference = assetPreferencesById.get(assetId);
281
+
282
+ if (preference) {
283
+ assetPreferencesByFileKey.set(
284
+ buildFileKey(file.name, file.size, file.lastModified),
285
+ preference
286
+ );
287
+ }
288
+
289
+ return preference;
290
+ }
291
+
292
+ return undefined;
293
+ };
294
+
295
+ const normalizeName = (value: string) => value.trim().toLowerCase();
296
+
297
+ const rememberCommittedPreference = (
298
+ assetId: string | undefined,
299
+ assetName: string | undefined,
300
+ preference: AssetOptimizationPreference
301
+ ) => {
302
+ const snapshot = structuredClone(preference);
303
+
304
+ if (assetId) {
305
+ committedPreferencesByAssetId.set(assetId, snapshot);
306
+ }
307
+
308
+ if (assetName) {
309
+ committedPreferencesByName.set(normalizeName(assetName), snapshot);
310
+ }
311
+
312
+ if (assetId && assetName) {
313
+ committedAssetIdByName.set(normalizeName(assetName), assetId);
314
+ }
315
+ };
316
+
317
+ const findCommittedPreference = (
318
+ file: File,
319
+ parsed: Record<string, unknown>
320
+ ): AssetOptimizationPreference | undefined => {
321
+ const assetId = parsed.optimizerAssetId;
322
+
323
+ if (typeof assetId === 'string') {
324
+ const byAssetId = committedPreferencesByAssetId.get(assetId);
325
+ if (byAssetId) {
326
+ return byAssetId;
327
+ }
328
+ }
329
+
330
+ const candidates = [file.name, String(parsed.name ?? '')].filter(Boolean);
331
+
332
+ for (const candidate of candidates) {
333
+ const byName = committedPreferencesByName.get(normalizeName(candidate));
334
+ if (byName) {
335
+ return byName;
336
+ }
337
+ }
338
+
339
+ return undefined;
340
+ };
341
+
342
+ export const clearCommittedPreferences = () => {
343
+ committedPreferencesByAssetId.clear();
344
+ committedPreferencesByName.clear();
345
+ committedAssetIdByName.clear();
346
+ };
347
+
348
+ const namesMatch = (left: string, right: string) => normalizeName(left) === normalizeName(right);
349
+
350
+ const findCardForFile = (file: File, parsed: Record<string, unknown>, index: number, batchSize: number) => {
351
+ if (batchSize > 1 && batchSize === cardsSnapshot.length) {
352
+ return cardsSnapshot[index];
353
+ }
354
+
355
+ const candidates = [String(parsed.name ?? ''), file.name].filter(Boolean);
356
+
357
+ for (const candidate of candidates) {
358
+ const card = cardsSnapshot.find((entry) => namesMatch(entry.assetName, candidate));
359
+
360
+ if (card) {
361
+ return card;
362
+ }
363
+ }
364
+
365
+ return undefined;
366
+ };
367
+
368
+ const resolvePreferenceForFile = (
369
+ file: File,
370
+ parsed: Record<string, unknown>,
371
+ index: number,
372
+ batchSize: number
373
+ ): AssetOptimizationPreference => {
374
+ const committed = findCommittedPreference(file, parsed);
375
+ if (committed) {
376
+ assetPreferencesByFileKey.set(
377
+ buildFileKey(file.name, file.size, file.lastModified),
378
+ committed
379
+ );
380
+ return committed;
381
+ }
382
+
383
+ const card = findCardForFile(file, parsed, index, batchSize);
384
+
385
+ if (card) {
386
+ const byCard = assetPreferencesById.get(card.assetId);
387
+
388
+ if (byCard) {
389
+ assetPreferencesByFileKey.set(
390
+ buildFileKey(file.name, file.size, file.lastModified),
391
+ byCard
392
+ );
393
+ return byCard;
394
+ }
395
+ }
396
+
397
+ const name = String(parsed.name ?? file.name);
398
+ const fileKey = buildFileKey(name, file.size, file.lastModified);
399
+
400
+ const byFileKey = assetPreferencesByFileKey.get(fileKey);
401
+ if (byFileKey) {
402
+ return byFileKey;
403
+ }
404
+
405
+ const assetId = parsed.optimizerAssetId;
406
+ if (typeof assetId === 'string') {
407
+ const byId = assetPreferencesById.get(assetId);
408
+ if (byId) {
409
+ assetPreferencesByFileKey.set(fileKey, byId);
410
+ return byId;
411
+ }
412
+ }
413
+
414
+ const byName = findPreferenceByAssetName(name, file);
415
+ if (byName) {
416
+ return byName;
417
+ }
418
+
419
+ if (parsed.optimizationChoice) {
420
+ return {
421
+ choice: parsed.optimizationChoice as OptimizationChoice,
422
+ custom: parsed.optimizationCustom as OptimizationSettings | undefined,
423
+ };
424
+ }
425
+
426
+ return createDefaultPreference();
427
+ };
428
+
429
+ const buildFileInfoPayload = (
430
+ parsed: Record<string, unknown>,
431
+ preference: AssetOptimizationPreference
432
+ ) => {
433
+ const payload: Record<string, unknown> = {
434
+ ...parsed,
435
+ optimizationChoice: preference.choice,
436
+ };
437
+
438
+ if (preference.choice === 'custom' && preference.custom) {
439
+ payload.optimizationCustom = preference.custom;
440
+ } else {
441
+ delete payload.optimizationCustom;
442
+ }
443
+
444
+ return payload;
445
+ };
446
+
447
+ const formDataHasVideo = (formData: FormData) => {
448
+ let hasVideo = false;
449
+
450
+ formData.forEach((value, key) => {
451
+ if (key === 'files' && value instanceof File && isVideoFileName(value.name)) {
452
+ hasVideo = true;
453
+ }
454
+ });
455
+
456
+ return hasVideo;
457
+ };
458
+
459
+ const isMediaUploadRequest = (url: string, method?: string) => {
460
+ if (method?.toUpperCase() !== 'POST') {
461
+ return false;
462
+ }
463
+
464
+ try {
465
+ const pathname = new URL(url, window.location.origin).pathname.replace(/\/+$/, '') || '/';
466
+
467
+ return (
468
+ pathname === '/upload' ||
469
+ pathname === '/upload/unstable/upload-file' ||
470
+ pathname.endsWith('/upload/unstable/upload-file')
471
+ );
472
+ } catch {
473
+ return (
474
+ url.includes('/upload/unstable/upload-file') ||
475
+ (url.includes('/upload') && !url.includes('/upload/actions/'))
476
+ );
477
+ }
478
+ };
479
+
480
+ const buildOptimizedFormData = (sourceFormData: FormData) => {
481
+ const nextFormData = new FormData();
482
+ const files: File[] = [];
483
+ const fileInfos: string[] = [];
484
+
485
+ sourceFormData.forEach((value, key) => {
486
+ if (key === 'files' && value instanceof File) {
487
+ files.push(value);
488
+ } else if (key === 'fileInfo' && typeof value === 'string') {
489
+ fileInfos.push(value);
490
+ }
491
+ });
492
+
493
+ const preferencesPayload: Array<{
494
+ assetId?: string;
495
+ fileName: string;
496
+ preference: AssetOptimizationPreference;
497
+ }> = [];
498
+
499
+ files.forEach((file, index) => {
500
+ nextFormData.append('files', file);
501
+
502
+ const parsed = JSON.parse(fileInfos[index] ?? '{}') as Record<string, unknown>;
503
+ const card = findCardForFile(file, parsed, index, files.length);
504
+
505
+ let assetId = parsed.optimizerAssetId as string | undefined;
506
+ if (!assetId && card) {
507
+ assetId = card.assetId;
508
+ }
509
+
510
+ if (!assetId) {
511
+ const name = String(parsed.name ?? file.name);
512
+ assetId =
513
+ committedAssetIdByName.get(normalizeName(name)) ??
514
+ committedAssetIdByName.get(normalizeName(file.name));
515
+
516
+ if (!assetId) {
517
+ for (const [id, assetName] of assetNamesById.entries()) {
518
+ if (namesMatch(assetName, name) || namesMatch(assetName, file.name)) {
519
+ assetId = id;
520
+ break;
521
+ }
522
+ }
523
+ }
524
+ }
525
+
526
+ if (assetId) {
527
+ parsed.optimizerAssetId = assetId;
528
+ }
529
+
530
+ const preference = resolvePreferenceForFile(file, parsed, index, files.length);
531
+
532
+ preferencesPayload.push({
533
+ assetId,
534
+ fileName: file.name,
535
+ preference,
536
+ });
537
+
538
+ nextFormData.append('fileInfo', JSON.stringify(buildFileInfoPayload(parsed, preference)));
539
+ });
540
+
541
+ nextFormData.append('videoOptimizerPreferences', JSON.stringify(preferencesPayload));
542
+ nextFormData.append('videoOptimizerBuildMarker', PLUGIN_BUILD_MARKER);
543
+
544
+ if (typeof window !== 'undefined') {
545
+ console.info(
546
+ `[video-optimizer] Upload payload patched (${PLUGIN_BUILD_MARKER})`,
547
+ preferencesPayload.map((entry) => ({
548
+ fileName: entry.fileName,
549
+ choice: entry.preference.choice,
550
+ format:
551
+ entry.preference.choice === 'custom'
552
+ ? entry.preference.custom?.defaultFormat
553
+ : entry.preference.choice,
554
+ }))
555
+ );
556
+ }
557
+
558
+ sourceFormData.forEach((value, key) => {
559
+ if (key !== 'files' && key !== 'fileInfo') {
560
+ nextFormData.append(key, value);
561
+ }
562
+ });
563
+
564
+ files.forEach((file) => {
565
+ committedPreferencesByName.delete(normalizeName(file.name));
566
+ });
567
+ preferencesPayload.forEach((entry) => {
568
+ if (entry.assetId) {
569
+ committedPreferencesByAssetId.delete(entry.assetId);
570
+ }
571
+ committedPreferencesByName.delete(normalizeName(entry.fileName));
572
+ });
573
+
574
+ return nextFormData;
575
+ };
576
+
577
+ export const patchUploadFetch = () => {
578
+ if (fetchPatched || typeof window === 'undefined') {
579
+ return;
580
+ }
581
+
582
+ fetchPatched = true;
583
+ const originalFetch = window.fetch.bind(window);
584
+
585
+ window.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
586
+ const request = input instanceof Request ? input : null;
587
+ const url =
588
+ typeof input === 'string'
589
+ ? input
590
+ : input instanceof URL
591
+ ? input.href
592
+ : input.url;
593
+ const method = init?.method ?? request?.method;
594
+ const body = init?.body ?? request?.body;
595
+
596
+ if (isMediaUploadRequest(url, method) && body instanceof FormData) {
597
+ const hasVideo = formDataHasVideo(body);
598
+ const response = await originalFetch(input, {
599
+ ...init,
600
+ method: method ?? init?.method ?? 'POST',
601
+ body: buildOptimizedFormData(body),
602
+ });
603
+
604
+ if (hasVideo && response.ok) {
605
+ wakeJobPoller();
606
+ }
607
+
608
+ return response;
609
+ }
610
+
611
+ return originalFetch(input, init);
612
+ };
613
+ };
614
+
615
+ export const patchUploadXHR = () => {
616
+ if (xhrPatched || typeof window === 'undefined' || typeof XMLHttpRequest === 'undefined') {
617
+ return;
618
+ }
619
+
620
+ xhrPatched = true;
621
+
622
+ const originalOpen = XMLHttpRequest.prototype.open;
623
+ const originalSend = XMLHttpRequest.prototype.send;
624
+
625
+ XMLHttpRequest.prototype.open = function open(method, url, ...rest) {
626
+ (this as XMLHttpRequest & { _optimizerMethod?: string; _optimizerUrl?: string })._optimizerMethod =
627
+ method;
628
+ (this as XMLHttpRequest & { _optimizerMethod?: string; _optimizerUrl?: string })._optimizerUrl =
629
+ typeof url === 'string' ? url : url.toString();
630
+ return originalOpen.call(this, method, url, ...rest);
631
+ };
632
+
633
+ XMLHttpRequest.prototype.send = function send(body?: Document | XMLHttpRequestBodyInit | null) {
634
+ const request = this as XMLHttpRequest & {
635
+ _optimizerMethod?: string;
636
+ _optimizerUrl?: string;
637
+ };
638
+
639
+ if (
640
+ body instanceof FormData &&
641
+ isMediaUploadRequest(request._optimizerUrl ?? '', request._optimizerMethod)
642
+ ) {
643
+ const optimizedBody = buildOptimizedFormData(body);
644
+ const hasVideo = formDataHasVideo(body);
645
+
646
+ if (hasVideo) {
647
+ this.addEventListener(
648
+ 'load',
649
+ () => {
650
+ if (this.status >= 200 && this.status < 300) {
651
+ wakeJobPoller();
652
+ }
653
+ },
654
+ { once: true }
655
+ );
656
+ }
657
+
658
+ return originalSend.call(this, optimizedBody);
659
+ }
660
+
661
+ return originalSend.call(this, body as XMLHttpRequestBodyInit | null | undefined);
662
+ };
663
+ };
664
+
665
+ export const isPreferenceCustomized = (assetId: string) => {
666
+ const current = getAssetPreference(assetId);
667
+ const defaults = createDefaultPreference();
668
+
669
+ return JSON.stringify(current) !== JSON.stringify(defaults);
670
+ };
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "@strapi/typescript-utils/tsconfigs/admin",
3
+ "include": ["./src", "./custom.d.ts"],
4
+ "compilerOptions": {
5
+ "rootDir": "../",
6
+ "baseUrl": "."
7
+ }
8
+ }