@strapi/upload 5.47.0 → 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 (126) 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 -2
  10. package/dist/admin/future/components/Drawer.js.map +1 -1
  11. package/dist/admin/future/components/Drawer.mjs +7 -2
  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 +733 -148
  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 +737 -155
  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 +88 -1
  34. package/dist/admin/future/services/assets.js.map +1 -1
  35. package/dist/admin/future/services/assets.mjs +86 -2
  36. package/dist/admin/future/services/assets.mjs.map +1 -1
  37. package/dist/admin/future/services/folders.js +33 -1
  38. package/dist/admin/future/services/folders.js.map +1 -1
  39. package/dist/admin/future/services/folders.mjs +33 -2
  40. package/dist/admin/future/services/folders.mjs.map +1 -1
  41. package/dist/admin/future/services/settings.js +18 -0
  42. package/dist/admin/future/services/settings.js.map +1 -0
  43. package/dist/admin/future/services/settings.mjs +16 -0
  44. package/dist/admin/future/services/settings.mjs.map +1 -0
  45. package/dist/admin/future/services/uploadFileViaXHR.js +92 -0
  46. package/dist/admin/future/services/uploadFileViaXHR.js.map +1 -0
  47. package/dist/admin/future/services/uploadFileViaXHR.mjs +88 -0
  48. package/dist/admin/future/services/uploadFileViaXHR.mjs.map +1 -0
  49. package/dist/admin/future/store/uploadProgress.js +32 -26
  50. package/dist/admin/future/store/uploadProgress.js.map +1 -1
  51. package/dist/admin/future/store/uploadProgress.mjs +32 -27
  52. package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
  53. package/dist/admin/future/utils/createRafBatcher.js +42 -0
  54. package/dist/admin/future/utils/createRafBatcher.js.map +1 -0
  55. package/dist/admin/future/utils/createRafBatcher.mjs +40 -0
  56. package/dist/admin/future/utils/createRafBatcher.mjs.map +1 -0
  57. package/dist/admin/future/utils/downloadFile.js +19 -0
  58. package/dist/admin/future/utils/downloadFile.js.map +1 -0
  59. package/dist/admin/future/utils/downloadFile.mjs +17 -0
  60. package/dist/admin/future/utils/downloadFile.mjs.map +1 -0
  61. package/dist/admin/hooks/useAssets.js +5 -3
  62. package/dist/admin/hooks/useAssets.js.map +1 -1
  63. package/dist/admin/hooks/useAssets.mjs +5 -3
  64. package/dist/admin/hooks/useAssets.mjs.map +1 -1
  65. package/dist/admin/index.js +1 -1
  66. package/dist/admin/index.mjs +1 -1
  67. package/dist/admin/src/components/EditAssetDialog/EditAssetContent.d.ts +2 -1
  68. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.d.ts +22 -0
  69. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetPreview.d.ts +4 -1
  70. package/dist/admin/src/future/services/api.d.ts +9 -8
  71. package/dist/admin/src/future/services/assets.d.ts +11 -2
  72. package/dist/admin/src/future/services/folders.d.ts +1 -1
  73. package/dist/admin/src/future/services/uploadFileViaXHR.d.ts +34 -0
  74. package/dist/admin/src/future/store/uploadProgress.d.ts +17 -4
  75. package/dist/admin/src/future/utils/createRafBatcher.d.ts +23 -0
  76. package/dist/admin/src/future/utils/downloadFile.d.ts +6 -0
  77. package/dist/admin/translations/{dk.json.js → da.json.js} +3 -3
  78. package/dist/admin/translations/{dk.json.js.map → da.json.js.map} +1 -1
  79. package/dist/admin/translations/{dk.json.mjs → da.json.mjs} +3 -3
  80. package/dist/admin/translations/{dk.json.mjs.map → da.json.mjs.map} +1 -1
  81. package/dist/admin/translations/en.json.js +26 -1
  82. package/dist/admin/translations/en.json.js.map +1 -1
  83. package/dist/admin/translations/en.json.mjs +26 -1
  84. package/dist/admin/translations/en.json.mjs.map +1 -1
  85. package/dist/server/bootstrap.js +0 -3
  86. package/dist/server/bootstrap.js.map +1 -1
  87. package/dist/server/bootstrap.mjs +0 -3
  88. package/dist/server/bootstrap.mjs.map +1 -1
  89. package/dist/server/controllers/admin-upload.js +69 -118
  90. package/dist/server/controllers/admin-upload.js.map +1 -1
  91. package/dist/server/controllers/admin-upload.mjs +69 -118
  92. package/dist/server/controllers/admin-upload.mjs.map +1 -1
  93. package/dist/server/routes/admin.js +2 -2
  94. package/dist/server/routes/admin.js.map +1 -1
  95. package/dist/server/routes/admin.mjs +2 -2
  96. package/dist/server/routes/admin.mjs.map +1 -1
  97. package/dist/server/services/ai-metadata-jobs.js +0 -23
  98. package/dist/server/services/ai-metadata-jobs.js.map +1 -1
  99. package/dist/server/services/ai-metadata-jobs.mjs +0 -23
  100. package/dist/server/services/ai-metadata-jobs.mjs.map +1 -1
  101. package/dist/server/services/image-manipulation.js +16 -8
  102. package/dist/server/services/image-manipulation.js.map +1 -1
  103. package/dist/server/services/image-manipulation.mjs +16 -8
  104. package/dist/server/services/image-manipulation.mjs.map +1 -1
  105. package/dist/server/services/upload.js +1 -1
  106. package/dist/server/services/upload.js.map +1 -1
  107. package/dist/server/services/upload.mjs +1 -1
  108. package/dist/server/services/upload.mjs.map +1 -1
  109. package/dist/server/src/bootstrap.d.ts.map +1 -1
  110. package/dist/server/src/controllers/admin-upload.d.ts +6 -8
  111. package/dist/server/src/controllers/admin-upload.d.ts.map +1 -1
  112. package/dist/server/src/controllers/index.d.ts +1 -1
  113. package/dist/server/src/index.d.ts +1 -2
  114. package/dist/server/src/index.d.ts.map +1 -1
  115. package/dist/server/src/services/ai-metadata-jobs.d.ts +0 -1
  116. package/dist/server/src/services/ai-metadata-jobs.d.ts.map +1 -1
  117. package/dist/server/src/services/image-manipulation.d.ts +5 -0
  118. package/dist/server/src/services/image-manipulation.d.ts.map +1 -1
  119. package/dist/server/src/services/index.d.ts +0 -1
  120. package/dist/server/src/services/index.d.ts.map +1 -1
  121. package/dist/server/src/services/upload.d.ts.map +1 -1
  122. package/dist/server/src/types.d.ts +2 -2
  123. package/dist/server/src/types.d.ts.map +1 -1
  124. package/dist/shared/contracts/files.d.ts +19 -2
  125. package/dist/shared/contracts/files.d.ts.map +1 -1
  126. package/package.json +8 -8
