@strapi/upload 5.47.1 → 5.48.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 (101) hide show
  1. package/dist/admin/components/EditAssetDialog/EditAssetContent.js +12 -2
  2. package/dist/admin/components/EditAssetDialog/EditAssetContent.js.map +1 -1
  3. package/dist/admin/components/EditAssetDialog/EditAssetContent.mjs +12 -2
  4. package/dist/admin/components/EditAssetDialog/EditAssetContent.mjs.map +1 -1
  5. package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.js +1 -0
  6. package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.js.map +1 -1
  7. package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.mjs +1 -0
  8. package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.mjs.map +1 -1
  9. package/dist/admin/future/components/Drawer.js +7 -8
  10. package/dist/admin/future/components/Drawer.js.map +1 -1
  11. package/dist/admin/future/components/Drawer.mjs +7 -8
  12. package/dist/admin/future/components/Drawer.mjs.map +1 -1
  13. package/dist/admin/future/components/UploadProgressDialog.js +33 -29
  14. package/dist/admin/future/components/UploadProgressDialog.js.map +1 -1
  15. package/dist/admin/future/components/UploadProgressDialog.mjs +36 -32
  16. package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -1
  17. package/dist/admin/future/pages/Assets/AssetsPage.js +2 -2
  18. package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
  19. package/dist/admin/future/pages/Assets/AssetsPage.mjs +3 -3
  20. package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
  21. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +626 -169
  22. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
  23. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +630 -175
  24. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
  25. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.js +25 -5
  26. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.js.map +1 -1
  27. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.mjs +25 -5
  28. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.mjs.map +1 -1
  29. package/dist/admin/future/services/api.js +124 -200
  30. package/dist/admin/future/services/api.js.map +1 -1
  31. package/dist/admin/future/services/api.mjs +124 -200
  32. package/dist/admin/future/services/api.mjs.map +1 -1
  33. package/dist/admin/future/services/assets.js +57 -1
  34. package/dist/admin/future/services/assets.js.map +1 -1
  35. package/dist/admin/future/services/assets.mjs +56 -2
  36. package/dist/admin/future/services/assets.mjs.map +1 -1
  37. package/dist/admin/future/services/settings.js +18 -0
  38. package/dist/admin/future/services/settings.js.map +1 -0
  39. package/dist/admin/future/services/settings.mjs +16 -0
  40. package/dist/admin/future/services/settings.mjs.map +1 -0
  41. package/dist/admin/future/services/uploadFileViaXHR.js +92 -0
  42. package/dist/admin/future/services/uploadFileViaXHR.js.map +1 -0
  43. package/dist/admin/future/services/uploadFileViaXHR.mjs +88 -0
  44. package/dist/admin/future/services/uploadFileViaXHR.mjs.map +1 -0
  45. package/dist/admin/future/store/uploadProgress.js +32 -26
  46. package/dist/admin/future/store/uploadProgress.js.map +1 -1
  47. package/dist/admin/future/store/uploadProgress.mjs +32 -27
  48. package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
  49. package/dist/admin/future/utils/createRafBatcher.js +42 -0
  50. package/dist/admin/future/utils/createRafBatcher.js.map +1 -0
  51. package/dist/admin/future/utils/createRafBatcher.mjs +40 -0
  52. package/dist/admin/future/utils/createRafBatcher.mjs.map +1 -0
  53. package/dist/admin/future/utils/downloadFile.js +19 -0
  54. package/dist/admin/future/utils/downloadFile.js.map +1 -0
  55. package/dist/admin/future/utils/downloadFile.mjs +17 -0
  56. package/dist/admin/future/utils/downloadFile.mjs.map +1 -0
  57. package/dist/admin/hooks/useAssets.js +5 -3
  58. package/dist/admin/hooks/useAssets.js.map +1 -1
  59. package/dist/admin/hooks/useAssets.mjs +5 -3
  60. package/dist/admin/hooks/useAssets.mjs.map +1 -1
  61. package/dist/admin/src/components/EditAssetDialog/EditAssetContent.d.ts +2 -1
  62. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.d.ts +15 -1
  63. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetPreview.d.ts +4 -1
  64. package/dist/admin/src/future/services/api.d.ts +9 -8
  65. package/dist/admin/src/future/services/assets.d.ts +6 -1
  66. package/dist/admin/src/future/services/uploadFileViaXHR.d.ts +34 -0
  67. package/dist/admin/src/future/store/uploadProgress.d.ts +17 -4
  68. package/dist/admin/src/future/utils/createRafBatcher.d.ts +23 -0
  69. package/dist/admin/src/future/utils/downloadFile.d.ts +6 -0
  70. package/dist/admin/translations/en.json.js +21 -0
  71. package/dist/admin/translations/en.json.js.map +1 -1
  72. package/dist/admin/translations/en.json.mjs +21 -0
  73. package/dist/admin/translations/en.json.mjs.map +1 -1
  74. package/dist/server/controllers/admin-upload.js +69 -118
  75. package/dist/server/controllers/admin-upload.js.map +1 -1
  76. package/dist/server/controllers/admin-upload.mjs +69 -118
  77. package/dist/server/controllers/admin-upload.mjs.map +1 -1
  78. package/dist/server/routes/admin.js +2 -2
  79. package/dist/server/routes/admin.js.map +1 -1
  80. package/dist/server/routes/admin.mjs +2 -2
  81. package/dist/server/routes/admin.mjs.map +1 -1
  82. package/dist/server/services/image-manipulation.js +16 -8
  83. package/dist/server/services/image-manipulation.js.map +1 -1
  84. package/dist/server/services/image-manipulation.mjs +16 -8
  85. package/dist/server/services/image-manipulation.mjs.map +1 -1
  86. package/dist/server/services/upload.js +1 -1
  87. package/dist/server/services/upload.js.map +1 -1
  88. package/dist/server/services/upload.mjs +1 -1
  89. package/dist/server/services/upload.mjs.map +1 -1
  90. package/dist/server/src/controllers/admin-upload.d.ts +6 -8
  91. package/dist/server/src/controllers/admin-upload.d.ts.map +1 -1
  92. package/dist/server/src/controllers/index.d.ts +1 -1
  93. package/dist/server/src/index.d.ts +1 -1
  94. package/dist/server/src/services/image-manipulation.d.ts +5 -0
  95. package/dist/server/src/services/image-manipulation.d.ts.map +1 -1
  96. package/dist/server/src/services/upload.d.ts.map +1 -1
  97. package/dist/server/src/types.d.ts +2 -2
  98. package/dist/server/src/types.d.ts.map +1 -1
  99. package/dist/shared/contracts/files.d.ts +19 -2
  100. package/dist/shared/contracts/files.d.ts.map +1 -1
  101. package/package.json +7 -7