@@ -10,9 +10,13 @@ var styledComponents = require('styled-components');
10
10
  var Drawer = require('../../../../components/Drawer.js');
11
11
  var enums = require('../../../../enums.js');
12
12
  var assets = require('../../../../services/assets.js');
13
+ var folders = require('../../../../services/folders.js');
14
+ var settings = require('../../../../services/settings.js');
15
+ var downloadFile = require('../../../../utils/downloadFile.js');
13
16
  var files = require('../../../../utils/files.js');
14
17
  var getAssetIcon = require('../../../../utils/getAssetIcon.js');
15
18
  var translations = require('../../../../utils/translations.js');
19
+ var useFolderInfo = require('../../hooks/useFolderInfo.js');
16
20
  var AssetPreview = require('./AssetPreview.js');
17
21
 
18
22
  function _interopNamespaceDefault(e) {
@@ -36,6 +40,22 @@ var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
36
40
 
37
41
  // Name of the parameter to look for in the URL to open the drawer
38
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
+ };
39
59
  /* -------------------------------------------------------------------------------------------------
40
60
  * useAssetDetailsParam - sync drawer visibility with URL ?{URL_PARAM}={id}
41
61
  * -----------------------------------------------------------------------------------------------*/ const useAssetDetailsParam = ()=>{
@@ -43,51 +63,49 @@ const URL_PARAM = 'assetId';
43
63
  const detailsId = query?.[URL_PARAM];
44
64
  const assetId = detailsId ? parseInt(detailsId, 10) : null;
45
65
  const hasValidId = assetId !== null && !Number.isNaN(assetId);
46
- const [isClosing, setIsClosing] = React__namespace.useState(false);
66
+ // Closing is driven by removing the URL param (a navigation), so navigation
67
+ // guards like <Blocker> can intercept it. `isMounted` keeps the drawer in the
68
+ // tree through the slide-out: it stays true once opened and only flips false
69
+ // when the close animation actually ends (see onCloseAnimationEnd), so the
70
+ // close duration lives entirely in CSS — no JS timer.
71
+ const [isMounted, setIsMounted] = React__namespace.useState(hasValidId);
47
72
  const displayAssetId = React__namespace.useRef(null);
48
- const isVisible = hasValidId && !isClosing;
49
73
  React__namespace.useEffect(()=>{
50
74
  if (hasValidId) {
51
75
  displayAssetId.current = assetId;
76
+ setIsMounted(true);
52
77
  }
53
78
  }, [
54
79
  hasValidId,
55
80
  assetId
56
81
  ]);
82
+ const onCloseAnimationEnd = React__namespace.useCallback((event)=>{
83
+ // Ignore animations bubbling up from descendants, and the slide-in.
84
+ if (event.target === event.currentTarget && !hasValidId) {
85
+ setIsMounted(false);
86
+ }
87
+ }, [
88
+ hasValidId
89
+ ]);
57
90
  const openDetails = React__namespace.useCallback((id)=>{
58
- setIsClosing(false);
59
91
  setQuery({
60
92
  [URL_PARAM]: String(id)
61
- });
93
+ }, 'push', true);
62
94
  }, [
63
95
  setQuery
64
96
  ]);