@@ -11,9 +11,12 @@ var Drawer = require('../../../../components/Drawer.js');
11
11
  var enums = require('../../../../enums.js');
12
12
  var assets = require('../../../../services/assets.js');
13
13
  var folders = require('../../../../services/folders.js');
14
+ var settings = require('../../../../services/settings.js');
15
+ var downloadFile = require('../../../../utils/downloadFile.js');
14
16
  var files = require('../../../../utils/files.js');
15
17
  var getAssetIcon = require('../../../../utils/getAssetIcon.js');
16
18
  var translations = require('../../../../utils/translations.js');
19
+ var useFolderInfo = require('../../hooks/useFolderInfo.js');
17
20
  var AssetPreview = require('./AssetPreview.js');
18
21
 
19
22
  function _interopNamespaceDefault(e) {
@@ -37,6 +40,22 @@ var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
37
40
 
38
41
  // Name of the parameter to look for in the URL to open the drawer
39
42
  const URL_PARAM = 'assetId';
43
+ const DrawerNotifyContext = /*#__PURE__*/ React__namespace.createContext(null);
44
+ const useDrawerNotify = ()=>{
45
+ const ctx = React__namespace.useContext(DrawerNotifyContext);
46
+ if (!ctx) {
47
+ throw new Error('useDrawerNotify must be used within AssetDetails');
48
+ }
49
+ return ctx;
50
+ };
51
+ const AssetOperationsContext = /*#__PURE__*/ React__namespace.createContext(null);
52
+ const useAssetOperation = ()=>{
53
+ const ctx = React__namespace.useContext(AssetOperationsContext);
54
+ if (!ctx) {
55
+ throw new Error('useAssetOperation must be used within AssetDetails');
56
+ }
57
+ return ctx;
58
+ };
40
59
  /* -------------------------------------------------------------------------------------------------
41
60
  * useAssetDetailsParam - sync drawer visibility with URL ?{URL_PARAM}={id}
42
61
  * -----------------------------------------------------------------------------------------------*/ const useAssetDetailsParam = ()=>{
@@ -116,7 +135,50 @@ const DetailItem = ({ label, value })=>/*#__PURE__*/ jsxRuntime.jsxs(DetailItemC
116
135
  });
117
136
  /* -------------------------------------------------------------------------------------------------
118
137
  * DetailField
119
- * -----------------------------------------------------------------------------------------------*/ const StyledWarning = styledComponents.styled(icons.WarningCircle)`
138
+ * -----------------------------------------------------------------------------------------------*/ /**
139
+ * Make the asset details Form behave as a flex column inside Drawer.Body so
140
+ * the scrollable area can grow while the footer stays pinned at the bottom.
141
+ * The Form component from `@strapi/admin/strapi-admin` only forwards `width`
142
+ * + `height` to its Box, so we target the rendered `<form>` element via a
143
+ * styled-components descendant rule.
144
+ */ const FormShell = styledComponents.styled(designSystem.Box)`
145
+ display: flex;
146
+ flex-direction: column;
147
+ flex: 1;
148
+ min-height: 0;
149
+
150
+ > form {
151
+ display: flex;
152
+ flex-direction: column;
153
+ flex: 1;
154
+ min-height: 0;
155
+ position: relative;
156
+ }
157
+ `;
158
+ /**
159
+ * In-drawer toast container
160
+ */ const DrawerToastSlot = styledComponents.styled(designSystem.Box)`
161
+ position: absolute;
162
+ top: ${({ theme })=>theme.spaces[2]};
163
+ left: 50%;
164
+ transform: translateX(-50%);
165
+ z-index: 10;
166
+ width: calc(100% - ${({ theme })=>theme.spaces[2]});
167
+ `;
168
+ /**
169
+ * Full-form overlay rendered during long-running drawer-scoped mutations
170
+ * (e.g. replacing the binary). Sits above the toast slot (z-index 10) and
171
+ * the in-drawer Alert so the user can't interact with the form mid-flight.
172
+ */ const DrawerBusyOverlay = styledComponents.styled(designSystem.Flex)`
173
+ position: absolute;
174
+ inset: 0;
175
+ z-index: 20;
176
+ align-items: center;
177
+ justify-content: center;
178
+ background: ${({ theme })=>theme.colors.neutral0};
179
+ opacity: 0.7;
180
+ `;
181
+ const StyledWarning = styledComponents.styled(icons.WarningCircle)`
120
182
  width: 1.6rem;
121
183
  height: 1.6rem;
122
184
 
@@ -189,11 +251,283 @@ const LocationField = ({ label, rootLabel, folders })=>{
189
251
  ]
190
252
  });
191
253
  };
192
- const AssetDetails = ({ asset })=>{
254
+ /* -------------------------------------------------------------------------------------------------
255
+ * DeleteAssetButton
256
+ * -----------------------------------------------------------------------------------------------*/ const DeleteAssetButton = ()=>{
257
+ const { formatMessage } = reactIntl.useIntl();
258
+ const { deleteAsset, isDeleting } = useAssetOperation();
259
+ const [isOpen, setIsOpen] = React__namespace.useState(false);
260
+ const handleConfirm = async ()=>{
261
+ await deleteAsset();
262
+ setIsOpen(false);
263
+ };
264
+ const triggerLabel = formatMessage({
265
+ id: translations.getTranslationKey('asset-details.delete.trigger'),
266
+ defaultMessage: 'Delete this file'
267
+ });
268
+ return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Dialog.Root, {
269
+ open: isOpen,
270
+ onOpenChange: setIsOpen,
271
+ children: [
272
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Trigger, {
273
+ children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
274
+ withTooltip: false,
275
+ label: triggerLabel,
276
+ variant: "danger-light",
277
+ children: /*#__PURE__*/ jsxRuntime.jsx(icons.Trash, {})
278
+ })
279
+ }),
280
+ /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Dialog.Content, {
281
+ children: [
282
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Header, {
283
+ children: formatMessage({
284
+ id: translations.getTranslationKey('asset-details.delete.title'),
285
+ defaultMessage: 'Delete this media file?'
286
+ })
287
+ }),
288
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Body, {
289
+ icon: /*#__PURE__*/ jsxRuntime.jsx(icons.WarningCircle, {
290
+ width: "24px",
291
+ height: "24px",
292
+ fill: "danger600"
293
+ }),
294
+ textAlign: "center",
295
+ children: formatMessage({
296
+ id: translations.getTranslationKey('asset-details.delete.description'),
297
+ defaultMessage: 'This file cannot be recovered once deleted. If it is currently in use, linked content will break and image containers will be empty.'
298
+ })
299
+ }),
300
+ /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Dialog.Footer, {
301
+ children: [
302
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Cancel, {
303
+ children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
304
+ variant: "tertiary",
305
+ disabled: isDeleting,
306
+ fullWidth: true,
307
+ children: formatMessage({
308
+ id: 'app.components.Button.cancel',
309
+ defaultMessage: 'Cancel'
310
+ })
311
+ })
312
+ }),
313
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Action, {
314
+ children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
315
+ variant: "danger-light",
316
+ loading: isDeleting,
317
+ onClick: handleConfirm,
318
+ fullWidth: true,
319
+ children: formatMessage({
320
+ id: 'app.components.Button.confirm',
321
+ defaultMessage: 'Confirm'
322
+ })
323
+ })
324
+ })
325
+ ]
326
+ })
327
+ ]
328
+ })
329
+ ]
330
+ });
331
+ };
332
+ const CopyLinkButton = ({ asset })=>{
333
+ const { formatMessage } = reactIntl.useIntl();
334
+ const { copy } = strapiAdmin.useClipboard();
335
+ const notify = useDrawerNotify();
336
+ const handleCopy = async ()=>{
337
+ const url = files.prefixFileUrlWithBackendUrl(asset.url);
338
+ if (!url) return;
339
+ const didCopy = await copy(url);
340
+ notify({
341
+ type: didCopy ? 'success' : 'danger',
342
+ message: didCopy ? formatMessage({
343
+ id: translations.getTranslationKey('asset-details.copy-link.success'),
344
+ defaultMessage: 'Link copied.'
345
+ }) : formatMessage({
346
+ id: translations.getTranslationKey('asset-details.copy-link.error'),
347
+ defaultMessage: 'Failed to copy the link.'
348
+ })
349
+ });
350
+ };
351
+ return /*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
352
+ withTooltip: false,
353
+ label: formatMessage({
354
+ id: translations.getTranslationKey('asset-details.copy-link.trigger'),
355
+ defaultMessage: 'Copy link'
356
+ }),
357
+ variant: "tertiary",
358
+ onClick: handleCopy,
359
+ children: /*#__PURE__*/ jsxRuntime.jsx(icons.Link, {})
360
+ });
361
+ };
362
+ const DownloadAssetButton = ({ asset })=>{
363
+ const { formatMessage } = reactIntl.useIntl();
364
+ const notify = useDrawerNotify();
365
+ const [isDownloading, setIsDownloading] = React__namespace.useState(false);
366
+ const handleDownload = async ()=>{
367
+ const url = files.prefixFileUrlWithBackendUrl(asset.url);
368
+ if (!url) return;
369
+ setIsDownloading(true);
370
+ try {
371
+ await downloadFile.downloadFile(url, asset.name);
372
+ } catch {
373
+ notify({
374
+ type: 'danger',
375
+ message: formatMessage({
376
+ id: translations.getTranslationKey('asset-details.download.error'),
377
+ defaultMessage: 'Failed to download the file.'
378
+ })
379
+ });
380
+ } finally{
381
+ setIsDownloading(false);
382
+ }
383
+ };
384
+ return /*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
385
+ withTooltip: false,
386
+ label: formatMessage({
387
+ id: translations.getTranslationKey('asset-details.download.trigger'),
388
+ defaultMessage: 'Download'
389
+ }),
390
+ variant: "tertiary",
391
+ onClick: handleDownload,
392
+ disabled: isDownloading,
393
+ children: /*#__PURE__*/ jsxRuntime.jsx(icons.Download, {})
394
+ });
395
+ };
396
+ /* -------------------------------------------------------------------------------------------------
397
+ * ReplaceAssetButton
398
+ * -----------------------------------------------------------------------------------------------*/ const ReplaceAssetButton = ()=>{
399
+ const { formatMessage } = reactIntl.useIntl();
400
+ const { replaceAsset, isReplacing } = useAssetOperation();
401
+ const fileInputRef = React__namespace.useRef(null);
402
+ const [isDialogOpen, setIsDialogOpen] = React__namespace.useState(false);
403
+ const { data: settings$1 } = settings.useGetSettingsQuery();
404
+ const aiEnabled = settings$1?.data?.aiMetadata ?? false;
405
+ const handleTriggerClick = ()=>{
406
+ setIsDialogOpen(true);
407
+ };
408
+ const handleContinue = ()=>{
409
+ // Confirm first, then open the native picker so the user only commits to
410
+ // replacing after acknowledging the warning. The actual POST is delegated
411
+ // to the parent (which owns the mutation + loading state).
412
+ setIsDialogOpen(false);
413
+ fileInputRef.current?.click();
414
+ };
415
+ const handleFileChange = async (event)=>{
416
+ const file = event.target.files?.[0];
417
+ // Reset the native input so the same file can be picked again later.
418
+ event.target.value = '';
419
+ if (!file) {
420
+ return;
421
+ }
422
+ await replaceAsset(file);
423
+ };
424
+ return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
425
+ children: [
426
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.VisuallyHidden, {
427
+ children: /*#__PURE__*/ jsxRuntime.jsx("input", {
428
+ ref: fileInputRef,
429
+ type: "file",
430
+ multiple: false,
431
+ onChange: handleFileChange,
432
+ "aria-hidden": true,
433
+ tabIndex: -1
434
+ })
435
+ }),
436
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
437
+ withTooltip: false,
438
+ label: formatMessage({
439
+ id: translations.getTranslationKey('asset-details.replace.trigger'),
440
+ defaultMessage: 'Replace this file'
441
+ }),
442
+ variant: "tertiary",
443
+ onClick: handleTriggerClick,
444
+ disabled: isReplacing,
445
+ children: /*#__PURE__*/ jsxRuntime.jsx(icons.ArrowsCounterClockwise, {})
446
+ }),
447
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Root, {
448
+ open: isDialogOpen,
449
+ onOpenChange: setIsDialogOpen,
450
+ children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Dialog.Content, {
451
+ children: [
452
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Header, {
453
+ children: formatMessage({
454
+ id: translations.getTranslationKey('asset-details.replace.title'),
455
+ defaultMessage: 'Replace this media file?'
456
+ })
457
+ }),
458
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Body, {
459
+ textAlign: "center",
460
+ children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
461
+ direction: "column",
462
+ textAlign: "center",
463
+ children: [
464
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
465
+ variant: "omega",
466
+ children: formatMessage({
467
+ id: translations.getTranslationKey('asset-details.replace.description'),
468
+ defaultMessage: 'Current content will be permanently replaced.'
469
+ })
470
+ }),
471
+ aiEnabled ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
472
+ variant: "omega",
473
+ children: formatMessage({
474
+ id: translations.getTranslationKey('asset-details.replace.description.ai'),
475
+ defaultMessage: 'AI will generate new metadata after upload.'
476
+ })
477
+ }) : null
478
+ ]
479
+ })
480
+ }),
481
+ /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Dialog.Footer, {
482
+ children: [
483
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Cancel, {
484
+ children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
485
+ variant: "tertiary",
486
+ fullWidth: true,
487
+ children: formatMessage({
488
+ id: 'app.components.Button.cancel',
489
+ defaultMessage: 'Cancel'
490
+ })
491
+ })
492
+ }),
493
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Action, {
494
+ children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
495
+ variant: "secondary",
496
+ onClick: handleContinue,
497
+ fullWidth: true,
498
+ children: formatMessage({
499
+ id: translations.getTranslationKey('asset-details.replace.continue'),
500
+ defaultMessage: 'Continue'
501
+ })
502
+ })
503
+ })
504
+ ]
505
+ })
506
+ ]
507
+ })
508
+ })
509
+ ]
510
+ });
511
+ };
512
+ const AssetDetails = ({ asset, closeDetails })=>{
193
513
  const { formatMessage, formatDate } = reactIntl.useIntl();
194
- const { toggleNotification } = strapiAdmin.useNotification();
195
514
  const { data: folders$1 = [] } = folders.useGetAllFoldersQuery();
515
+ const { toggleNotification } = strapiAdmin.useNotification();
196
516
  const [updateAsset] = assets.useUpdateAssetMutation();
517
+ const [replaceMutation, { isLoading: isReplacing }] = assets.useReplaceAssetMutation();
518
+ const [deleteMutation, { isLoading: isDeleting }] = assets.useDeleteAssetMutation();
519
+ // In-drawer toast slot
520
+ const [drawerToast, setDrawerToast] = React__namespace.useState(null);
521
+ React__namespace.useEffect(()=>{
522
+ if (!drawerToast) return;
523
+ const timer = window.setTimeout(()=>setDrawerToast(null), 5000);
524
+ return ()=>window.clearTimeout(timer);
525
+ }, [
526
+ drawerToast
527
+ ]);
528
+ // Local alias matching the DrawerNotifyContext signature, so the drawer's
529
+ // own handlers (replace, update) read like the consumers do.
530
+ const notify = React__namespace.useCallback((toast)=>setDrawerToast(toast), []);
197
531
  const isImage = asset.mime?.includes(enums.AssetType.Image);
198
532
  const initialValues = {
199
533
  name: asset.name ?? '',
@@ -212,7 +546,7 @@ const AssetDetails = ({ asset })=>{
212
546
  }
213
547
  });