65
97
  const closeDetails = React__namespace.useCallback(()=>{
66
- if (!hasValidId) return;
67
- setIsClosing(true);
68
- }, [
69
- hasValidId
70
- ]);
71
- React__namespace.useEffect(()=>{
72
- if (!isClosing) return;
73
- const timer = window.setTimeout(()=>{
74
- setQuery({
75
- [URL_PARAM]: undefined
76
- }, 'remove');
77
- setIsClosing(false);
78
- displayAssetId.current = null;
79
- }, Drawer.DRAWER_CLOSE_ANIMATION_MS);
80
- return ()=>window.clearTimeout(timer);
98
+ setQuery({
99
+ [URL_PARAM]: undefined
100
+ }, 'remove', true);
81
101
  }, [
82
- isClosing,
83
102
  setQuery
84
103
  ]);
85
- const shouldRenderDrawer = hasValidId || isClosing;
86
- const drawerAssetId = isClosing ? displayAssetId.current ?? assetId : assetId;
87
104
  return {
88
- assetId: drawerAssetId,
89
- isVisible,
90
- shouldRenderDrawer,
105
+ assetId: hasValidId ? assetId : displayAssetId.current,
106
+ isVisible: hasValidId,
107
+ shouldRenderDrawer: isMounted,
108
+ onCloseAnimationEnd,
91
109
  openDetails,
92
110
  closeDetails
93
111
  };
@@ -117,7 +135,50 @@ const DetailItem = ({ label, value })=>/*#__PURE__*/ jsxRuntime.jsxs(DetailItemC
117
135
  });
118
136
  /* -------------------------------------------------------------------------------------------------
119
137
  * DetailField
120
- * -----------------------------------------------------------------------------------------------*/ 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)`
121
182
  width: 1.6rem;
122
183
  height: 1.6rem;
123
184
 
@@ -125,7 +186,18 @@ const DetailItem = ({ label, value })=>/*#__PURE__*/ jsxRuntime.jsxs(DetailItemC
125
186
  fill: ${({ theme })=>theme.colors.warning500};
126
187
  }
127
188
  `;
128
- const DetailField = ({ name, label, value, required })=>/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
189
+ const DetailField = ({ name, label, required })=>{
190
+ const { formatMessage } = reactIntl.useIntl();
191
+ const field = strapiAdmin.useField(name);
192
+ const isSubmitting = strapiAdmin.useForm('DetailField', (state)=>state.isSubmitting);
193
+ const value = field.value ?? '';
194
+ const emptyTooltipLabel = formatMessage({
195
+ id: translations.getTranslationKey('asset-details.field.empty'),
196
+ defaultMessage: '{label} is currently empty.'
197
+ }, {
198
+ label
199
+ });
200
+ return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
129
201
  name: name,
130
202
  required: required,
131
203
  children: [
@@ -133,139 +205,651 @@ const DetailField = ({ name, label, value, required })=>/*#__PURE__*/ jsxRuntime
133
205
  children: label
134
206
  }),