214
548
  if ('error' in res) {
215
- toggleNotification({
549
+ notify({
216
550
  type: 'danger',
217
551
  message: formatMessage({
218
552
  id: translations.getTranslationKey('asset-details.update.error'),
@@ -221,7 +555,7 @@ const AssetDetails = ({ asset })=>{
221
555
  });
222
556
  return;
223
557
  }
224
- toggleNotification({
558
+ notify({
225
559
  type: 'success',
226
560
  message: formatMessage({
227
561
  id: translations.getTranslationKey('asset-details.update.success'),
@@ -229,168 +563,292 @@ const AssetDetails = ({ asset })=>{
229
563
  })
230
564
  });
231
565
  };
566
+ const { title: folderName } = useFolderInfo.useFolderInfo(typeof asset.folder === 'object' && asset.folder !== null ? asset.folder.id ?? null : asset.folder ?? null);
567
+ // Owns the replace upload so isReplacing can drive the busy overlay.
568
+ const handleReplace = async (file)=>{
569
+ const res = await replaceMutation({
570
+ id: asset.id,
571
+ file
572
+ });
573
+ if ('error' in res) {
574
+ const error = res.error;
575
+ const message = error?.data?.error?.message ?? error?.data?.message ?? formatMessage({
576
+ id: translations.getTranslationKey('asset-details.replace.error'),
577
+ defaultMessage: 'Failed to replace the file.'
578
+ });
579
+ notify({
580
+ type: 'danger',
581
+ message
582
+ });
583
+ return;
584
+ }
585
+ notify({
586
+ type: 'success',
587
+ message: formatMessage({
588
+ id: translations.getTranslationKey('asset-details.replace.success'),
589
+ defaultMessage: 'File replaced.'
590
+ })
591
+ });
592
+ };
593
+ // Owns the delete: on error notify in-drawer (drawer stays), on success fire
594
+ // a persistent global notification then close the drawer.
595
+ const handleDelete = async ()=>{
596
+ const res = await deleteMutation(asset.id);
597
+ if ('error' in res) {
598
+ const error = res.error;
599
+ const message = error?.data?.error?.message ?? error?.data?.message ?? formatMessage({
600
+ id: translations.getTranslationKey('asset-details.delete.error'),
601
+ defaultMessage: 'Failed to delete the asset.'
602
+ });
603
+ notify({
604
+ type: 'danger',
605
+ message
606
+ });
607
+ return;
608
+ }
609
+ toggleNotification({
610
+ type: 'success',
611
+ message: formatMessage({
612
+ id: translations.getTranslationKey('asset-details.delete.success'),
613
+ defaultMessage: '1 element have been deleted from {folderName}'
614
+ }, {
615
+ folderName
616
+ })
617
+ });
618
+ closeDetails();
619
+ };
620
+ const operations = React__namespace.useMemo(()=>({
621
+ replaceAsset: handleReplace,
622
+ deleteAsset: handleDelete,
623
+ isReplacing,
624
+ isDeleting
625
+ }), // handleReplace / handleDelete close over asset+mutations and don't need a
626
+ // stable identity here; the consumers re-render with the new context value.
627
+ // eslint-disable-next-line react-hooks/exhaustive-deps
628
+ [
629
+ isReplacing,
630
+ isDeleting
631
+ ]);
232
632
  return(// `key={asset.id}` resets the form when the drawer switches to a different
233
633
  // asset so cached values from the previous asset don't bleed in.
234
- /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Form, {
235
- method: "POST",
236
- initialValues: initialValues,
237
- onSubmit: handleSubmit,
238
- children: ({ modified, isSubmitting, values, resetForm })=>{
239
- const nameIsEmpty = (values.name ?? '').trim() === '';
240
- return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
241
- children: [
242
- /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Blocker, {
243
- onProceed: resetForm
244
- }),
245
- /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
246
- direction: "column",
247
- alignItems: "stretch",
248
- gap: 4,
249
- paddingTop: 4,
250
- paddingBottom: 4,
251
- paddingLeft: 5,
252
- paddingRight: 5,
253
- children: [
254
- /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
255
- variant: "beta",
256
- fontWeight: "semiBold",
257
- tag: "h3",
258
- children: formatMessage({
259
- id: translations.getTranslationKey('asset-details.fileInfo'),
260
- defaultMessage: 'File info'
261
- })
262
- }),
263
- /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
264
- wrap: "wrap",
265
- gap: 4,
266
- background: "neutral100",
267
- paddingTop: 4,
268
- paddingBottom: 4,
269
- paddingLeft: 6,
270
- paddingRight: 6,
271
- alignItems: "flex-start",
272
- children: [
273
- /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
274
- label: formatMessage({
275
- id: translations.getTranslationKey('asset-details.creationDate'),
276
- defaultMessage: 'Creation date'
277
- }),
278
- value: asset.createdAt ? formatDate(new Date(asset.createdAt), {
279
- dateStyle: 'long',
280
- timeStyle: 'short'
281
- }) : null
282
- }),
283
- /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
284
- label: formatMessage({
285
- id: translations.getTranslationKey('asset-details.lastUpdated'),
286
- defaultMessage: 'Last updated'
287
- }),
288
- value: asset.updatedAt ? formatDate(new Date(asset.updatedAt), {
289
- dateStyle: 'long',
290
- timeStyle: 'short'
291
- }) : null
292
- }),
293
- /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
294
- label: formatMessage({
295
- id: translations.getTranslationKey('asset-details.createdBy'),
296
- defaultMessage: 'Created by'
297
- }),
298
- value: asset.createdBy ? strapiAdmin.getDisplayName({
299
- firstname: asset.createdBy.firstname ?? undefined,
300
- lastname: asset.createdBy.lastname ?? undefined,
301
- username: asset.createdBy.username ?? undefined,
302
- email: asset.createdBy.email ?? undefined
303
- }) ?? '-' : null
304
- }),
305
- /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
306
- label: formatMessage({
307
- id: translations.getTranslationKey('asset-details.size'),
308
- defaultMessage: 'Size'
309
- }),
310
- value: asset.size ? files.formatBytes(asset.size, 1) : null
311
- }),
312
- isImage && (asset.width != null || asset.height != null) && /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
313
- label: formatMessage({
314
- id: translations.getTranslationKey('asset-details.dimensions'),
315
- defaultMessage: 'Dimensions'
316
- }),
317
- value: asset.width != null && asset.height != null ? `${asset.width} × ${asset.height}` : null
318
- }),
319
- /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
320
- label: formatMessage({
321
- id: translations.getTranslationKey('asset-details.extension'),
322
- defaultMessage: 'Extension'
323
- }),
324
- value: files.getFileExtension(asset.ext)
325
- }),
326
- /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
327
- label: formatMessage({
328
- id: translations.getTranslationKey('asset-details.assetId'),
329
- defaultMessage: 'Asset ID'
330
- }),
331
- value: String(asset.id)
332
- })
333
- ]
334
- }),
335
- /*#__PURE__*/ jsxRuntime.jsx(DetailField, {
336
- name: "name",
337
- label: formatMessage({
338
- id: translations.getTranslationKey('asset-details.fileName'),
339
- defaultMessage: 'File name'
340
- }),
341
- required: true
342
- }),
343
- /*#__PURE__*/ jsxRuntime.jsx(LocationField, {
344
- label: formatMessage({
345
- id: translations.getTranslationKey('asset-details.location'),
346
- defaultMessage: 'Location'
347
- }),
348
- rootLabel: formatMessage({
349
- id: translations.getTranslationKey('plugin.home'),
350
- defaultMessage: 'Home'
634
+ /*#__PURE__*/ jsxRuntime.jsx(DrawerNotifyContext.Provider, {
635
+ value: notify,
636
+ children: /*#__PURE__*/ jsxRuntime.jsx(AssetOperationsContext.Provider, {
637
+ value: operations,
638
+ children: /*#__PURE__*/ jsxRuntime.jsx(FormShell, {
639
+ children: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Form, {
640
+ method: "POST",
641
+ initialValues: initialValues,
642
+ onSubmit: handleSubmit,
643
+ children: ({ modified, isSubmitting, values, resetForm })=>{
644
+ const nameIsEmpty = (values.name ?? '').trim() === '';
645
+ return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
646
+ children: [
647
+ /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Blocker, {
648
+ onProceed: resetForm
351
649
  }),
352
- folders: folders$1
353
- }),
354
- isImage && /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
355
- children: [
356
- /*#__PURE__*/ jsxRuntime.jsx(DetailField, {
357
- name: "caption",
358
- label: formatMessage({
359
- id: translations.getTranslationKey('asset-details.caption'),
360
- defaultMessage: 'Caption'
361
- })
362
- }),
363
- /*#__PURE__*/ jsxRuntime.jsx(DetailField, {
364
- name: "alternativeText",
365
- label: formatMessage({
366
- id: translations.getTranslationKey('asset-details.alternativeText'),
367
- defaultMessage: 'Alternative text'
650
+ isReplacing || isDeleting ? /*#__PURE__*/ jsxRuntime.jsx(DrawerBusyOverlay, {
651
+ children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Loader, {
652
+ children: formatMessage({
653
+ id: translations.getTranslationKey(isDeleting ? 'asset-details.delete.loading' : 'asset-details.replace.loading'),
654
+ defaultMessage: isDeleting ? 'Deleting the file…' : 'Replacing the file…'
368
655
  })
369
656
  })
370
- ]
371
- }),
372
- /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
373
- justifyContent: "flex-end",
374
- gap: 2,
375
- paddingTop: 2,
376
- children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
377
- type: "submit",
378
- variant: "default",
379
- loading: isSubmitting,
380
- // File name is required; block submit when it's empty or whitespace so the API can't 400 on a blank value.
381
- disabled: !modified || isSubmitting || nameIsEmpty,
382
- children: formatMessage({
383
- id: translations.getTranslationKey('asset-details.save'),
384
- defaultMessage: 'Save changes'
657
+ }) : null,
658
+ drawerToast ? /*#__PURE__*/ jsxRuntime.jsx(DrawerToastSlot, {
659
+ children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Alert, {
660
+ variant: drawerToast.type === 'success' ? 'success' : 'danger',
661
+ closeLabel: formatMessage({
662
+ id: 'global.close',
663
+ defaultMessage: 'Close'
664
+ }),
665
+ onClose: ()=>setDrawerToast(null),
666
+ children: drawerToast.message
385
667
  })
668
+ }) : null,
669
+ /*#__PURE__*/ jsxRuntime.jsxs(Drawer.Drawer.ScrollableContent, {
670
+ children: [
671
+ /*#__PURE__*/ jsxRuntime.jsx(AssetPreview.AssetPreview, {
672
+ asset: asset,
673
+ actions: isImage ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
674
+ direction: "column",
675
+ gap: 2,
676
+ children: /*#__PURE__*/ jsxRuntime.jsx(ReplaceAssetButton, {})
677
+ }) : null
678
+ }),
679
+ /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
680
+ direction: "column",
681
+ alignItems: "stretch",
682
+ gap: 4,
683
+ paddingTop: 4,
684
+ paddingBottom: 4,
685
+ paddingLeft: 5,
686
+ paddingRight: 5,
687
+ children: [
688
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
689
+ variant: "beta",
690
+ fontWeight: "semiBold",
691
+ tag: "h3",
692
+ children: formatMessage({
693
+ id: translations.getTranslationKey('asset-details.fileInfo'),
694
+ defaultMessage: 'File info'
695
+ })
696
+ }),
697
+ /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
698
+ wrap: "wrap",
699
+ gap: 4,
700
+ background: "neutral100",
701
+ paddingTop: 4,
702
+ paddingBottom: 4,
703
+ paddingLeft: 6,
704
+ paddingRight: 6,
705
+ alignItems: "flex-start",
706
+ children: [
707
+ /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
708
+ label: formatMessage({
709
+ id: translations.getTranslationKey('asset-details.creationDate'),
710
+ defaultMessage: 'Creation date'
711
+ }),
712
+ value: asset.createdAt ? formatDate(new Date(asset.createdAt), {
713
+ dateStyle: 'long',
714
+ timeStyle: 'short'
715
+ }) : null
716
+ }),
717
+ /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
718
+ label: formatMessage({
719
+ id: translations.getTranslationKey('asset-details.lastUpdated'),
720
+ defaultMessage: 'Last updated'
721
+ }),
722
+ value: asset.updatedAt ? formatDate(new Date(asset.updatedAt), {
723
+ dateStyle: 'long',
724
+ timeStyle: 'short'
725
+ }) : null
726
+ }),
727
+ /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
728
+ label: formatMessage({
729
+ id: translations.getTranslationKey('asset-details.createdBy'),
730
+ defaultMessage: 'Created by'
731
+ }),
732
+ value: asset.createdBy ? strapiAdmin.getDisplayName({
733
+ firstname: asset.createdBy.firstname ?? undefined,
734
+ lastname: asset.createdBy.lastname ?? undefined,
735
+ username: asset.createdBy.username ?? undefined,
736
+ email: asset.createdBy.email ?? undefined
737
+ }) ?? '-' : null
738
+ }),
739
+ /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
740
+ label: formatMessage({
741
+ id: translations.getTranslationKey('asset-details.size'),
742
+ defaultMessage: 'Size'
743
+ }),
744
+ value: asset.size ? files.formatBytes(asset.size, 1) : null
745
+ }),
746
+ isImage && (asset.width != null || asset.height != null) && /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
747
+ label: formatMessage({
748
+ id: translations.getTranslationKey('asset-details.dimensions'),
749
+ defaultMessage: 'Dimensions'
750
+ }),
751
+ value: asset.width != null && asset.height != null ? `${asset.width} × ${asset.height}` : null
752
+ }),
753
+ /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
754
+ label: formatMessage({
755
+ id: translations.getTranslationKey('asset-details.extension'),
756
+ defaultMessage: 'Extension'
757
+ }),
758
+ value: files.getFileExtension(asset.ext)
759
+ }),
760
+ /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
761
+ label: formatMessage({
762
+ id: translations.getTranslationKey('asset-details.assetId'),
763
+ defaultMessage: 'Asset ID'
764
+ }),
765
+ value: String(asset.id)
766
+ })
767
+ ]
768
+ }),
769
+ /*#__PURE__*/ jsxRuntime.jsx(DetailField, {
770
+ name: "name",
771
+ label: formatMessage({
772
+ id: translations.getTranslationKey('asset-details.fileName'),
773
+ defaultMessage: 'File name'
774
+ }),
775
+ required: true
776
+ }),
777
+ /*#__PURE__*/ jsxRuntime.jsx(LocationField, {
778
+ label: formatMessage({
779
+ id: translations.getTranslationKey('asset-details.location'),
780
+ defaultMessage: 'Location'
781
+ }),
782
+ rootLabel: formatMessage({
783
+ id: translations.getTranslationKey('plugin.home'),
784
+ defaultMessage: 'Home'
785
+ }),
786
+ folders: folders$1
787
+ }),
788
+ isImage && /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
789
+ children: [
790
+ /*#__PURE__*/ jsxRuntime.jsx(DetailField, {
791
+ name: "caption",
792
+ label: formatMessage({
793
+ id: translations.getTranslationKey('asset-details.caption'),
794
+ defaultMessage: 'Caption'
795
+ })
796
+ }),
797
+ /*#__PURE__*/ jsxRuntime.jsx(DetailField, {
798
+ name: "alternativeText",
799
+ label: formatMessage({
800
+ id: translations.getTranslationKey('asset-details.alternativeText'),
801
+ defaultMessage: 'Alternative text'
802
+ })
803
+ })
804
+ ]
805
+ })
806
+ ]
807
+ })
808
+ ]
809
+ }),
810
+ /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
811
+ justifyContent: "space-between",
812
+ alignItems: "center",
813
+ gap: 2,
814
+ padding: 3,
815
+ borderColor: "neutral150",
816
+ borderStyle: "solid",
817
+ borderWidth: "1px 0 0 0",
818
+ background: "neutral0",
819
+ children: [
820
+ /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
821
+ gap: 2,
822
+ children: [
823
+ /*#__PURE__*/ jsxRuntime.jsx(DeleteAssetButton, {}),
824
+ /*#__PURE__*/ jsxRuntime.jsx(CopyLinkButton, {
825
+ asset: asset
826
+ }),
827
+ /*#__PURE__*/ jsxRuntime.jsx(DownloadAssetButton, {
828
+ asset: asset
829
+ })
830
+ ]
831
+ }),
832
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
833
+ type: "submit",
834
+ variant: "default",
835
+ loading: isSubmitting,
836
+ // File name is required; block submit when it's empty or whitespace so the API can't 400 on a blank value.
837
+ disabled: !modified || isSubmitting || nameIsEmpty,
838
+ children: formatMessage({
839
+ id: translations.getTranslationKey('asset-details.save'),
840
+ defaultMessage: 'Save changes'
841
+ })
842
+ })
843
+ ]
386
844
  })