135
207
  /*#__PURE__*/ jsxRuntime.jsx(designSystem.TextInput, {
136
- value: value ?? '',
137
- // TODO: handle onChange
138
- onChange: ()=>{},
139
- endAction: !value ? /*#__PURE__*/ jsxRuntime.jsx(StyledWarning, {}) : undefined,
140
- type: "text"
208
+ value: value,
209
+ onChange: (event)=>field.onChange(name, event.target.value),
210
+ endAction: !value ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Tooltip, {
211
+ label: emptyTooltipLabel,
212
+ children: /*#__PURE__*/ jsxRuntime.jsx(StyledWarning, {
213
+ "aria-label": emptyTooltipLabel,
214
+ role: "img"
215
+ })
216
+ }) : undefined,
217
+ type: "text",
218
+ disabled: isSubmitting
141
219
  })
142
220
  ]
143
221
  });
144
- const AssetDetails = ({ asset })=>{
145
- const { formatMessage, formatDate } = reactIntl.useIntl();
146
- const isImage = asset.mime?.includes(enums.AssetType.Image);
147
- return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
148
- direction: "column",
149
- alignItems: "stretch",
150
- gap: 4,
151
- paddingTop: 4,
152
- paddingBottom: 4,
153
- paddingLeft: 5,
154
- paddingRight: 5,
222
+ };
223
+ const LocationField = ({ label, rootLabel, folders })=>{
224
+ const field = strapiAdmin.useField('folder');
225
+ const isSubmitting = strapiAdmin.useForm('LocationField', (state)=>state.isSubmitting);
226
+ return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
227
+ name: "folder",
228
+ required: true,
155
229
  children: [
156
- /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
157
- variant: "beta",
158
- fontWeight: "semiBold",
159
- tag: "h3",
160
- children: formatMessage({
161
- id: translations.getTranslationKey('asset-details.fileInfo'),
162
- defaultMessage: 'File info'
163
- })
230
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
231
+ children: label
164
232
  }),
165
- /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
166
- wrap: "wrap",
167
- gap: 4,
168
- background: "neutral100",
169
- paddingTop: 4,
170
- paddingBottom: 4,
171
- paddingLeft: 6,
172
- paddingRight: 6,
173
- alignItems: "flex-start",
233
+ /*#__PURE__*/ jsxRuntime.jsxs(designSystem.SingleSelect, {
234
+ value: field.value == null ? '' : String(field.value),
235
+ onChange: (value)=>{
236
+ const next = value === '' ? null : Number(value);
237
+ field.onChange('folder', next);
238
+ },
239
+ disabled: isSubmitting,
174
240
  children: [
175
- /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
176
- label: formatMessage({
177
- id: translations.getTranslationKey('asset-details.creationDate'),
178
- defaultMessage: 'Creation date'
179
- }),
180
- value: asset.createdAt ? formatDate(new Date(asset.createdAt), {
181
- dateStyle: 'long',
182
- timeStyle: 'short'
183
- }) : null
184
- }),
185
- /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
186
- label: formatMessage({
187
- id: translations.getTranslationKey('asset-details.lastUpdated'),
188
- defaultMessage: 'Last updated'
189
- }),
190
- value: asset.updatedAt ? formatDate(new Date(asset.updatedAt), {
191
- dateStyle: 'long',
192
- timeStyle: 'short'
193
- }) : null
194
- }),
195
- /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
196
- label: formatMessage({
197
- id: translations.getTranslationKey('asset-details.createdBy'),
198
- defaultMessage: 'Created by'
199
- }),
200
- value: asset.createdBy ? strapiAdmin.getDisplayName({
201
- firstname: asset.createdBy.firstname ?? undefined,
202
- lastname: asset.createdBy.lastname ?? undefined,
203
- username: asset.createdBy.username ?? undefined,
204
- email: asset.createdBy.email ?? undefined
205
- }) ?? '-' : null
241
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelectOption, {
242
+ value: "",
243
+ children: rootLabel
206
244
  }),
207
- /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
208
- label: formatMessage({
209
- id: translations.getTranslationKey('asset-details.size'),
210
- defaultMessage: 'Size'
211
- }),
212
- value: asset.size ? files.formatBytes(asset.size, 1) : null
213
- }),
214
- isImage && (asset.width != null || asset.height != null) && /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
215
- label: formatMessage({
216
- id: translations.getTranslationKey('asset-details.dimensions'),
217
- defaultMessage: 'Dimensions'
218
- }),
219
- value: asset.width != null && asset.height != null ? `${asset.width} × ${asset.height}` : null
245
+ folders.map((folder)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelectOption, {
246
+ value: String(folder.id),
247
+ children: folder.name
248
+ }, folder.id))
249
+ ]
250
+ })
251
+ ]
252
+ });
253
+ };
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
+ })
220
287
  }),
221
- /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
222
- label: formatMessage({
223
- id: translations.getTranslationKey('asset-details.extension'),
224
- defaultMessage: 'Extension'
288
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Body, {
289
+ icon: /*#__PURE__*/ jsxRuntime.jsx(icons.WarningCircle, {
290
+ width: "24px",
291
+ height: "24px",
292
+ fill: "danger600"
225
293
  }),
226
- value: files.getFileExtension(asset.ext)
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
+ })
227
299
  }),
228
- /*#__PURE__*/ jsxRuntime.jsx(DetailItem, {
229
- label: formatMessage({
230
- id: translations.getTranslationKey('asset-details.assetId'),
231
- defaultMessage: 'Asset ID'
232
- }),
233
- value: String(asset.id)
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
+ ]
234
326
  })
235
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
+ })
236
435
  }),
237
- /*#__PURE__*/ jsxRuntime.jsx(DetailField, {
238
- name: "fileName",
436
+ /*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
437
+ withTooltip: false,
239
438
  label: formatMessage({
240
- id: translations.getTranslationKey('asset-details.fileName'),
241
- defaultMessage: 'File name'
439
+ id: translations.getTranslationKey('asset-details.replace.trigger'),
440
+ defaultMessage: 'Replace this file'
242
441
  }),
243
- value: asset.name,
244
- required: true
442
+ variant: "tertiary",
443
+ onClick: handleTriggerClick,
444
+ disabled: isReplacing,
445
+ children: /*#__PURE__*/ jsxRuntime.jsx(icons.ArrowsCounterClockwise, {})
245
446
  }),
246
- isImage && /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
247
- children: [
248
- /*#__PURE__*/ jsxRuntime.jsx(DetailField, {
249
- name: "caption",
250
- label: formatMessage({
251
- id: translations.getTranslationKey('asset-details.caption'),
252
- defaultMessage: 'Caption'
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
+ })
253
457
  }),
254
- value: asset.caption
255
- }),
256
- /*#__PURE__*/ jsxRuntime.jsx(DetailField, {
257
- name: "alternativeText",
258
- label: formatMessage({
259
- id: translations.getTranslationKey('asset-details.alternativeText'),
260
- defaultMessage: 'Alternative text'
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
+ })
261
480
  }),
262
- value: asset.alternativeText
263
- })
264
- ]
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
+ })
265
508
  })
266
509
  ]
267
510
  });
268
511
  };