387
- })
388
- ]
389
- })
390
- ]
391
- });
392
- }
393
- }, asset.id));
845
+ ]
846
+ });
847
+ }
848
+ }, asset.id)
849
+ })
850
+ })
851
+ }));
394
852
  };
395
853
  const DrawerHeader = ({ asset, closeDetails })=>{
396
854
  const DocIcon = asset ? getAssetIcon.getAssetIcon(asset.mime, asset.ext) : icons.FileError;
@@ -400,6 +858,9 @@ const DrawerHeader = ({ asset, closeDetails })=>{
400
858
  paddingTop: 3,
401
859
  paddingBottom: 3,
402
860
  paddingRight: 3,
861
+ borderColor: "neutral150",
862
+ borderStyle: "solid",
863
+ borderWidth: "0 0 1px 0",
403
864
  children: [
404
865
  /*#__PURE__*/ jsxRuntime.jsx(DocIcon, {
405
866
  width: 20,
@@ -471,15 +932,9 @@ const DrawerContent = ({ assetId, closeDetails })=>{
471
932
  asset: asset,
472
933
  closeDetails: closeDetails
473
934
  }),
474
- /*#__PURE__*/ jsxRuntime.jsxs(Drawer.Drawer.ScrollableContent, {
475
- children: [
476
- /*#__PURE__*/ jsxRuntime.jsx(AssetPreview.AssetPreview, {
477
- asset: asset
478
- }),
479
- /*#__PURE__*/ jsxRuntime.jsx(AssetDetails, {
480
- asset: asset
481
- })
482
- ]
935
+ /*#__PURE__*/ jsxRuntime.jsx(AssetDetails, {
936
+ asset: asset,
937
+ closeDetails: closeDetails
483
938
  })
484
939
  ]
485
940
  });
@@ -530,5 +985,7 @@ const DrawerContent = ({ assetId, closeDetails })=>{
530
985
 
531
986
  exports.AssetDetails = AssetDetails;
532
987
  exports.AssetDetailsDrawer = AssetDetailsDrawer;
988
+ exports.AssetOperationsContext = AssetOperationsContext;
989
+ exports.DrawerNotifyContext = DrawerNotifyContext;
533
990
  exports.useAssetDetailsParam = useAssetDetailsParam;
534
991
  //# sourceMappingURL=AssetDetailsDrawer.js.map