512
+ const AssetDetails = ({ asset, closeDetails })=>{
513
+ const { formatMessage, formatDate } = reactIntl.useIntl();
514
+ const { data: folders$1 = [] } = folders.useGetAllFoldersQuery();
515
+ const { toggleNotification } = strapiAdmin.useNotification();
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), []);
531
+ const isImage = asset.mime?.includes(enums.AssetType.Image);
532
+ const initialValues = {
533
+ name: asset.name ?? '',
534
+ caption: asset.caption ?? '',
535
+ alternativeText: asset.alternativeText ?? '',
536
+ folder: typeof asset.folder === 'object' && asset.folder !== null ? asset.folder.id ?? null : asset.folder ?? null
537
+ };
538
+ const handleSubmit = async (values)=>{
539
+ const res = await updateAsset({
540
+ id: asset.id,
541
+ fileInfo: {
542
+ name: values.name,
543
+ caption: values.caption,
544
+ alternativeText: values.alternativeText,
545
+ folder: values.folder
546
+ }
547
+ });
548
+ if ('error' in res) {
549
+ notify({
550
+ type: 'danger',
551
+ message: formatMessage({
552
+ id: translations.getTranslationKey('asset-details.update.error'),
553
+ defaultMessage: 'Failed to update the file.'
554
+ })
555
+ });
556
+ return;
557
+ }
558
+ notify({
559
+ type: 'success',
560
+ message: formatMessage({
561
+ id: translations.getTranslationKey('asset-details.update.success'),
562
+ defaultMessage: 'File updated'
563
+ })
564
+ });
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
+ ]);
632
+ return(// `key={asset.id}` resets the form when the drawer switches to a different
633
+ // asset so cached values from the previous asset don't bleed in.
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
649
+ }),
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…'
655
+ })
656
+ })
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
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
+ ]
844
+ })
845
+ ]
846
+ });
847
+ }
848
+ }, asset.id)
849
+ })
850
+ })
851
+ }));
852
+ };
269
853
  const DrawerHeader = ({ asset, closeDetails })=>{
270
854
  const DocIcon = asset ? getAssetIcon.getAssetIcon(asset.mime, asset.ext) : icons.FileError;
271
855
  return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
@@ -274,6 +858,9 @@ const DrawerHeader = ({ asset, closeDetails })=>{
274
858
  paddingTop: 3,
275
859
  paddingBottom: 3,
276
860
  paddingRight: 3,
861
+ borderColor: "neutral150",
862
+ borderStyle: "solid",
863
+ borderWidth: "0 0 1px 0",
277
864
  children: [
278
865
  /*#__PURE__*/ jsxRuntime.jsx(DocIcon, {
279
866
  width: 20,
@@ -345,15 +932,9 @@ const DrawerContent = ({ assetId, closeDetails })=>{
345
932
  asset: asset,
346
933
  closeDetails: closeDetails
347
934
  }),
348
- /*#__PURE__*/ jsxRuntime.jsxs(Drawer.Drawer.ScrollableContent, {
349
- children: [
350
- /*#__PURE__*/ jsxRuntime.jsx(AssetPreview.AssetPreview, {
351
- asset: asset
352
- }),
353
- /*#__PURE__*/ jsxRuntime.jsx(AssetDetails, {
354
- asset: asset
355
- })
356
- ]
935
+ /*#__PURE__*/ jsxRuntime.jsx(AssetDetails, {
936
+ asset: asset,
937
+ closeDetails: closeDetails
357
938
  })
358
939
  ]
359
940
  });
@@ -362,7 +943,7 @@ const DrawerContent = ({ assetId, closeDetails })=>{
362
943
  * AssetDetailsDrawer
363
944
  * -----------------------------------------------------------------------------------------------*/ const AssetDetailsDrawer = ()=>{
364
945
  const { formatMessage } = reactIntl.useIntl();
365
- const { assetId, isVisible, shouldRenderDrawer, closeDetails } = useAssetDetailsParam();
946
+ const { assetId, isVisible, shouldRenderDrawer, onCloseAnimationEnd, closeDetails } = useAssetDetailsParam();
366
947
  if (!shouldRenderDrawer || assetId === null) {
367
948
  return null;
368
949
  }
@@ -392,6 +973,7 @@ const DrawerContent = ({ assetId, closeDetails })=>{
392
973
  animationDirection: "left",
393
974
  width: "41.6rem",
394
975
  height: "100vh",
976
+ onAnimationEnd: onCloseAnimationEnd,
395
977
  children: /*#__PURE__*/ jsxRuntime.jsx(DrawerContent, {
396
978
  assetId: assetId,
397
979
  closeDetails: closeDetails
@@ -401,6 +983,9 @@ const DrawerContent = ({ assetId, closeDetails })=>{
401
983
  });
402
984
  };
403
985
 
986
+ exports.AssetDetails = AssetDetails;
404
987
  exports.AssetDetailsDrawer = AssetDetailsDrawer;
988
+ exports.AssetOperationsContext = AssetOperationsContext;
989
+ exports.DrawerNotifyContext = DrawerNotifyContext;
405
990
  exports.useAssetDetailsParam = useAssetDetailsParam;
406
991
  //# sourceMappingURL=AssetDetailsDrawer.js.map