@aws-amplify/ui-react-storage 3.12.2 → 3.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/browser.js +1 -1
  2. package/dist/{createStorageBrowser-pzJe4BD8.js → createStorageBrowser-DaVWyJzC.js} +1884 -1068
  3. package/dist/esm/browser.mjs +6 -2
  4. package/dist/esm/components/StorageBrowser/ErrorBoundary/ErrorBoundary.mjs +9 -0
  5. package/dist/esm/components/StorageBrowser/components/ComponentsProvider.mjs +9 -0
  6. package/dist/esm/components/StorageBrowser/components/base/Table.mjs +1 -1
  7. package/dist/esm/components/StorageBrowser/components/base/preview/DownloadButton.mjs +39 -0
  8. package/dist/esm/components/StorageBrowser/components/base/preview/FileMetadata.mjs +40 -0
  9. package/dist/esm/components/StorageBrowser/components/base/preview/FilePreviewLayout.mjs +19 -0
  10. package/dist/esm/components/StorageBrowser/components/base/preview/ImagePreview.mjs +37 -0
  11. package/dist/esm/components/StorageBrowser/components/base/preview/PreviewFallback.mjs +34 -0
  12. package/dist/esm/components/StorageBrowser/components/base/preview/PreviewPlaceholder.mjs +13 -0
  13. package/dist/esm/components/StorageBrowser/components/base/preview/TextPreview.mjs +64 -0
  14. package/dist/esm/components/StorageBrowser/components/base/preview/VideoPreview.mjs +61 -0
  15. package/dist/esm/components/StorageBrowser/components/composables/DataTable/DataTable.mjs +2 -1
  16. package/dist/esm/components/StorageBrowser/components/composables/FilePreview.mjs +85 -0
  17. package/dist/esm/components/StorageBrowser/components/composables/defaults.mjs +2 -0
  18. package/dist/esm/components/StorageBrowser/components/elements/definitions.mjs +2 -2
  19. package/dist/esm/components/StorageBrowser/controls/DataTableControl.mjs +9 -0
  20. package/dist/esm/components/StorageBrowser/controls/FilePreviewControl.mjs +24 -0
  21. package/dist/esm/components/StorageBrowser/createStorageBrowser/StorageBrowserDefault.mjs +1 -0
  22. package/dist/esm/components/StorageBrowser/createStorageBrowser/createProvider.mjs +4 -2
  23. package/dist/esm/components/StorageBrowser/createStorageBrowser/createStorageBrowser.mjs +1 -0
  24. package/dist/esm/components/StorageBrowser/displayText/context.mjs +4 -0
  25. package/dist/esm/components/StorageBrowser/displayText/libraries/en/locationDetailView.mjs +28 -0
  26. package/dist/esm/components/StorageBrowser/filePreview/context.mjs +12 -0
  27. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/CopyView.mjs +9 -0
  28. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/CopyViewProvider.mjs +8 -0
  29. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/FoldersMessageControl.mjs +8 -0
  30. package/dist/esm/components/StorageBrowser/views/LocationActionView/CreateFolderView/CreateFolderView.mjs +9 -0
  31. package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/DeleteView.mjs +9 -0
  32. package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/DownloadView.mjs +9 -0
  33. package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.mjs +9 -0
  34. package/dist/esm/components/StorageBrowser/views/LocationDetailView/LocationDetailView.mjs +17 -4
  35. package/dist/esm/components/StorageBrowser/views/LocationDetailView/LocationDetailViewProvider.mjs +9 -2
  36. package/dist/esm/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/constants.mjs +69 -1
  37. package/dist/esm/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/fileIcon.mjs +11 -0
  38. package/dist/esm/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getFileRowContent.mjs +8 -5
  39. package/dist/esm/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getLocationDetailViewTableData.mjs +8 -1
  40. package/dist/esm/components/StorageBrowser/views/LocationDetailView/useLocationDetailView.mjs +76 -3
  41. package/dist/esm/components/StorageBrowser/views/LocationsView/LocationsView.mjs +9 -0
  42. package/dist/esm/components/StorageBrowser/views/LocationsView/LocationsViewProvider.mjs +8 -0
  43. package/dist/esm/components/StorageBrowser/views/context/primaryViews.mjs +1 -0
  44. package/dist/esm/components/StorageBrowser/views/hooks/useFilePreview/useFilePreview.mjs +115 -0
  45. package/dist/esm/components/StorageBrowser/views/utils/files/const.mjs +62 -0
  46. package/dist/esm/components/StorageBrowser/views/utils/files/fileName.mjs +12 -0
  47. package/dist/esm/components/StorageBrowser/views/utils/files/fileSize.mjs +23 -0
  48. package/dist/esm/components/StorageBrowser/views/utils/files/fileType.mjs +87 -0
  49. package/dist/esm/components/StorageBrowser/views/utils/files/safeGetProperties.mjs +12 -0
  50. package/dist/esm/components/StorageBrowser/views/utils/files/url.mjs +16 -0
  51. package/dist/esm/version.mjs +1 -1
  52. package/dist/index.js +1 -1
  53. package/dist/styles.css +250 -0
  54. package/dist/types/components/StorageBrowser/actions/configs/defaults.d.ts +2 -2
  55. package/dist/types/components/StorageBrowser/actions/handlers/types.d.ts +4 -0
  56. package/dist/types/components/StorageBrowser/components/base/Table.d.ts +1 -0
  57. package/dist/types/components/StorageBrowser/components/base/preview/DownloadButton.d.ts +4 -0
  58. package/dist/types/components/StorageBrowser/components/base/preview/FileMetadata.d.ts +7 -0
  59. package/dist/types/components/StorageBrowser/components/base/preview/FilePreviewLayout.d.ts +8 -0
  60. package/dist/types/components/StorageBrowser/components/base/preview/ImagePreview.d.ts +3 -0
  61. package/dist/types/components/StorageBrowser/components/base/preview/PreviewFallback.d.ts +10 -0
  62. package/dist/types/components/StorageBrowser/components/base/preview/PreviewPlaceholder.d.ts +2 -0
  63. package/dist/types/components/StorageBrowser/components/base/preview/TextPreview.d.ts +3 -0
  64. package/dist/types/components/StorageBrowser/components/base/preview/VideoPreview.d.ts +3 -0
  65. package/dist/types/components/StorageBrowser/components/base/preview/type.d.ts +6 -0
  66. package/dist/types/components/StorageBrowser/components/composables/DataTable/DataTable.d.ts +1 -0
  67. package/dist/types/components/StorageBrowser/components/composables/FilePreview.d.ts +12 -0
  68. package/dist/types/components/StorageBrowser/components/composables/types.d.ts +2 -0
  69. package/dist/types/components/StorageBrowser/controls/FilePreviewControl.d.ts +2 -0
  70. package/dist/types/components/StorageBrowser/controls/types.d.ts +11 -2
  71. package/dist/types/components/StorageBrowser/createStorageBrowser/createProvider.d.ts +2 -1
  72. package/dist/types/components/StorageBrowser/createStorageBrowser/createStorageBrowser.d.ts +4 -1
  73. package/dist/types/components/StorageBrowser/createStorageBrowser/types.d.ts +78 -2
  74. package/dist/types/components/StorageBrowser/displayText/types.d.ts +30 -1
  75. package/dist/types/components/StorageBrowser/filePreview/context.d.ts +10 -0
  76. package/dist/types/components/StorageBrowser/filePreview/index.d.ts +1 -0
  77. package/dist/types/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/constants.d.ts +6 -0
  78. package/dist/types/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/fileIcon.d.ts +2 -0
  79. package/dist/types/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getFileRowContent.d.ts +3 -1
  80. package/dist/types/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getLocationDetailViewTableData.d.ts +5 -2
  81. package/dist/types/components/StorageBrowser/views/LocationDetailView/types.d.ts +10 -0
  82. package/dist/types/components/StorageBrowser/views/hooks/useFilePreview/index.d.ts +2 -0
  83. package/dist/types/components/StorageBrowser/views/hooks/useFilePreview/types.d.ts +28 -0
  84. package/dist/types/components/StorageBrowser/views/hooks/useFilePreview/useFilePreview.d.ts +5 -0
  85. package/dist/types/components/StorageBrowser/views/utils/files/const.d.ts +9 -0
  86. package/dist/types/components/StorageBrowser/views/utils/files/fileName.d.ts +6 -0
  87. package/dist/types/components/StorageBrowser/views/utils/files/fileSize.d.ts +2 -0
  88. package/dist/types/components/StorageBrowser/views/utils/files/fileType.d.ts +12 -0
  89. package/dist/types/components/StorageBrowser/views/utils/files/safeGetProperties.d.ts +2 -0
  90. package/dist/types/components/StorageBrowser/views/utils/files/url.d.ts +2 -0
  91. package/dist/types/version.d.ts +1 -1
  92. package/package.json +6 -6
@@ -32,7 +32,7 @@ function _interopNamespace(e) {
32
32
 
33
33
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
34
34
 
35
- const VERSION = '3.12.2';
35
+ const VERSION = '3.13.1';
36
36
 
37
37
  const toAccessGrantPermission = (permission) => {
38
38
  let result = '';
@@ -784,7 +784,7 @@ const NavElement = elements.defineBaseElement({
784
784
  type: 'nav',
785
785
  displayName: 'Nav',
786
786
  });
787
- elements.defineBaseElement({
787
+ const TextElement = elements.defineBaseElement({
788
788
  type: 'p',
789
789
  displayName: 'Text',
790
790
  });
@@ -919,7 +919,7 @@ function PaginationButton({ isDisabled, onClick, type, }) {
919
919
  const Table = ({ headers, rows }) => {
920
920
  return (React__namespace["default"].createElement(TableElement, { className: `${STORAGE_BROWSER_BLOCK}__table` },
921
921
  React__namespace["default"].createElement(TableHeadElement, { className: `${STORAGE_BROWSER_BLOCK}__table-head` }, headers.length ? (React__namespace["default"].createElement(TableRowElement, { className: `${STORAGE_BROWSER_BLOCK}__table-row` }, headers.map(({ key, content }) => (React__namespace["default"].createElement(TableHeaderElement, { key: key, className: `${STORAGE_BROWSER_BLOCK}__table-header` }, content))))) : null),
922
- React__namespace["default"].createElement(TableBodyElement, { className: `${STORAGE_BROWSER_BLOCK}__table-body` }, rows?.map(({ key, content }) => (React__namespace["default"].createElement(TableRowElement, { key: key, className: `${STORAGE_BROWSER_BLOCK}__table-row` }, content.map(({ key, content, type }) => {
922
+ React__namespace["default"].createElement(TableBodyElement, { className: `${STORAGE_BROWSER_BLOCK}__table-body` }, rows?.map(({ key, content, active }) => (React__namespace["default"].createElement(TableRowElement, { key: key, className: `${STORAGE_BROWSER_BLOCK}__table-row${active ? ` ${STORAGE_BROWSER_BLOCK}__table-row_active` : ''}` }, content.map(({ key, content, type }) => {
923
923
  return type === 'header' ? (React__namespace["default"].createElement(TableHeaderElement, { key: key, className: `${STORAGE_BROWSER_BLOCK}__table-header`, role: "rowheader" }, content)) : (React__namespace["default"].createElement(TableDataCellElement, { key: key, className: `${STORAGE_BROWSER_BLOCK}__table-data-cell` }, content));
924
924
  })))))));
925
925
  };
@@ -1049,8 +1049,9 @@ const DataTable = ({ headers, isLoading, rows, }) => {
1049
1049
  });
1050
1050
  const mappedRows = isLoading
1051
1051
  ? []
1052
- : rows.map(({ key, content }) => ({
1052
+ : rows.map(({ key, content, active }) => ({
1053
1053
  key,
1054
+ active,
1054
1055
  content: content.map(({ key, content, type }) => {
1055
1056
  switch (type) {
1056
1057
  case 'button': {
@@ -1146,218 +1147,21 @@ const Navigation$1 = ({ items, }) => {
1146
1147
  return React__namespace["default"].createElement(BreadcrumbNavigation, { breadcrumbs: items });
1147
1148
  };
1148
1149
 
1149
- const OVERWRITE_TOGGLE_ID = 'overwrite-toggle';
1150
- const OverwriteToggle$1 = ({ isOverwritingEnabled, isDisabled, label, onToggle, }) => (React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__overwrite-toggle` },
1151
- React__namespace["default"].createElement(InputElement, { checked: isOverwritingEnabled, disabled: isDisabled, id: OVERWRITE_TOGGLE_ID, onChange: onToggle, type: "checkbox" }),
1152
- React__namespace["default"].createElement(LabelElement, { htmlFor: OVERWRITE_TOGGLE_ID }, label)));
1153
-
1154
- const Pagination$1 = ({ page, hasNextPage, onPaginate, highestPageVisited, }) => {
1155
- if (!page)
1156
- return null;
1157
- return (React__namespace["default"].createElement(NavElement, { "aria-label": 'Pagination', className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__pagination` },
1158
- React__namespace["default"].createElement(OrderedListElement, { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__pagination-list` },
1159
- React__namespace["default"].createElement(ListItemElement, { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__pagination-list-item` },
1160
- React__namespace["default"].createElement(PaginationButton, { isDisabled: page <= 1, onClick: () => {
1161
- if (onPaginate)
1162
- onPaginate(page - 1);
1163
- }, type: "previous" })),
1164
- React__namespace["default"].createElement(ListItemElement, { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__pagination-list-item` },
1165
- React__namespace["default"].createElement(SpanElement, { "aria-label": `Page ${page}`, "aria-current": "page", className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__pagination-current-page` }, page)),
1166
- React__namespace["default"].createElement(ListItemElement, { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__pagination-list-item` },
1167
- React__namespace["default"].createElement(PaginationButton, { isDisabled: !highestPageVisited ||
1168
- (page >= highestPageVisited && !hasNextPage), onClick: () => {
1169
- if (onPaginate)
1170
- onPaginate(page + 1);
1171
- }, type: "next" })))));
1172
- };
1173
-
1174
- const SearchField$1 = ({ id, label, clearLabel, submitLabel, onSearch, onClear, placeholder, query = '', onQueryChange, }) => {
1175
- // FIXME: focus not returning to input field after clear
1176
- return (React__namespace["default"].createElement(React__namespace["default"].Fragment, null,
1177
- React__namespace["default"].createElement(Field, { id: id, label: label, icon: React__namespace["default"].createElement(IconElement, { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__search-field-icon`, variant: "search" }), className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__search-field`, variant: "search", onChange: (e) => {
1178
- onQueryChange?.(e.target.value);
1179
- }, placeholder: placeholder, onKeyUp: (event) => {
1180
- if (event.key === 'Enter') {
1181
- onSearch?.();
1182
- }
1183
- }, value: query }, query ? (React__namespace["default"].createElement(ButtonElement, { "aria-label": clearLabel, className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__search-field-clear`, onClick: onClear, variant: "refresh" },
1184
- React__namespace["default"].createElement(IconElement, { variant: "dismiss" }))) : null),
1185
- React__namespace["default"].createElement(ButtonElement, { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__search-submit`, onClick: onSearch }, submitLabel)));
1186
- };
1187
-
1188
- const SEARCH_SUBFOLDERS_TOGGLE_ID = 'search-subfolders-toggle';
1189
- const SearchSubfoldersToggle$1 = ({ isSearchingSubfolders, label, onToggle, }) => (React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__search-subfolders-toggle` },
1190
- React__namespace["default"].createElement(InputElement, { checked: isSearchingSubfolders, id: SEARCH_SUBFOLDERS_TOGGLE_ID, onChange: onToggle, type: "checkbox" }),
1191
- React__namespace["default"].createElement(LabelElement, { htmlFor: SEARCH_SUBFOLDERS_TOGGLE_ID }, label)));
1192
-
1193
- const StatusDisplay$1 = ({ statuses, total, }) => {
1194
- if (!statuses?.length) {
1195
- return null;
1196
- }
1197
- const descriptions = statuses.map(({ name, count }) => ({
1198
- term: name,
1199
- details: `${count}/${total}`,
1200
- }));
1201
- return (React__namespace["default"].createElement(DescriptionList, { className: `${STORAGE_BROWSER_BLOCK}__status-display`, descriptions: descriptions }));
1202
- };
1203
-
1204
- const Title$1 = ({ title }) => (React__namespace["default"].createElement(HeadingElement, { className: `${STORAGE_BROWSER_BLOCK}__title` }, title));
1205
-
1206
- const DEFAULT_COMPOSABLES = {
1207
- ActionCancel,
1208
- ActionDestination: ActionDestination$1,
1209
- ActionExit,
1210
- ActionStart,
1211
- ActionsList: ActionsList$1,
1212
- AddFiles,
1213
- AddFolder,
1214
- DataRefresh: DataRefresh$1,
1215
- DataTable,
1216
- DropZone,
1217
- FolderNameField: FolderNameField$1,
1218
- LoadingIndicator: LoadingIndicator$1,
1219
- Message,
1220
- Navigation: Navigation$1,
1221
- OverwriteToggle: OverwriteToggle$1,
1222
- Pagination: Pagination$1,
1223
- SearchSubfoldersToggle: SearchSubfoldersToggle$1,
1224
- SearchField: SearchField$1,
1225
- StatusDisplay: StatusDisplay$1,
1226
- Title: Title$1,
1227
- };
1228
-
1229
- function ComponentsProvider(props) {
1230
- const { children, composables } = props;
1231
- return (React__namespace["default"].createElement(elements.ElementsProvider, { elements: elementsDefault },
1232
- React__namespace["default"].createElement(ComposablesProvider, { composables: composables }, children)));
1150
+ function PreviewPlaceholder() {
1151
+ return (React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__preview-placeholder` },
1152
+ React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__preview-placeholder-content` },
1153
+ React__namespace["default"].createElement(uiReact.Placeholder, { width: "100%", height: "400px" }))));
1233
1154
  }
1234
1155
 
1235
- const OverwriteToggle = ({ isDisabled, isOverwritingEnabled, label = '', onToggle, }) => {
1236
- return (React__namespace.createElement(uiReact.CheckboxField, { name: label, label: label, labelPosition: "end", isDisabled: isDisabled, checked: isOverwritingEnabled, onChange: () => {
1237
- onToggle?.();
1238
- } }));
1239
- };
1240
- const SearchSubfoldersToggle = ({ isSearchingSubfolders, label = '', onToggle }) => {
1241
- return (React__namespace.createElement(uiReact.CheckboxField, { name: label, label: label, labelPosition: "end", checked: isSearchingSubfolders, onChange: () => {
1242
- onToggle?.();
1243
- } }));
1244
- };
1245
- const Pagination = ({ page = 1, onPaginate, hasNextPage, highestPageVisited, }) => {
1246
- return (React__namespace.createElement(uiReact.Pagination, { currentPage: page, totalPages: highestPageVisited ?? 1, hasMorePages: hasNextPage, siblingCount: 1, onChange: (index) => {
1247
- onPaginate?.(index ?? 0);
1248
- }, onNext: () => {
1249
- onPaginate?.(page + 1);
1250
- }, onPrevious: () => {
1251
- onPaginate?.(page - 1);
1252
- } }));
1253
- };
1254
- const SearchField = ({ onQueryChange, onSearch, onClear, placeholder, label, query, }) => {
1255
- return (React__namespace.createElement(uiReact.SearchField, { label: label, size: "small", clearButtonLabel: "Clear search", placeholder: placeholder, value: query, onChange: (e) => {
1256
- onQueryChange?.(e.target.value);
1257
- }, onSubmit: () => {
1258
- onSearch?.();
1259
- }, onClear: () => {
1260
- onClear?.();
1261
- } }));
1262
- };
1263
- const Navigation = ({ items }) => {
1264
- return (React__namespace.createElement(uiReact.Breadcrumbs.Container, null, items.map((item, i) => {
1265
- return (React__namespace.createElement(uiReact.Breadcrumbs.Item, { key: i },
1266
- React__namespace.createElement(uiReact.Breadcrumbs.Link, { as: item.isCurrent ? 'span' : 'button', isCurrent: item.isCurrent, onClick: item.onNavigate }, item.name),
1267
- item.isCurrent ? null : React__namespace.createElement(uiReact.Breadcrumbs.Separator, null)));
1268
- })));
1269
- };
1270
- const LoadingIndicator = ({ isLoading, }) => {
1271
- if (isLoading) {
1272
- return (React__namespace.createElement(uiReact.Loader, { className: "amplify-storage-browser__loader", variation: "linear", size: "small" }));
1273
- }
1274
- };
1275
- const FolderNameField = ({ onChange, label, placeholder, validationMessage, onValidate, }) => {
1276
- const handleValidate = ({ target: { value }, }) => {
1277
- onValidate?.(value);
1278
- };
1279
- return (React__namespace.createElement(uiReact.TextField, { label: label, placeholder: placeholder, errorMessage: validationMessage, hasError: !!validationMessage, onBlur: handleValidate, onChange: (event) => {
1280
- const { value } = event.target;
1281
- handleValidate?.(event);
1282
- onChange?.(value);
1283
- } }));
1284
- };
1285
- const DataRefresh = ({ onRefresh, }) => {
1286
- return (React__namespace.createElement(uiReact.Button, { onClick: () => {
1287
- onRefresh?.();
1288
- }, "aria-label": "Refresh data" },
1289
- React__namespace.createElement(IconElement, { className: "amplify-icon", variant: "refresh" })));
1290
- };
1291
- const ActionsList = ({ items, onActionSelect, isDisabled, }) => {
1292
- return (React__namespace.createElement(uiReact.Menu, { isDisabled: isDisabled, trigger: React__namespace.createElement(uiReact.Button, { ariaLabel: "Menu Toggle" },
1293
- React__namespace.createElement(IconElement, { className: "amplify-icon", variant: "menu" })) }, items
1294
- .filter(({ isHidden }) => !isHidden)
1295
- .map(({ actionType, icon, label, isDisabled }, i) => {
1296
- return (React__namespace.createElement(uiReact.MenuItem, { key: i, size: "small", gap: "xs", isDisabled: isDisabled, onClick: () => {
1297
- onActionSelect?.(actionType);
1298
- } },
1299
- icon && React__namespace.createElement(IconElement, { variant: icon }),
1300
- label));
1301
- })));
1302
- };
1303
- const StatusDisplay = ({ statuses, total, }) => {
1304
- if (!statuses?.length) {
1305
- return null;
1306
- }
1307
- return (React__namespace.createElement(uiReact.View, { as: "dl", className: `${STORAGE_BROWSER_BLOCK}__status-display` }, statuses.map(({ name, count }, i) => (React__namespace.createElement(uiReact.View, { as: "div", className: `${STORAGE_BROWSER_BLOCK}__status`, key: i },
1308
- React__namespace.createElement(uiReact.View, { as: "dt", className: `${STORAGE_BROWSER_BLOCK}__status-label` }, name),
1309
- React__namespace.createElement(uiReact.View, { as: "dd", className: `${STORAGE_BROWSER_BLOCK}__status-value` }, `${count}/${total}`))))));
1310
- };
1311
- const ActionDestination = ({ isNavigable, items, label, }) => {
1312
- if (!items.length) {
1313
- return null;
1314
- }
1315
- return (React__namespace.createElement(uiReact.View, { as: "dl", className: `${STORAGE_BROWSER_BLOCK}__destination` },
1316
- React__namespace.createElement(uiReact.View, { as: "dt", className: `${STORAGE_BROWSER_BLOCK}__destination-label` }, label),
1317
- React__namespace.createElement(uiReact.View, { as: "dd", className: `${STORAGE_BROWSER_BLOCK}__destination-value` },
1318
- React__namespace.createElement(uiReact.Breadcrumbs.Container, null, items.map((item, i) => {
1319
- return (React__namespace.createElement(uiReact.Breadcrumbs.Item, { key: i },
1320
- isNavigable ? (React__namespace.createElement(uiReact.Breadcrumbs.Link, { as: item.isCurrent ? 'span' : 'button', isCurrent: item.isCurrent, onClick: item.onNavigate }, item.name)) : (item.name),
1321
- item.isCurrent ? null : React__namespace.createElement(uiReact.Breadcrumbs.Separator, null)));
1322
- })))));
1323
- };
1324
- const Title = ({ title }) => {
1325
- return (React__namespace.createElement(uiReact.Heading, { className: `${STORAGE_BROWSER_BLOCK}__title`, level: 2 }, title));
1326
- };
1327
- const componentsDefault = {
1328
- ActionDestination,
1329
- ActionsList,
1330
- DataRefresh,
1331
- LoadingIndicator,
1332
- Pagination,
1333
- Navigation,
1334
- OverwriteToggle,
1335
- SearchField,
1336
- SearchSubfoldersToggle,
1337
- StatusDisplay,
1338
- FolderNameField,
1339
- Title,
1340
- };
1341
-
1342
- const Fallback = () => (React__namespace["default"].createElement("div", { className: STORAGE_BROWSER_BLOCK_TO_BE_UPDATED },
1343
- React__namespace["default"].createElement("div", { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__error-boundary` }, "Something went wrong.")));
1344
- class ErrorBoundary extends React__namespace["default"].Component {
1345
- constructor(props) {
1346
- super(props);
1347
- this.state = { hasError: false };
1348
- }
1349
- static getDerivedStateFromError(_error) {
1350
- // Update state so the next render will show the fallback UI.
1351
- return { hasError: true };
1352
- }
1353
- render() {
1354
- const { hasError } = this.state;
1355
- const { children } = this.props;
1356
- if (hasError) {
1357
- return React__namespace["default"].createElement(Fallback, null);
1358
- }
1359
- return children;
1360
- }
1156
+ /**
1157
+ * Extracts the filename from a file key path
1158
+ * @param fileKey - The full file key/path
1159
+ * @returns The filename (last part after '/') or empty string if fileKey is null/undefined
1160
+ */
1161
+ function getFileName(fileKey) {
1162
+ if (!fileKey)
1163
+ return '';
1164
+ return fileKey.split('/').pop() ?? fileKey;
1361
1165
  }
1362
1166
 
1363
1167
  const { ActionHandlersProvider, useActionHandlers } = uiReactCore.createContextUtilities({
@@ -2204,938 +2008,1706 @@ const getActionHandlers = (configs) => {
2204
2008
  : { ...handlers, [key]: resolveHandler(config) }, defaultHandlers);
2205
2009
  };
2206
2010
 
2207
- const defaultValue$5 = { data: {} };
2208
- const { useControlsContext, ControlsContextProvider } = uiReactCore.createContextUtilities({
2209
- contextName: 'ControlsContext',
2210
- defaultValue: defaultValue$5,
2211
- });
2212
-
2213
- const useActionCancel = () => {
2214
- const { data: { actionCancelLabel, isActionCancelDisabled }, onActionCancel, } = useControlsContext();
2215
- return {
2216
- onCancel: onActionCancel,
2217
- isDisabled: isActionCancelDisabled,
2218
- label: actionCancelLabel,
2219
- };
2011
+ const DEFAULT_ACTION_VIEW_DISPLAY_TEXT = {
2012
+ actionCancelLabel: 'Cancel',
2013
+ actionExitLabel: 'Exit',
2014
+ actionDestinationLabel: 'Destination',
2015
+ statusDisplayCanceledLabel: 'Canceled',
2016
+ statusDisplayCompletedLabel: 'Completed',
2017
+ statusDisplayFailedLabel: 'Failed',
2018
+ statusDisplayInProgressLabel: 'In progress',
2019
+ statusDisplayTotalLabel: 'Total',
2020
+ statusDisplayQueuedLabel: 'Not started',
2021
+ // empty by default
2022
+ tableColumnCancelHeader: '',
2023
+ tableColumnStatusHeader: 'Status',
2024
+ tableColumnFolderHeader: 'Folder',
2025
+ tableColumnNameHeader: 'Name',
2026
+ tableColumnTypeHeader: 'Type',
2027
+ tableColumnSizeHeader: 'Size',
2028
+ };
2029
+ const DEFAULT_LIST_VIEW_DISPLAY_TEXT = {
2030
+ loadingIndicatorLabel: 'Loading',
2031
+ searchSubmitLabel: 'Submit',
2032
+ searchClearLabel: 'Clear search',
2033
+ getDateDisplayValue: (date) => new Intl.DateTimeFormat('en-US', {
2034
+ month: 'short',
2035
+ day: 'numeric',
2036
+ hour: 'numeric',
2037
+ year: 'numeric',
2038
+ minute: 'numeric',
2039
+ hourCycle: 'h12',
2040
+ }).format(date),
2220
2041
  };
2221
2042
 
2222
- function useResolvedComposable(DefaultComposable, name) {
2223
- const { composables } = useComposables();
2224
- const Composable = React__namespace["default"].useMemo(() => {
2225
- const ResolvedComposable = (props) => {
2226
- const Resolved = composables?.[name] ?? DefaultComposable;
2227
- return React__namespace["default"].createElement(Resolved, { ...props });
2228
- };
2229
- ResolvedComposable.displayName = name;
2230
- return ResolvedComposable;
2231
- }, [composables, DefaultComposable, name]);
2232
- return Composable;
2233
- }
2234
-
2235
- const ActionCancelControl = () => {
2236
- const props = useActionCancel();
2237
- const Resolved = useResolvedComposable(ActionCancel, 'ActionCancel');
2238
- return React__namespace["default"].createElement(Resolved, { ...props });
2043
+ const DEFAULT_CREATE_FOLDER_VIEW_DISPLAY_TEXT = {
2044
+ ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
2045
+ title: 'Create folder',
2046
+ actionStartLabel: 'Create folder',
2047
+ folderNameLabel: 'Folder name',
2048
+ folderNamePlaceholder: 'Folder name cannot contain "/", nor end or start with "."',
2049
+ getValidationMessage: () => 'Folder name cannot contain "/", nor end or start with "."',
2050
+ getActionCompleteMessage: (data) => {
2051
+ const { counts } = data ?? {};
2052
+ const { FAILED, OVERWRITE_PREVENTED } = counts ?? {};
2053
+ if (OVERWRITE_PREVENTED) {
2054
+ return {
2055
+ content: 'A folder already exists with the provided name',
2056
+ type: 'warning',
2057
+ };
2058
+ }
2059
+ if (FAILED) {
2060
+ return {
2061
+ content: 'There was an issue creating the folder.',
2062
+ type: 'error',
2063
+ };
2064
+ }
2065
+ return { content: 'Folder created.', type: 'success' };
2066
+ },
2239
2067
  };
2240
2068
 
2241
- const getNavigationItems = ({ destinationParts, location, onNavigate, }) => {
2242
- const { bucket, permissions, prefix = '', type } = location;
2243
- const destinationSubpaths = [];
2244
- return destinationParts.map((part, index) => {
2245
- const isCurrent = index === destinationParts.length - 1;
2246
- if (index !== 0) {
2247
- destinationSubpaths.push(part);
2069
+ const DEFAULT_COPY_VIEW_DISPLAY_TEXT = {
2070
+ ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
2071
+ title: 'Copy',
2072
+ actionStartLabel: 'Copy',
2073
+ actionDestinationLabel: 'Copy destination',
2074
+ getListFoldersResultsMessage: ({ folders, query, message, hasError, hasExhaustedSearch, }) => {
2075
+ if (!folders?.length) {
2076
+ return {
2077
+ content: query
2078
+ ? `No folders found matching "${query}"`
2079
+ : 'No subfolders found within selected folder.',
2080
+ type: 'info',
2081
+ };
2082
+ }
2083
+ if (message && !!query) {
2084
+ return { content: 'Error loading folders.', type: 'error' };
2085
+ }
2086
+ if (hasError) {
2087
+ return { content: 'Error loading folders.', type: 'error' };
2088
+ }
2089
+ if (hasExhaustedSearch) {
2090
+ return {
2091
+ content: 'Showing results for up to the first 10,000 items.',
2092
+ type: 'info',
2093
+ };
2094
+ }
2095
+ },
2096
+ loadingIndicatorLabel: 'Loading',
2097
+ overwriteWarningMessage: 'Copied files will overwrite existing files at selected destination.',
2098
+ searchPlaceholder: 'Search for folders',
2099
+ getActionCompleteMessage: (data) => {
2100
+ const { counts } = data ?? {};
2101
+ const { COMPLETE, FAILED, TOTAL } = counts ?? {};
2102
+ if (COMPLETE === TOTAL) {
2103
+ return {
2104
+ content: 'All files copied.',
2105
+ type: 'success',
2106
+ };
2107
+ }
2108
+ if (FAILED === TOTAL) {
2109
+ return { content: 'All files failed to copy.', type: 'error' };
2248
2110
  }
2249
- const destinationPath = `${destinationSubpaths.concat('').join('/')}`;
2250
- const destination = {
2251
- id: crypto.randomUUID(),
2252
- type,
2253
- permissions,
2254
- bucket,
2255
- prefix,
2256
- };
2257
2111
  return {
2258
- name: part,
2259
- ...(isCurrent && { isCurrent }),
2260
- onNavigate: () => {
2261
- onNavigate?.(destination, destinationPath);
2262
- },
2112
+ content: `${COMPLETE} files copied, ${FAILED} files failed to copy.`,
2113
+ type: 'error',
2263
2114
  };
2264
- });
2115
+ },
2116
+ searchSubmitLabel: 'Submit',
2117
+ searchClearLabel: 'Clear search',
2265
2118
  };
2266
2119
 
2267
- const getNavigationParts = ({ location, path, includeBucketInPrefix, }) => {
2268
- const { bucket, prefix = '', type } = location;
2269
- const trimmedPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
2270
- const trimmedPath = path.endsWith('/') ? path.slice(0, -1) : path;
2271
- const firstPrefixPart = [];
2272
- if (type !== 'BUCKET') {
2273
- if (includeBucketInPrefix) {
2274
- firstPrefixPart.push(bucket);
2275
- }
2276
- if (trimmedPrefix) {
2277
- if (includeBucketInPrefix) {
2278
- firstPrefixPart.push('/');
2279
- }
2280
- firstPrefixPart.push(trimmedPrefix);
2120
+ const DEFAULT_DELETE_VIEW_DISPLAY_TEXT = {
2121
+ ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
2122
+ title: 'Delete',
2123
+ actionStartLabel: 'Delete',
2124
+ getActionCompleteMessage: (data) => {
2125
+ const { counts } = data ?? {};
2126
+ const { COMPLETE, FAILED, TOTAL } = counts ?? {};
2127
+ if (COMPLETE === TOTAL) {
2128
+ return { content: 'All files deleted.', type: 'success' };
2281
2129
  }
2282
- }
2283
- const prefixParts = type === 'BUCKET' ? [bucket] : [firstPrefixPart.join('')];
2284
- if (type === 'BUCKET' && trimmedPrefix) {
2285
- prefixParts.push(trimmedPrefix);
2286
- }
2287
- const pathParts = trimmedPath ? trimmedPath.split('/') : [];
2288
- return prefixParts.concat(pathParts);
2289
- };
2290
-
2291
- const useActionDestination = () => {
2292
- const { data, onSelectDestination } = useControlsContext();
2293
- const { actionDestinationLabel, isActionDestinationNavigable, destination } = data;
2294
- return React__namespace["default"].useMemo(() => {
2295
- if (!destination?.current) {
2296
- return { items: [] };
2130
+ if (FAILED === TOTAL) {
2131
+ return { content: 'All files failed to delete.', type: 'error' };
2297
2132
  }
2298
- const { current, path } = destination;
2299
- const destinationParts = getNavigationParts({
2300
- location: current,
2301
- path,
2302
- });
2303
2133
  return {
2304
- label: actionDestinationLabel,
2305
- items: getNavigationItems({
2306
- location: current,
2307
- destinationParts,
2308
- onNavigate: onSelectDestination,
2309
- }),
2310
- isNavigable: isActionDestinationNavigable,
2134
+ content: `${COMPLETE} files deleted, ${FAILED} files failed to delete.`,
2135
+ type: 'error',
2311
2136
  };
2312
- }, [
2313
- actionDestinationLabel,
2314
- isActionDestinationNavigable,
2315
- destination,
2316
- onSelectDestination,
2317
- ]);
2318
- };
2319
-
2320
- const ActionDestinationControl = () => {
2321
- const props = useActionDestination();
2322
- const Resolved = useResolvedComposable(ActionDestination$1, 'ActionDestination');
2323
- return React__namespace["default"].createElement(Resolved, { ...props });
2324
- };
2325
-
2326
- const useActionExit = () => {
2327
- const { data: { actionExitLabel: label, isActionExitDisabled: isDisabled }, onActionExit: onExit, } = useControlsContext();
2328
- return { label, isDisabled, onExit };
2137
+ },
2329
2138
  };
2330
2139
 
2331
- const ActionExitControl = () => {
2332
- const props = useActionExit();
2333
- const Resolved = useResolvedComposable(ActionExit, 'ActionExit');
2334
- return React__namespace["default"].createElement(Resolved, { ...props });
2140
+ const DEFAULT_ERROR_MESSAGE$1 = 'There was an error loading items.';
2141
+ const DEFAULT_FILE_PREVIEW_DISPLAY_TEXT = {
2142
+ closeButtonLabel: 'Close',
2143
+ filePreviewTitle: 'File Preview',
2144
+ fileInformationTitle: 'File Information',
2145
+ errorMessage: 'Something went wrong',
2146
+ sizeLimitMessage: 'File preview not possible due to preview size limit',
2147
+ unsupportedFileMessage: 'File preview not supported for this file type',
2148
+ keyLabel: 'Key',
2149
+ sizeLabel: 'Size',
2150
+ versionIdLabel: 'Version Id',
2151
+ lastModifiedLabel: 'Last Modified',
2152
+ entityTagLabel: 'Entity tag',
2153
+ typeLabel: 'Type',
2154
+ unknownValue: 'Unknown',
2155
+ errorDescription: 'We encountered an issue while loading the file preview.',
2156
+ unsupportedFileDescription: 'This file format is not supported for preview. You can download the file to view it with an appropriate application.',
2157
+ imageLoadErrorDescription: 'The image could not be loaded. This may be due to network issues, file corruption, or an unsupported image format.',
2158
+ videoLoadErrorDescription: 'The video could not be played. This may be due to network issues, file corruption, or an unsupported video format or codec.',
2159
+ textLoadErrorDescription: 'The text file could not be loaded. This may be due to network issues, file access permissions, or the file being too large to display.',
2160
+ generalPreviewErrorDescription: 'An unexpected error occurred while loading the file preview. Please try again or download the file to view it with an appropriate application.',
2161
+ fileSizeLimitDescription: 'This file is too large to preview in the browser. You can download the file to view it with an appropriate application.',
2162
+ filePrefix: 'File: ',
2163
+ retryButtonLabel: 'Retry',
2164
+ downloadButtonLabel: 'Download',
2165
+ getTextErrorMessage: (error) => `Error loading file: ${error}`,
2166
+ emptyFileMessage: 'File is empty',
2335
2167
  };
2336
-
2337
- const useActionsList = () => {
2338
- const { data: { actions, isActionsListDisabled }, onActionSelect, } = useControlsContext();
2339
- return {
2340
- isDisabled: isActionsListDisabled,
2341
- items: actions ?? [],
2342
- onActionSelect,
2343
- };
2168
+ const DEFAULT_LOCATION_DETAIL_VIEW_DISPLAY_TEXT = {
2169
+ ...DEFAULT_LIST_VIEW_DISPLAY_TEXT,
2170
+ getListItemsResultMessage: (data) => {
2171
+ const { items, hasExhaustedSearch, hasError = false, message, isLoading, } = data ?? {};
2172
+ if (isLoading) {
2173
+ return undefined;
2174
+ }
2175
+ if (hasError) {
2176
+ return {
2177
+ type: 'error',
2178
+ content: message ?? DEFAULT_ERROR_MESSAGE$1,
2179
+ };
2180
+ }
2181
+ if (!items?.length && hasExhaustedSearch) {
2182
+ return {
2183
+ type: 'info',
2184
+ content: `No results found in the first 10,000 items.`,
2185
+ };
2186
+ }
2187
+ if (!items?.length) {
2188
+ return {
2189
+ type: 'info',
2190
+ content: 'No files.',
2191
+ };
2192
+ }
2193
+ if (hasExhaustedSearch) {
2194
+ return {
2195
+ type: 'info',
2196
+ content: `Showing results for up to the first 10,000 items.`,
2197
+ };
2198
+ }
2199
+ // TODO: add more cases as needed
2200
+ return undefined;
2201
+ },
2202
+ searchSubfoldersToggleLabel: 'Include subfolders',
2203
+ searchPlaceholder: 'Search current folder',
2204
+ tableColumnLastModifiedHeader: 'Last modified',
2205
+ tableColumnNameHeader: 'Name',
2206
+ tableColumnSizeHeader: 'Size',
2207
+ tableColumnTypeHeader: 'Type',
2208
+ selectFileLabel: 'Select file',
2209
+ selectAllFilesLabel: 'Select all files',
2210
+ getActionListItemLabel: (key = '') => {
2211
+ switch (key) {
2212
+ case 'Copy':
2213
+ return 'Copy';
2214
+ case 'Delete':
2215
+ return 'Delete';
2216
+ case 'Create folder':
2217
+ return 'Create folder';
2218
+ case 'Upload':
2219
+ return 'Upload';
2220
+ case 'Download':
2221
+ return 'Download';
2222
+ default:
2223
+ return key;
2224
+ }
2225
+ },
2226
+ getTitle: (location) => {
2227
+ const { current, key } = location;
2228
+ const { bucket = '' } = current ?? {};
2229
+ return key || bucket;
2230
+ },
2231
+ filePreview: DEFAULT_FILE_PREVIEW_DISPLAY_TEXT,
2344
2232
  };
2345
2233
 
2346
- const ActionsListControl = () => {
2347
- const props = useActionsList();
2348
- const Resolved = useResolvedComposable(ActionsList$1, 'ActionsList');
2349
- return React__namespace["default"].createElement(Resolved, { ...props });
2350
- };
2351
-
2352
- const useActionStart = () => {
2353
- const { data: { actionStartLabel, isActionStartDisabled }, onActionStart, } = useControlsContext();
2354
- return {
2355
- label: actionStartLabel,
2356
- isDisabled: isActionStartDisabled,
2357
- onStart: onActionStart,
2358
- };
2234
+ const DEFAULT_ERROR_MESSAGE = 'There was an error loading locations.';
2235
+ const DEFAULT_LOCATIONS_VIEW_DISPLAY_TEXT = {
2236
+ ...DEFAULT_LIST_VIEW_DISPLAY_TEXT,
2237
+ title: 'Home',
2238
+ searchPlaceholder: 'Filter folders and files',
2239
+ getListLocationsResultMessage: (data) => {
2240
+ const { isLoading, items, hasExhaustedSearch, hasError = false, message, } = data ?? {};
2241
+ if (isLoading) {
2242
+ return undefined;
2243
+ }
2244
+ if (hasError) {
2245
+ return {
2246
+ type: 'error',
2247
+ content: message ?? DEFAULT_ERROR_MESSAGE,
2248
+ };
2249
+ }
2250
+ if (items?.length === 0 && !hasExhaustedSearch) {
2251
+ return {
2252
+ type: 'info',
2253
+ content: 'No folders or files.',
2254
+ };
2255
+ }
2256
+ if (hasExhaustedSearch) {
2257
+ return {
2258
+ type: 'info',
2259
+ content: `Showing results for up to the first 10,000 items.`,
2260
+ };
2261
+ }
2262
+ // TODO: add more cases as needed
2263
+ return undefined;
2264
+ },
2265
+ getPermissionName: (permissions) => {
2266
+ let text = '';
2267
+ if (permissions.includes('get') || permissions.includes('list')) {
2268
+ text = 'Read';
2269
+ }
2270
+ if (permissions.includes('write') || permissions.includes('delete')) {
2271
+ text = text ? 'Read/Write' : 'Write';
2272
+ }
2273
+ if (!text) {
2274
+ text = permissions.join('/');
2275
+ }
2276
+ return text;
2277
+ },
2278
+ getDownloadLabel: (fileName) => `Download ${fileName}`,
2279
+ tableColumnBucketHeader: 'Bucket',
2280
+ tableColumnFolderHeader: 'Folder',
2281
+ tableColumnPermissionsHeader: 'Permissions',
2282
+ tableColumnActionsHeader: 'Actions',
2359
2283
  };
2360
2284
 
2361
- const ActionStartControl = () => {
2362
- const props = useActionStart();
2363
- const Resolved = useResolvedComposable(ActionStart, 'ActionStart');
2364
- return React__namespace["default"].createElement(Resolved, { ...props });
2285
+ const DEFAULT_UPLOAD_VIEW_DISPLAY_TEXT = {
2286
+ ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
2287
+ actionStartLabel: 'Upload',
2288
+ addFilesLabel: 'Add files',
2289
+ addFolderLabel: 'Add folder',
2290
+ getActionCompleteMessage: (data) => {
2291
+ const { counts } = data ?? {};
2292
+ const { COMPLETE, FAILED, OVERWRITE_PREVENTED, CANCELED, TOTAL } = counts ?? {};
2293
+ const hasPreventedOverwrite = !!OVERWRITE_PREVENTED;
2294
+ const hasFailure = !!FAILED;
2295
+ const hasSuccess = !!COMPLETE;
2296
+ const hasCanceled = !!CANCELED;
2297
+ const type = hasFailure
2298
+ ? 'error'
2299
+ : hasPreventedOverwrite || hasCanceled
2300
+ ? 'warning'
2301
+ : 'success';
2302
+ const preventedOverwriteMessage = hasPreventedOverwrite
2303
+ ? [
2304
+ 'Overwrite prevented for',
2305
+ OVERWRITE_PREVENTED === TOTAL ? 'all' : String(OVERWRITE_PREVENTED),
2306
+ OVERWRITE_PREVENTED > 1 || OVERWRITE_PREVENTED === TOTAL
2307
+ ? `files`
2308
+ : 'file',
2309
+ ].join(' ')
2310
+ : undefined;
2311
+ const canceledMessage = hasCanceled
2312
+ ? [
2313
+ CANCELED === TOTAL ? 'All' : String(CANCELED),
2314
+ CANCELED > 1 || CANCELED === TOTAL ? `uploads` : 'upload',
2315
+ 'canceled',
2316
+ ].join(' ')
2317
+ : undefined;
2318
+ const failedMessage = hasFailure
2319
+ ? [
2320
+ FAILED === TOTAL ? 'All' : String(FAILED),
2321
+ FAILED > 1 || FAILED === TOTAL ? `files` : 'file',
2322
+ 'failed to upload',
2323
+ ].join(' ')
2324
+ : undefined;
2325
+ const completedMessage = hasSuccess
2326
+ ? [
2327
+ COMPLETE === TOTAL ? 'All' : String(COMPLETE),
2328
+ COMPLETE > 1 || COMPLETE === TOTAL ? `files` : 'file',
2329
+ 'uploaded',
2330
+ ].join(' ')
2331
+ : undefined;
2332
+ const messages = [
2333
+ preventedOverwriteMessage,
2334
+ failedMessage,
2335
+ canceledMessage,
2336
+ completedMessage,
2337
+ ].filter(Boolean);
2338
+ if (messages.length > 0) {
2339
+ return {
2340
+ content: messages.join(', ') + '.',
2341
+ type,
2342
+ };
2343
+ }
2344
+ return { content: 'All files uploaded.', type };
2345
+ },
2346
+ getFilesValidationMessage: (data) => {
2347
+ if (!data?.invalidFiles) {
2348
+ return undefined;
2349
+ }
2350
+ const invalidFileNames = data.invalidFiles
2351
+ .map(({ file }) => file.name)
2352
+ .join(', ');
2353
+ if (invalidFileNames) {
2354
+ return {
2355
+ content: `Files larger than 160GB cannot be added to the upload queue: ${invalidFileNames}`,
2356
+ type: 'warning',
2357
+ };
2358
+ }
2359
+ return undefined;
2360
+ },
2361
+ overwriteToggleLabel: 'Overwrite existing files',
2362
+ statusDisplayOverwritePreventedLabel: 'Overwrite prevented',
2363
+ tableColumnProgressHeader: 'Progress',
2364
+ title: 'Upload',
2365
2365
  };
2366
2366
 
2367
- const useAddFiles = () => {
2368
- const { data: { addFilesLabel, isAddFilesDisabled }, onAddFiles, } = useControlsContext();
2369
- return {
2370
- isDisabled: isAddFilesDisabled,
2371
- label: addFilesLabel,
2372
- onAddFiles,
2373
- };
2367
+ const DEFAULT_DOWNLOAD_VIEW_DISPLAY_TEXT = {
2368
+ ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
2369
+ title: 'Download',
2370
+ actionStartLabel: 'Download',
2371
+ getActionCompleteMessage: (data) => {
2372
+ const { counts } = data ?? {};
2373
+ const { COMPLETE, FAILED, TOTAL } = counts ?? {};
2374
+ if (COMPLETE === TOTAL) {
2375
+ return { content: 'All files downloaded.', type: 'success' };
2376
+ }
2377
+ if (FAILED === TOTAL) {
2378
+ return { content: 'All files failed to download.', type: 'error' };
2379
+ }
2380
+ return {
2381
+ content: `${COMPLETE} files downloaded, ${FAILED} files failed to download.`,
2382
+ type: 'error',
2383
+ };
2384
+ },
2374
2385
  };
2375
2386
 
2376
- const AddFilesControl = () => {
2377
- const props = useAddFiles();
2378
- const Resolved = useResolvedComposable(AddFiles, 'AddFiles');
2379
- return React__namespace["default"].createElement(Resolved, { ...props });
2387
+ const DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT = {
2388
+ CopyView: DEFAULT_COPY_VIEW_DISPLAY_TEXT,
2389
+ CreateFolderView: DEFAULT_CREATE_FOLDER_VIEW_DISPLAY_TEXT,
2390
+ DeleteView: DEFAULT_DELETE_VIEW_DISPLAY_TEXT,
2391
+ DownloadView: DEFAULT_DOWNLOAD_VIEW_DISPLAY_TEXT,
2392
+ LocationDetailView: DEFAULT_LOCATION_DETAIL_VIEW_DISPLAY_TEXT,
2393
+ LocationsView: DEFAULT_LOCATIONS_VIEW_DISPLAY_TEXT,
2394
+ UploadView: DEFAULT_UPLOAD_VIEW_DISPLAY_TEXT,
2380
2395
  };
2381
2396
 
2382
- const useAddFolder = () => {
2383
- const { data: { addFolderLabel, isAddFolderDisabled }, onAddFolder, } = useControlsContext();
2397
+ const { DisplayTextContext, useDisplayText } = uiReactCore.createContextUtilities({
2398
+ contextName: 'DisplayText',
2399
+ errorMessage: '`useDisplayText` must be called inside `DisplayTextProvider`',
2400
+ });
2401
+ function resolveDisplayText(displayText) {
2402
+ if (!displayText)
2403
+ return DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT;
2404
+ // override
2405
+ const { CopyView, CreateFolderView, DeleteView, DownloadView, LocationDetailView, LocationsView, UploadView, } = displayText;
2384
2406
  return {
2385
- isDisabled: isAddFolderDisabled,
2386
- label: addFolderLabel,
2387
- onAddFolder,
2407
+ CopyView: { ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.CopyView, ...CopyView },
2408
+ CreateFolderView: {
2409
+ ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.CreateFolderView,
2410
+ ...CreateFolderView,
2411
+ },
2412
+ DeleteView: {
2413
+ ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.DeleteView,
2414
+ ...DeleteView,
2415
+ },
2416
+ DownloadView: {
2417
+ ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.DownloadView,
2418
+ ...DownloadView,
2419
+ },
2420
+ LocationDetailView: {
2421
+ ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.LocationDetailView,
2422
+ ...LocationDetailView,
2423
+ filePreview: {
2424
+ ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.LocationDetailView.filePreview,
2425
+ ...(LocationDetailView?.filePreview ?? {}),
2426
+ },
2427
+ },
2428
+ LocationsView: {
2429
+ ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.LocationsView,
2430
+ ...LocationsView,
2431
+ },
2432
+ UploadView: {
2433
+ ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.UploadView,
2434
+ ...UploadView,
2435
+ },
2388
2436
  };
2389
- };
2390
-
2391
- const AddFolderControl = () => {
2392
- const props = useAddFolder();
2393
- const Resolved = useResolvedComposable(AddFolder, 'AddFolder');
2394
- return React__namespace["default"].createElement(Resolved, { ...props });
2395
- };
2396
-
2397
- const useDataRefresh = () => {
2398
- const { data: { isDataRefreshDisabled }, onRefresh, } = useControlsContext();
2399
- return { isDisabled: isDataRefreshDisabled, onRefresh };
2400
- };
2437
+ }
2438
+ function DisplayTextProvider({ children, displayText: _override, }) {
2439
+ // do deep merge here of default and override here
2440
+ const resolvedDisplayText = React__namespace["default"].useMemo(() => resolveDisplayText(_override), [_override]);
2441
+ return (React__namespace["default"].createElement(DisplayTextContext.Provider, { value: resolvedDisplayText }, children));
2442
+ }
2401
2443
 
2402
- const DataRefreshControl = () => {
2403
- const props = useDataRefresh();
2404
- const Resolved = useResolvedComposable(DataRefresh$1, 'DataRefresh');
2405
- return React__namespace["default"].createElement(Resolved, { ...props });
2406
- };
2444
+ const isCopyViewDisplayTextKey = (value) => !!DEFAULT_COPY_VIEW_DISPLAY_TEXT[value];
2445
+ const isDeleteViewDisplayTextKey = (value) => !!DEFAULT_DELETE_VIEW_DISPLAY_TEXT[value];
2446
+ const isDownloadViewDisplayTextKey = (value) => !!DEFAULT_DOWNLOAD_VIEW_DISPLAY_TEXT[value];
2407
2447
 
2408
- const compareContent$3 = ({ label: a }, { label: b }) => {
2409
- if (a === undefined) {
2410
- return b === undefined ? 0 : 1;
2448
+ const DownloadButton = ({ fileKey }) => {
2449
+ const [{ isProcessing: downloadPending }, handleDownload] = useAction('download');
2450
+ const fileName = React.useMemo(() => getFileName(fileKey), [fileKey]);
2451
+ const { LocationDetailView: displayText } = useDisplayText();
2452
+ const { filePreview: { downloadButtonLabel }, } = displayText;
2453
+ const handleDownloadClick = React.useCallback(() => {
2454
+ handleDownload({
2455
+ data: {
2456
+ fileKey: fileName,
2457
+ key: fileKey,
2458
+ id: crypto.randomUUID(),
2459
+ },
2460
+ });
2461
+ }, [fileName, fileKey, handleDownload]);
2462
+ return (React__namespace["default"].createElement(ButtonElement, { ['aria-label']: `Download ${fileName} file`, className: `${STORAGE_BROWSER_BLOCK}__download-button`, onClick: handleDownloadClick, disabled: downloadPending, variant: "primary" },
2463
+ downloadPending ? (React__namespace["default"].createElement(IconElement, { className: `${STORAGE_BROWSER_BLOCK}__download-button_icon`, variant: "action-progress" })) : null,
2464
+ downloadButtonLabel));
2465
+ };
2466
+
2467
+ const DEFAULT_URL_OPTIONS = {
2468
+ expiresIn: 7200,
2469
+ validateObjectExistence: true,
2470
+ };
2471
+ const DEFAULT_FILE_SIZE_LIMIT = 200 * 1024 * 1024; // 200MB
2472
+ const DEFAULT_FILE_SIZE_LIMITS = {
2473
+ image: 200 * 1024 * 1024,
2474
+ video: 2 * 1024 * 1024 * 1024,
2475
+ text: 10 * 1024 * 1024, // 10MB
2476
+ };
2477
+ const GENERIC_CONTENT_TYPES = new Set([
2478
+ 'application/octet-stream',
2479
+ 'binary/octet-stream',
2480
+ 'application/unknown',
2481
+ 'unknown/unknown',
2482
+ 'application/force-download',
2483
+ 'application/download',
2484
+ 'application/x-download',
2485
+ 'application/binary',
2486
+ ]);
2487
+ const EXTENSION_MAPPINGS = {
2488
+ // Images
2489
+ jpg: 'image',
2490
+ jpeg: 'image',
2491
+ png: 'image',
2492
+ gif: 'image',
2493
+ webp: 'image',
2494
+ svg: 'image',
2495
+ bmp: 'image',
2496
+ tiff: 'image',
2497
+ tif: 'image',
2498
+ ico: 'image',
2499
+ heic: 'image',
2500
+ heif: 'image',
2501
+ avif: 'image',
2502
+ // Videos
2503
+ mp4: 'video',
2504
+ avi: 'video',
2505
+ mov: 'video',
2506
+ wmv: 'video',
2507
+ flv: 'video',
2508
+ webm: 'video',
2509
+ mkv: 'video',
2510
+ m4v: 'video',
2511
+ mpg: 'video',
2512
+ mpeg: 'video',
2513
+ '3gp': 'video',
2514
+ ogv: 'video',
2515
+ // Plain text files only
2516
+ txt: 'text',
2517
+ csv: 'text',
2518
+ log: 'text',
2519
+ json: 'text',
2520
+ xml: 'text',
2521
+ yaml: 'text',
2522
+ yml: 'text',
2523
+ ini: 'text',
2524
+ conf: 'text',
2525
+ cfg: 'text',
2526
+ };
2527
+
2528
+ function isGenericContentType(contentType) {
2529
+ if (!contentType || typeof contentType !== 'string') {
2530
+ return true;
2411
2531
  }
2412
- return b === undefined ? -1 : a.localeCompare(b);
2413
- };
2414
- const compareButtonData = (a, b, direction) => direction === 'ascending'
2415
- ? compareContent$3(a.content, b.content)
2416
- : compareContent$3(b.content, a.content);
2417
-
2418
- const compareContent$2 = ({ value: a }, { value: b }) => {
2419
- if (a === undefined) {
2420
- return b === undefined ? 0 : 1;
2532
+ const normalizedContentType = contentType.toLowerCase().trim();
2533
+ return GENERIC_CONTENT_TYPES.has(normalizedContentType);
2534
+ }
2535
+ function isTextBasedApplicationType(contentType) {
2536
+ if (contentType === 'application/json' ||
2537
+ (contentType.startsWith('application/') && contentType.includes('+json'))) {
2538
+ return true;
2421
2539
  }
2422
- return b === undefined ? -1 : a.getTime() - b.getTime();
2423
- };
2424
- const compareDateData = (a, b, direction) => direction === 'ascending'
2425
- ? compareContent$2(a.content, b.content)
2426
- : compareContent$2(b.content, a.content);
2427
-
2428
- const compareContent$1 = ({ value: a }, { value: b }) => {
2429
- if (a === undefined) {
2430
- return b === undefined ? 0 : 1;
2540
+ if (contentType === 'application/xml' ||
2541
+ (contentType.startsWith('application/') && contentType.includes('+xml'))) {
2542
+ return true;
2431
2543
  }
2432
- return b === undefined ? -1 : a - b;
2433
- };
2434
- const compareNumberData = (a, b, direction) => direction === 'ascending'
2435
- ? compareContent$1(a.content, b.content)
2436
- : compareContent$1(b.content, a.content);
2437
-
2438
- const compareContent = ({ text: a }, { text: b }) => {
2439
- if (a === undefined) {
2440
- return b === undefined ? 0 : 1;
2544
+ const textBasedTypes = [
2545
+ 'application/csv',
2546
+ 'application/yaml',
2547
+ 'application/toml',
2548
+ ];
2549
+ return textBasedTypes.includes(contentType);
2550
+ }
2551
+ function getFileTypeFromContentType(contentType) {
2552
+ if (!contentType || typeof contentType !== 'string') {
2553
+ return null;
2441
2554
  }
2442
- return b === undefined ? -1 : a.localeCompare(b);
2443
- };
2444
- const compareTextData = (a, b, direction) => direction === 'ascending'
2445
- ? compareContent(a.content, b.content)
2446
- : compareContent(b.content, a.content);
2447
-
2448
- const GROUP_ORDER = [
2449
- 'checkbox',
2450
- 'button',
2451
- 'date',
2452
- 'number',
2453
- 'text',
2454
- ];
2455
- const UNSORTABLE_GROUPS = ['checkbox'];
2456
- const useDataTable = () => {
2457
- const { data } = useControlsContext();
2458
- const { isLoading, tableData } = data;
2459
- const defaultSortIndex = React__namespace["default"].useMemo(() => tableData?.headers?.findIndex(({ type }) => type === 'sort') ?? -1, [tableData]);
2460
- const [sortState, setSortState] = React__namespace["default"].useState({
2461
- index: defaultSortIndex,
2462
- direction: 'ascending',
2463
- });
2464
- const mappedHeaders = React__namespace["default"].useMemo(() => tableData?.headers.map((header, index) => {
2465
- const { type } = header;
2466
- switch (type) {
2467
- case 'sort': {
2468
- return {
2469
- ...header,
2470
- content: {
2471
- ...header.content,
2472
- onSort: () => {
2473
- setSortState({
2474
- index,
2475
- direction: sortState.index === index
2476
- ? sortState.direction === 'ascending'
2477
- ? 'descending'
2478
- : 'ascending'
2479
- : 'ascending',
2480
- });
2481
- },
2482
- sortDirection: sortState.index === index ? sortState.direction : undefined,
2483
- },
2484
- };
2485
- }
2486
- case 'checkbox':
2487
- case 'text':
2488
- default: {
2489
- return header;
2555
+ const normalizedContentType = contentType.toLowerCase().trim().split(';')[0];
2556
+ if (normalizedContentType.startsWith('image/')) {
2557
+ return 'image';
2558
+ }
2559
+ if (normalizedContentType.startsWith('video/')) {
2560
+ return 'video';
2561
+ }
2562
+ if (normalizedContentType.startsWith('text/') ||
2563
+ isTextBasedApplicationType(normalizedContentType)) {
2564
+ return 'text';
2565
+ }
2566
+ return null;
2567
+ }
2568
+ function getFileExtension(key) {
2569
+ if (!key || typeof key !== 'string') {
2570
+ return null;
2571
+ }
2572
+ const lastDotIndex = key.lastIndexOf('.');
2573
+ if (lastDotIndex === -1 || lastDotIndex === key.length - 1) {
2574
+ return null;
2575
+ }
2576
+ return key.slice(lastDotIndex + 1).toLowerCase();
2577
+ }
2578
+ function getFileTypeFromExtension(extension) {
2579
+ if (!extension || typeof extension !== 'string') {
2580
+ return null;
2581
+ }
2582
+ const normalizedExtension = extension.toLowerCase().trim();
2583
+ return EXTENSION_MAPPINGS[normalizedExtension] || null;
2584
+ }
2585
+ function determineFileType(options) {
2586
+ const { fileData, fileTypeResolver } = options;
2587
+ const { contentType, key } = fileData ?? {};
2588
+ if (typeof fileTypeResolver === 'function') {
2589
+ try {
2590
+ const customResult = fileTypeResolver(fileData);
2591
+ if (customResult !== undefined && customResult !== null) {
2592
+ return customResult;
2490
2593
  }
2491
2594
  }
2492
- }), [sortState, tableData]);
2493
- const sortedRows = React__namespace["default"].useMemo(() => {
2494
- // Early return if there is no table data
2495
- if (!tableData) {
2496
- return;
2595
+ catch (error) {
2596
+ //
2497
2597
  }
2498
- // Return rows as is if there are no sortable columns
2499
- if (sortState.index < 0) {
2500
- return tableData.rows;
2598
+ }
2599
+ if (!isGenericContentType(contentType)) {
2600
+ const typeFromContentType = getFileTypeFromContentType(contentType);
2601
+ if (typeFromContentType) {
2602
+ return typeFromContentType;
2501
2603
  }
2502
- const { index, direction } = sortState;
2503
- const groupedRows = {
2504
- button: [],
2505
- checkbox: [],
2506
- date: [],
2507
- number: [],
2508
- text: [],
2509
- };
2510
- tableData.rows.forEach((row) => {
2511
- const { type } = row.content[index];
2512
- groupedRows[type].push(row);
2513
- });
2514
- const groupOrder = direction === 'ascending' ? GROUP_ORDER : [...GROUP_ORDER].reverse();
2515
- return groupOrder
2516
- .map((groupType) => {
2517
- if (UNSORTABLE_GROUPS.includes(groupType)) {
2518
- return groupedRows[groupType];
2519
- }
2520
- return groupedRows[groupType].sort((rowA, rowB) => {
2521
- switch (groupType) {
2522
- case 'button': {
2523
- return compareButtonData(rowA.content[index], rowB.content[index], direction);
2524
- }
2525
- case 'date': {
2526
- return compareDateData(rowA.content[index], rowB.content[index], direction);
2527
- }
2528
- case 'number': {
2529
- return compareNumberData(rowA.content[index], rowB.content[index], direction);
2530
- }
2531
- case 'text':
2532
- default: {
2533
- return compareTextData(rowA.content[index], rowB.content[index], direction);
2534
- }
2535
- }
2536
- });
2537
- })
2538
- .flat();
2539
- }, [sortState, tableData]);
2540
- return {
2541
- headers: mappedHeaders ?? [],
2542
- isLoading,
2543
- rows: sortedRows ?? [],
2544
- };
2545
- };
2604
+ }
2605
+ const extension = getFileExtension(key);
2606
+ if (extension) {
2607
+ return getFileTypeFromExtension(extension);
2608
+ }
2609
+ return null;
2610
+ }
2546
2611
 
2547
- const DataTableControl = () => {
2548
- const props = useDataTable();
2549
- const Resolved = useResolvedComposable(DataTable, 'DataTable');
2550
- return React__namespace["default"].createElement(Resolved, { ...props });
2551
- };
2612
+ const LOCATION_DETAIL_VIEW_HEADERS = [
2613
+ 'checkbox',
2614
+ 'name',
2615
+ 'type',
2616
+ 'last-modified',
2617
+ 'size',
2618
+ 'download',
2619
+ ];
2620
+ const GENERIC_FILE_ICON = 'file';
2621
+ const EXTENSION_THUMBNAIL_MAPPINGS = {
2622
+ // Images
2623
+ jpg: 'file-image',
2624
+ jpeg: 'file-image',
2625
+ png: 'file-image',
2626
+ gif: 'file-image',
2627
+ webp: 'file-image',
2628
+ svg: 'file-image',
2629
+ bmp: 'file-image',
2630
+ tiff: 'file-image',
2631
+ tif: 'file-image',
2632
+ ico: 'file-image',
2633
+ heic: 'file-image',
2634
+ heif: 'file-image',
2635
+ avif: 'file-image',
2636
+ // Videos
2637
+ mp4: 'file-video',
2638
+ avi: 'file-video',
2639
+ mov: 'file-video',
2640
+ wmv: 'file-video',
2641
+ flv: 'file-video',
2642
+ webm: 'file-video',
2643
+ mkv: 'file-video',
2644
+ m4v: 'file-video',
2645
+ mpg: 'file-video',
2646
+ mpeg: 'file-video',
2647
+ '3gp': 'file-video',
2648
+ ogv: 'file-video',
2649
+ // Plain text files only
2650
+ txt: 'file-text',
2651
+ log: 'file-text',
2652
+ json: 'file-text',
2653
+ xml: 'file-text',
2654
+ yaml: 'file-text',
2655
+ yml: 'file-text',
2656
+ ini: 'file-text',
2657
+ conf: 'file-text',
2658
+ cfg: 'file-text',
2659
+ // Audio
2660
+ mp3: 'file-audio',
2661
+ wav: 'file-audio',
2662
+ flac: 'file-audio',
2663
+ aac: 'file-audio',
2664
+ ogg: 'file-audio',
2665
+ wma: 'file-audio',
2666
+ m4a: 'file-audio',
2667
+ // Documents
2668
+ pdf: 'file-pdf',
2669
+ // Excel
2670
+ xls: 'file-excel',
2671
+ xlsx: 'file-excel',
2672
+ csv: 'file-excel',
2673
+ // Word
2674
+ doc: 'file-word',
2675
+ docx: 'file-word',
2676
+ rtf: 'file-word',
2677
+ // PowerPoint
2678
+ ppt: 'file-powerpoint',
2679
+ pptx: 'file-powerpoint',
2680
+ // Archives
2681
+ zip: 'file-archive',
2682
+ rar: 'file-archive',
2683
+ '7z': 'file-archive',
2684
+ tar: 'file-archive',
2685
+ gz: 'file-archive',
2686
+ bz2: 'file-archive',
2687
+ };
2688
+
2689
+ function getFileThumbnail(fileKey) {
2690
+ const extension = getFileExtension(fileKey);
2691
+ if (!fileKey || !extension)
2692
+ return GENERIC_FILE_ICON;
2693
+ return EXTENSION_THUMBNAIL_MAPPINGS[extension] || GENERIC_FILE_ICON;
2694
+ }
2552
2695
 
2553
- /**
2554
- * This hook, not to be confused with the useDropZone vended from @aws-amplify/ui-react-core, is only intended for use
2555
- * with its corresponding DropZone control.
2556
- */
2557
- const useDropZone = () => {
2558
- const { onDropFiles } = useControlsContext();
2559
- return { onDropFiles };
2560
- };
2696
+ function PreviewFallback({ fileKey, message, description, isError = false, onRetry, showRetry = false, }) {
2697
+ const { LocationDetailView: displayText } = useDisplayText();
2698
+ const { filePreview: { errorDescription, unsupportedFileDescription, filePrefix, retryButtonLabel, }, } = displayText;
2699
+ const fileName = getFileName(fileKey);
2700
+ const fallbackClass = isError
2701
+ ? `${STORAGE_BROWSER_BLOCK}__preview-fallback--error`
2702
+ : `${STORAGE_BROWSER_BLOCK}__preview-fallback--default`;
2703
+ return (React__namespace["default"].createElement(ViewElement, { className: ui.classNames(`${STORAGE_BROWSER_BLOCK}__preview-fallback`, fallbackClass) },
2704
+ React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__preview-fallback-icon` },
2705
+ React__namespace["default"].createElement(IconElement, { className: "amplify-icon", variant: isError ? 'error' : getFileThumbnail(fileKey) })),
2706
+ React__namespace["default"].createElement(ViewElement, null,
2707
+ React__namespace["default"].createElement(TextElement, { className: `${STORAGE_BROWSER_BLOCK}__preview-fallback-title` }, message),
2708
+ React__namespace["default"].createElement(TextElement, { className: `${STORAGE_BROWSER_BLOCK}__preview-fallback-description` }, description ??
2709
+ (isError ? errorDescription : unsupportedFileDescription)),
2710
+ React__namespace["default"].createElement(TextElement, { className: `${STORAGE_BROWSER_BLOCK}__preview-fallback-filename` },
2711
+ filePrefix,
2712
+ fileName)),
2713
+ React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__preview-fallback-actions` },
2714
+ showRetry && onRetry && (React__namespace["default"].createElement(ButtonElement, { variant: "primary", onClick: onRetry, ['aria-label']: `Retry loading ${fileName} file` }, retryButtonLabel)),
2715
+ React__namespace["default"].createElement(DownloadButton, { fileKey: fileKey }))));
2716
+ }
2561
2717
 
2562
- const DropZoneControl = ({ children, }) => {
2563
- const props = useDropZone();
2564
- const Resolved = useResolvedComposable(DropZone, 'DropZone');
2565
- return React__namespace["default"].createElement(Resolved, { ...props }, children);
2566
- };
2718
+ function ImagePreview({ url, fileKey, }) {
2719
+ const [error, setError] = React.useState(null);
2720
+ const [isLoading, setIsLoading] = React.useState(true);
2721
+ const [imageKey, setImageKey] = React.useState(0);
2722
+ const { LocationDetailView: displayText } = useDisplayText();
2723
+ const { filePreview: { imageLoadErrorDescription }, } = displayText;
2724
+ const handleError = React.useCallback(() => {
2725
+ setError('Failed to load image');
2726
+ setIsLoading(false);
2727
+ }, []);
2728
+ const handleLoad = React.useCallback(() => {
2729
+ setIsLoading(false);
2730
+ setError(null);
2731
+ }, []);
2732
+ const handleRetry = React.useCallback(() => {
2733
+ setError(null);
2734
+ setIsLoading(true);
2735
+ setImageKey((prev) => prev + 1);
2736
+ }, []);
2737
+ if (error) {
2738
+ return (React__namespace["default"].createElement(PreviewFallback, { fileKey: fileKey, message: error, description: imageLoadErrorDescription, isError: true, onRetry: handleRetry, showRetry: true }));
2739
+ }
2740
+ return (React__namespace["default"].createElement("div", { className: `${STORAGE_BROWSER_BLOCK}__image-container` },
2741
+ isLoading && React__namespace["default"].createElement(PreviewPlaceholder, null),
2742
+ React__namespace["default"].createElement("div", { className: `${STORAGE_BROWSER_BLOCK}__image-preview`, style: { display: isLoading ? 'none' : 'flex' } },
2743
+ React__namespace["default"].createElement("img", { key: imageKey, className: ui.classNames(ui.ComponentClassName.StorageImage), src: url, alt: `Image preview for ${getFileName(fileKey)}`, onError: handleError, onLoad: handleLoad }))));
2744
+ }
2567
2745
 
2568
- const useFolderNameField = () => {
2569
- const { data, onValidateFolderName, onFolderNameChange } = useControlsContext();
2570
- const { folderNameId, folderNameLabel, folderNamePlaceholder, folderNameValidationMessage, isFolderNameDisabled, } = data;
2571
- return {
2572
- id: folderNameId,
2573
- isDisabled: isFolderNameDisabled,
2574
- label: folderNameLabel,
2575
- onChange: onFolderNameChange,
2576
- onValidate: onValidateFolderName,
2577
- placeholder: folderNamePlaceholder,
2578
- validationMessage: folderNameValidationMessage,
2579
- };
2580
- };
2746
+ const { FilePreviewContext, useFilePreview: useFilePreviewContext } = uiReactCore.createContextUtilities({
2747
+ contextName: 'FilePreview',
2748
+ defaultValue: {},
2749
+ });
2750
+ function FilePreviewProvider({ children, filePreview, }) {
2751
+ return (React__namespace["default"].createElement(FilePreviewContext.Provider, { value: filePreview }, children));
2752
+ }
2581
2753
 
2582
- const FolderNameFieldControl = () => {
2583
- const props = useFolderNameField();
2584
- const Resolved = useResolvedComposable(FolderNameField$1, 'FolderNameField');
2585
- return React__namespace["default"].createElement(Resolved, { ...props });
2586
- };
2754
+ function VideoPreview({ url, fileKey, }) {
2755
+ const [error, setError] = React.useState(null);
2756
+ const [isLoading, setIsLoading] = React.useState(true);
2757
+ const [videoKey, setVideoKey] = React.useState(0);
2758
+ const { LocationDetailView: displayText } = useDisplayText();
2759
+ const { filePreview: { videoLoadErrorDescription }, } = displayText;
2760
+ const handleError = React.useCallback((event) => {
2761
+ const videoElement = event.currentTarget;
2762
+ const errorCode = videoElement.error?.code;
2763
+ let errorMessage = 'Failed to load video';
2764
+ switch (errorCode) {
2765
+ case MediaError.MEDIA_ERR_ABORTED:
2766
+ errorMessage = 'Video loading was aborted';
2767
+ break;
2768
+ case MediaError.MEDIA_ERR_NETWORK:
2769
+ errorMessage = 'Network error occurred while loading video';
2770
+ break;
2771
+ case MediaError.MEDIA_ERR_DECODE:
2772
+ errorMessage = 'Video format is not supported or corrupted';
2773
+ break;
2774
+ case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:
2775
+ errorMessage = 'Video format is not supported';
2776
+ break;
2777
+ default:
2778
+ errorMessage = 'An unknown error occurred while loading video';
2779
+ }
2780
+ setError(errorMessage);
2781
+ setIsLoading(false);
2782
+ }, []);
2783
+ const handleLoadStart = React.useCallback(() => {
2784
+ setIsLoading(true);
2785
+ setError(null);
2786
+ }, []);
2787
+ const handleLoadedData = React.useCallback(() => {
2788
+ setIsLoading(false);
2789
+ setError(null);
2790
+ }, []);
2791
+ const handleRetry = React.useCallback(() => {
2792
+ setError(null);
2793
+ setIsLoading(true);
2794
+ setVideoKey((prev) => prev + 1);
2795
+ }, []);
2796
+ if (error) {
2797
+ return (React__namespace["default"].createElement(PreviewFallback, { fileKey: fileKey, message: error, description: videoLoadErrorDescription, isError: true, onRetry: handleRetry, showRetry: true }));
2798
+ }
2799
+ return (React__namespace["default"].createElement("div", { className: `${STORAGE_BROWSER_BLOCK}__video-container` },
2800
+ isLoading && React__namespace["default"].createElement(PreviewPlaceholder, null),
2801
+ React__namespace["default"].createElement("div", { className: `${STORAGE_BROWSER_BLOCK}__video-preview`, style: { display: isLoading ? 'none' : 'flex' } },
2802
+ React__namespace["default"].createElement("video", { key: videoKey, controls: true, preload: "metadata", onError: handleError, onLoadStart: handleLoadStart, onLoadedData: handleLoadedData, "aria-label": `Video preview for ${getFileName(fileKey)}` },
2803
+ React__namespace["default"].createElement("source", { src: url }),
2804
+ "Your browser does not support the video tag."))));
2805
+ }
2587
2806
 
2588
- const useLoadingIndicator = () => {
2589
- const { data: { isLoading, loadingIndicatorLabel: label }, } = useControlsContext();
2590
- return { isLoading, label };
2591
- };
2807
+ function TextPreview({ url, fileKey, }) {
2808
+ const [content, setContent] = React.useState('');
2809
+ const [isLoading, setIsLoading] = React.useState(true);
2810
+ const [error, setError] = React.useState(null);
2811
+ const [retryCount, setRetryCount] = React.useState(0);
2812
+ const { LocationDetailView: displayText } = useDisplayText();
2813
+ const { filePreview: { emptyFileMessage, textLoadErrorDescription }, } = displayText;
2814
+ const handleRetry = React.useCallback(() => {
2815
+ setRetryCount((prev) => prev + 1);
2816
+ }, []);
2817
+ React.useEffect(() => {
2818
+ if (!url)
2819
+ return;
2820
+ const controller = new AbortController();
2821
+ async function loadTextFileContent() {
2822
+ try {
2823
+ setIsLoading(true);
2824
+ setError(null);
2825
+ const response = await fetch(url, {
2826
+ signal: controller.signal,
2827
+ });
2828
+ if (!response.ok) {
2829
+ throw new Error(`Failed to fetch file: ${response.statusText}`);
2830
+ }
2831
+ const textContent = await response.text();
2832
+ setContent(textContent);
2833
+ }
2834
+ catch (err) {
2835
+ if (err instanceof Error && err.name !== 'AbortError') {
2836
+ const errorMessage = err.message || 'Failed to load file';
2837
+ setError(errorMessage);
2838
+ }
2839
+ }
2840
+ finally {
2841
+ if (!controller.signal.aborted) {
2842
+ setIsLoading(false);
2843
+ }
2844
+ }
2845
+ }
2846
+ loadTextFileContent();
2847
+ return () => {
2848
+ controller.abort();
2849
+ };
2850
+ }, [url, fileKey, retryCount]);
2851
+ if (isLoading) {
2852
+ return React__namespace["default"].createElement(PreviewPlaceholder, null);
2853
+ }
2854
+ if (error) {
2855
+ return (React__namespace["default"].createElement(PreviewFallback, { fileKey: fileKey, message: error, description: textLoadErrorDescription, isError: true, onRetry: handleRetry, showRetry: true }));
2856
+ }
2857
+ return (React__namespace["default"].createElement("div", { className: `${STORAGE_BROWSER_BLOCK}__text-container` },
2858
+ React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__text-preview` }, content || emptyFileMessage)));
2859
+ }
2592
2860
 
2593
- const LoadingIndicatorControl = () => {
2594
- const props = useLoadingIndicator();
2595
- const Resolved = useResolvedComposable(LoadingIndicator$1, 'LoadingIndicator');
2596
- return React__namespace["default"].createElement(Resolved, { ...props });
2597
- };
2861
+ const NONE = 'None';
2862
+ function FileMetadata({ fileData, }) {
2863
+ const { key, lastModified, versionId = NONE, size, eTag } = fileData;
2864
+ const { LocationDetailView: displayText } = useDisplayText();
2865
+ const { filePreview: { keyLabel, sizeLabel, versionIdLabel, lastModifiedLabel, entityTagLabel, typeLabel, fileInformationTitle, unknownValue, }, } = displayText;
2866
+ return (React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__file-preview-section` },
2867
+ React__namespace["default"].createElement(HeadingElement, { className: `${STORAGE_BROWSER_BLOCK}__file-preview-title` }, fileInformationTitle),
2868
+ React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__file-metadata` }, [
2869
+ { label: keyLabel, value: key },
2870
+ {
2871
+ label: sizeLabel,
2872
+ value: ui.humanFileSize(size, true),
2873
+ },
2874
+ { label: versionIdLabel, value: versionId },
2875
+ {
2876
+ label: lastModifiedLabel,
2877
+ value: lastModified?.toLocaleString() || unknownValue,
2878
+ },
2879
+ { label: entityTagLabel, value: eTag },
2880
+ {
2881
+ label: typeLabel,
2882
+ value: getFileExtension(key) ?? NONE,
2883
+ },
2884
+ ].map(({ label, value }) => (React__namespace["default"].createElement(ViewElement, { key: label, className: `${STORAGE_BROWSER_BLOCK}__file-metadata-item` },
2885
+ React__namespace["default"].createElement(TextElement, { className: `${STORAGE_BROWSER_BLOCK}__file-metadata-label` },
2886
+ label,
2887
+ ":"),
2888
+ React__namespace["default"].createElement(TextElement, { className: `${STORAGE_BROWSER_BLOCK}__file-metadata-value` }, value)))))));
2889
+ }
2598
2890
 
2599
- const useMessage = () => {
2600
- const { data: { message = {} }, } = useControlsContext();
2601
- return message;
2602
- };
2891
+ function FilePreviewLayout({ fileData, children, }) {
2892
+ const { LocationDetailView: displayText } = useDisplayText();
2893
+ const { filePreview: { filePreviewTitle }, } = displayText;
2894
+ return (React__namespace["default"].createElement(React__namespace["default"].Fragment, null,
2895
+ React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__file-preview-section` },
2896
+ React__namespace["default"].createElement(HeadingElement, { className: `${STORAGE_BROWSER_BLOCK}__file-preview-title` }, filePreviewTitle),
2897
+ React__namespace["default"].createElement("div", { className: `${STORAGE_BROWSER_BLOCK}__file-preview-content` }, children)),
2898
+ React__namespace["default"].createElement(FileMetadata, { fileData: fileData })));
2899
+ }
2603
2900
 
2604
- const MessageControl = () => {
2605
- const props = useMessage();
2606
- const Resolved = useResolvedComposable(Message, 'Message');
2607
- return React__namespace["default"].createElement(Resolved, { ...props });
2901
+ const rendererMap = {
2902
+ image: ImagePreview,
2903
+ video: VideoPreview,
2904
+ text: TextPreview,
2608
2905
  };
2609
-
2610
- const useNavigation = () => {
2611
- const { data, onNavigate, onNavigateHome } = useControlsContext();
2612
- const { location } = data;
2613
- return React__namespace["default"].useMemo(() => {
2614
- if (!location?.current) {
2615
- return { items: [] };
2906
+ const DefaultRenderer = ({ url, type, fileKey }) => {
2907
+ const { LocationDetailView: displayText } = useDisplayText();
2908
+ const { filePreview: { unsupportedFileDescription, unsupportedFileMessage }, } = displayText;
2909
+ if (type && type in rendererMap) {
2910
+ const PreviewComponent = rendererMap[type];
2911
+ return React__namespace["default"].createElement(PreviewComponent, { fileKey: fileKey, url: url });
2912
+ }
2913
+ return (React__namespace["default"].createElement(PreviewFallback, { fileKey: fileKey, message: unsupportedFileMessage, description: unsupportedFileDescription }));
2914
+ };
2915
+ const ResolvedRenderer = ({ fileKey, url, fileData }) => {
2916
+ const { rendererResolver } = (useFilePreviewContext() ?? {}) || {};
2917
+ if (rendererResolver && fileData.fileType) {
2918
+ const CustomRenderer = rendererResolver(fileData.fileType);
2919
+ if (CustomRenderer) {
2920
+ return React__namespace["default"].createElement(CustomRenderer, { url: url, fileData: fileData });
2616
2921
  }
2617
- const { current, path } = location;
2618
- const destinationParts = getNavigationParts({
2619
- location: current,
2620
- path,
2621
- includeBucketInPrefix: true,
2622
- });
2623
- const homeItem = [
2624
- { name: 'Home', onNavigate: onNavigateHome },
2625
- ];
2626
- return {
2627
- items: homeItem.concat(getNavigationItems({ location: current, destinationParts, onNavigate })),
2628
- };
2629
- }, [location, onNavigate, onNavigateHome]);
2630
- };
2631
-
2632
- const NavigationControl = () => {
2633
- const props = useNavigation();
2634
- const Resolved = useResolvedComposable(Navigation$1, 'Navigation');
2635
- return React__namespace["default"].createElement(Resolved, { ...props });
2922
+ }
2923
+ return (React__namespace["default"].createElement(DefaultRenderer, { fileKey: fileKey, url: url, type: fileData.fileType }));
2636
2924
  };
2925
+ const FilePreviewContent = ({ filePreview, activeFile, onRetryFilePreview, onSelectActiveFile, activeFileHasNext, activeFileHasPrev, }) => {
2926
+ const { LocationDetailView: displayText } = useDisplayText();
2927
+ const { filePreview: { errorMessage, sizeLimitMessage, generalPreviewErrorDescription, fileSizeLimitDescription, }, } = displayText;
2928
+ const { key } = activeFile;
2929
+ if (filePreview.isLoading) {
2930
+ return (React__namespace["default"].createElement(FilePreviewLayout, { fileData: activeFile },
2931
+ React__namespace["default"].createElement(PreviewPlaceholder, null)));
2932
+ }
2933
+ if (!filePreview.ok) {
2934
+ if (filePreview.error === 'LIMIT_EXCEEDED') {
2935
+ return (React__namespace["default"].createElement(FilePreviewLayout, { fileData: activeFile },
2936
+ React__namespace["default"].createElement(PreviewFallback, { fileKey: key, message: sizeLimitMessage, description: fileSizeLimitDescription })));
2937
+ }
2938
+ return (React__namespace["default"].createElement(FilePreviewLayout, { fileData: activeFile },
2939
+ React__namespace["default"].createElement(PreviewFallback, { fileKey: key, message: errorMessage, description: generalPreviewErrorDescription, isError: true, onRetry: onRetryFilePreview, showRetry: true })));
2940
+ }
2941
+ return (React__namespace["default"].createElement(FilePreviewLayout, { fileData: filePreview.fileData },
2942
+ React__namespace["default"].createElement(ResolvedRenderer, { fileKey: key, url: filePreview.url, fileData: filePreview.fileData }),
2943
+ React__namespace["default"].createElement("div", { style: { display: 'flex', flexFlow: 'row nowrap' } },
2944
+ React__namespace["default"].createElement(ButtonElement, { disabled: !activeFileHasPrev, onClick: () => onSelectActiveFile('prev'), variant: 'paginate-previous' },
2945
+ React__namespace["default"].createElement(IconElement, { variant: 'paginate-previous' })),
2946
+ React__namespace["default"].createElement(DownloadButton, { fileKey: key }),
2947
+ React__namespace["default"].createElement(ButtonElement, { disabled: !activeFileHasNext, onClick: () => onSelectActiveFile('next'), variant: 'paginate-next' },
2948
+ React__namespace["default"].createElement(IconElement, { variant: 'paginate-next' })))));
2949
+ };
2950
+ function FilePreview(props) {
2951
+ const { filePreview, activeFileHasNext, activeFileHasPrev, onRetryFilePreview = () => undefined, activeFile, onSelectActiveFile = () => undefined, } = props;
2952
+ const { LocationDetailView: displayText } = useDisplayText();
2953
+ const { filePreview: { closeButtonLabel }, } = displayText;
2954
+ if (!activeFile || !filePreview) {
2955
+ return null;
2956
+ }
2957
+ if (!filePreview.enabled) {
2958
+ return null;
2959
+ }
2960
+ return (React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__file-preview` },
2961
+ React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__file-preview-header` },
2962
+ React__namespace["default"].createElement(ButtonElement, { variant: "exit", onClick: () => onSelectActiveFile(undefined) },
2963
+ React__namespace["default"].createElement(IconElement, { variant: "dismiss" }),
2964
+ closeButtonLabel)),
2965
+ React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__file-preview-container` },
2966
+ React__namespace["default"].createElement(FilePreviewContent, { filePreview: filePreview, activeFile: activeFile, activeFileHasNext: activeFileHasNext, activeFileHasPrev: activeFileHasPrev, onRetryFilePreview: onRetryFilePreview, onSelectActiveFile: onSelectActiveFile }))));
2967
+ }
2637
2968
 
2638
- const useOverwriteToggle = () => {
2639
- const { data: { isOverwritingEnabled, isOverwriteToggleDisabled, overwriteToggleLabel, }, onToggleOverwrite, } = useControlsContext();
2640
- return {
2641
- isDisabled: isOverwriteToggleDisabled,
2642
- isOverwritingEnabled,
2643
- label: overwriteToggleLabel,
2644
- onToggle: onToggleOverwrite,
2645
- };
2646
- };
2969
+ const OVERWRITE_TOGGLE_ID = 'overwrite-toggle';
2970
+ const OverwriteToggle$1 = ({ isOverwritingEnabled, isDisabled, label, onToggle, }) => (React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__overwrite-toggle` },
2971
+ React__namespace["default"].createElement(InputElement, { checked: isOverwritingEnabled, disabled: isDisabled, id: OVERWRITE_TOGGLE_ID, onChange: onToggle, type: "checkbox" }),
2972
+ React__namespace["default"].createElement(LabelElement, { htmlFor: OVERWRITE_TOGGLE_ID }, label)));
2647
2973
 
2648
- const OverwriteToggleControl = () => {
2649
- const props = useOverwriteToggle();
2650
- const Resolved = useResolvedComposable(OverwriteToggle$1, 'OverwriteToggle');
2651
- return React__namespace["default"].createElement(Resolved, { ...props });
2974
+ const Pagination$1 = ({ page, hasNextPage, onPaginate, highestPageVisited, }) => {
2975
+ if (!page)
2976
+ return null;
2977
+ return (React__namespace["default"].createElement(NavElement, { "aria-label": 'Pagination', className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__pagination` },
2978
+ React__namespace["default"].createElement(OrderedListElement, { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__pagination-list` },
2979
+ React__namespace["default"].createElement(ListItemElement, { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__pagination-list-item` },
2980
+ React__namespace["default"].createElement(PaginationButton, { isDisabled: page <= 1, onClick: () => {
2981
+ if (onPaginate)
2982
+ onPaginate(page - 1);
2983
+ }, type: "previous" })),
2984
+ React__namespace["default"].createElement(ListItemElement, { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__pagination-list-item` },
2985
+ React__namespace["default"].createElement(SpanElement, { "aria-label": `Page ${page}`, "aria-current": "page", className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__pagination-current-page` }, page)),
2986
+ React__namespace["default"].createElement(ListItemElement, { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__pagination-list-item` },
2987
+ React__namespace["default"].createElement(PaginationButton, { isDisabled: !highestPageVisited ||
2988
+ (page >= highestPageVisited && !hasNextPage), onClick: () => {
2989
+ if (onPaginate)
2990
+ onPaginate(page + 1);
2991
+ }, type: "next" })))));
2652
2992
  };
2653
2993
 
2654
- const usePagination = () => {
2655
- const { data, onPaginate } = useControlsContext();
2656
- const { paginationData } = data;
2657
- return { ...paginationData, onPaginate };
2994
+ const SearchField$1 = ({ id, label, clearLabel, submitLabel, onSearch, onClear, placeholder, query = '', onQueryChange, }) => {
2995
+ // FIXME: focus not returning to input field after clear
2996
+ return (React__namespace["default"].createElement(React__namespace["default"].Fragment, null,
2997
+ React__namespace["default"].createElement(Field, { id: id, label: label, icon: React__namespace["default"].createElement(IconElement, { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__search-field-icon`, variant: "search" }), className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__search-field`, variant: "search", onChange: (e) => {
2998
+ onQueryChange?.(e.target.value);
2999
+ }, placeholder: placeholder, onKeyUp: (event) => {
3000
+ if (event.key === 'Enter') {
3001
+ onSearch?.();
3002
+ }
3003
+ }, value: query }, query ? (React__namespace["default"].createElement(ButtonElement, { "aria-label": clearLabel, className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__search-field-clear`, onClick: onClear, variant: "refresh" },
3004
+ React__namespace["default"].createElement(IconElement, { variant: "dismiss" }))) : null),
3005
+ React__namespace["default"].createElement(ButtonElement, { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__search-submit`, onClick: onSearch }, submitLabel)));
2658
3006
  };
2659
3007
 
2660
- const PaginationControl = () => {
2661
- const props = usePagination();
2662
- const Resolved = useResolvedComposable(Pagination$1, 'Pagination');
2663
- return React__namespace["default"].createElement(Resolved, { ...props });
2664
- };
3008
+ const SEARCH_SUBFOLDERS_TOGGLE_ID = 'search-subfolders-toggle';
3009
+ const SearchSubfoldersToggle$1 = ({ isSearchingSubfolders, label, onToggle, }) => (React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__search-subfolders-toggle` },
3010
+ React__namespace["default"].createElement(InputElement, { checked: isSearchingSubfolders, id: SEARCH_SUBFOLDERS_TOGGLE_ID, onChange: onToggle, type: "checkbox" }),
3011
+ React__namespace["default"].createElement(LabelElement, { htmlFor: SEARCH_SUBFOLDERS_TOGGLE_ID }, label)));
2665
3012
 
2666
- const useSearchField = () => {
2667
- const { data, onSearch, onSearchClear, onSearchQueryChange } = useControlsContext();
2668
- const { searchPlaceholder, searchClearLabel, searchQuery, searchSubmitLabel, } = data;
2669
- return {
2670
- clearLabel: searchClearLabel,
2671
- placeholder: searchPlaceholder,
2672
- query: searchQuery,
2673
- submitLabel: searchSubmitLabel,
2674
- onClear: onSearchClear,
2675
- onQueryChange: onSearchQueryChange,
2676
- onSearch,
2677
- };
3013
+ const StatusDisplay$1 = ({ statuses, total, }) => {
3014
+ if (!statuses?.length) {
3015
+ return null;
3016
+ }
3017
+ const descriptions = statuses.map(({ name, count }) => ({
3018
+ term: name,
3019
+ details: `${count}/${total}`,
3020
+ }));
3021
+ return (React__namespace["default"].createElement(DescriptionList, { className: `${STORAGE_BROWSER_BLOCK}__status-display`, descriptions: descriptions }));
2678
3022
  };
2679
3023
 
2680
- const SearchFieldControl = () => {
2681
- const props = useSearchField();
2682
- const Resolved = useResolvedComposable(SearchField$1, 'SearchField');
2683
- return React__namespace["default"].createElement(Resolved, { ...props });
2684
- };
3024
+ const Title$1 = ({ title }) => (React__namespace["default"].createElement(HeadingElement, { className: `${STORAGE_BROWSER_BLOCK}__title` }, title));
2685
3025
 
2686
- const useSearchSubfoldersToggle = () => {
2687
- const { data: { isSearchingSubfolders, searchSubfoldersToggleLabel }, onToggleSearchSubfolders, } = useControlsContext();
2688
- return {
2689
- isSearchingSubfolders,
2690
- label: searchSubfoldersToggleLabel,
2691
- onToggle: onToggleSearchSubfolders,
2692
- };
3026
+ const DEFAULT_COMPOSABLES = {
3027
+ ActionCancel,
3028
+ ActionDestination: ActionDestination$1,
3029
+ ActionExit,
3030
+ ActionStart,
3031
+ ActionsList: ActionsList$1,
3032
+ AddFiles,
3033
+ AddFolder,
3034
+ DataRefresh: DataRefresh$1,
3035
+ DataTable,
3036
+ DropZone,
3037
+ FolderNameField: FolderNameField$1,
3038
+ LoadingIndicator: LoadingIndicator$1,
3039
+ Message,
3040
+ Navigation: Navigation$1,
3041
+ OverwriteToggle: OverwriteToggle$1,
3042
+ Pagination: Pagination$1,
3043
+ SearchSubfoldersToggle: SearchSubfoldersToggle$1,
3044
+ SearchField: SearchField$1,
3045
+ StatusDisplay: StatusDisplay$1,
3046
+ Title: Title$1,
3047
+ FilePreview,
2693
3048
  };
2694
3049
 
2695
- const SearchSubfoldersToggleControl = () => {
2696
- const props = useSearchSubfoldersToggle();
2697
- const Resolved = useResolvedComposable(SearchSubfoldersToggle$1, 'SearchSubfoldersToggle');
2698
- return React__namespace["default"].createElement(Resolved, { ...props });
2699
- };
3050
+ function ComponentsProvider(props) {
3051
+ const { children, composables } = props;
3052
+ return (React__namespace["default"].createElement(elements.ElementsProvider, { elements: elementsDefault },
3053
+ React__namespace["default"].createElement(ComposablesProvider, { composables: composables }, children)));
3054
+ }
2700
3055
 
2701
- const useStatusDisplay = () => {
2702
- const { data } = useControlsContext();
2703
- const { statusCounts, statusDisplayCanceledLabel, statusDisplayCompletedLabel, statusDisplayFailedLabel, statusDisplayQueuedLabel, } = data;
2704
- if (!statusCounts?.TOTAL) {
2705
- return { statuses: [], total: 0 };
2706
- }
2707
- const statuses = [
2708
- { name: statusDisplayCompletedLabel ?? '', count: statusCounts.COMPLETE },
2709
- { name: statusDisplayFailedLabel ?? '', count: statusCounts.FAILED },
2710
- { name: statusDisplayCanceledLabel ?? '', count: statusCounts.CANCELED },
2711
- { name: statusDisplayQueuedLabel ?? '', count: statusCounts.QUEUED },
2712
- ];
2713
- return { statuses, total: statusCounts.TOTAL };
3056
+ const OverwriteToggle = ({ isDisabled, isOverwritingEnabled, label = '', onToggle, }) => {
3057
+ return (React__namespace.createElement(uiReact.CheckboxField, { name: label, label: label, labelPosition: "end", isDisabled: isDisabled, checked: isOverwritingEnabled, onChange: () => {
3058
+ onToggle?.();
3059
+ } }));
2714
3060
  };
2715
-
2716
- const StatusDisplayControl = () => {
2717
- const props = useStatusDisplay();
2718
- const Resolved = useResolvedComposable(StatusDisplay$1, 'StatusDisplay');
2719
- return React__namespace["default"].createElement(Resolved, { ...props });
3061
+ const SearchSubfoldersToggle = ({ isSearchingSubfolders, label = '', onToggle }) => {
3062
+ return (React__namespace.createElement(uiReact.CheckboxField, { name: label, label: label, labelPosition: "end", checked: isSearchingSubfolders, onChange: () => {
3063
+ onToggle?.();
3064
+ } }));
2720
3065
  };
2721
-
2722
- const useTitle = () => {
2723
- const { data } = useControlsContext();
2724
- return {
2725
- title: data?.title,
2726
- };
3066
+ const Pagination = ({ page = 1, onPaginate, hasNextPage, highestPageVisited, }) => {
3067
+ return (React__namespace.createElement(uiReact.Pagination, { currentPage: page, totalPages: highestPageVisited ?? 1, hasMorePages: hasNextPage, siblingCount: 1, onChange: (index) => {
3068
+ onPaginate?.(index ?? 0);
3069
+ }, onNext: () => {
3070
+ onPaginate?.(page + 1);
3071
+ }, onPrevious: () => {
3072
+ onPaginate?.(page - 1);
3073
+ } }));
2727
3074
  };
2728
-
2729
- const TitleControl = () => {
2730
- const props = useTitle();
2731
- const Resolved = useResolvedComposable(Title$1, 'Title');
2732
- return React__namespace["default"].createElement(Resolved, { ...props });
3075
+ const SearchField = ({ onQueryChange, onSearch, onClear, placeholder, label, query, }) => {
3076
+ return (React__namespace.createElement(uiReact.SearchField, { label: label, size: "small", clearButtonLabel: "Clear search", placeholder: placeholder, value: query, onChange: (e) => {
3077
+ onQueryChange?.(e.target.value);
3078
+ }, onSubmit: () => {
3079
+ onSearch?.();
3080
+ }, onClear: () => {
3081
+ onClear?.();
3082
+ } }));
2733
3083
  };
2734
-
2735
- const DEFAULT_ACTION_VIEW_DISPLAY_TEXT = {
2736
- actionCancelLabel: 'Cancel',
2737
- actionExitLabel: 'Exit',
2738
- actionDestinationLabel: 'Destination',
2739
- statusDisplayCanceledLabel: 'Canceled',
2740
- statusDisplayCompletedLabel: 'Completed',
2741
- statusDisplayFailedLabel: 'Failed',
2742
- statusDisplayInProgressLabel: 'In progress',
2743
- statusDisplayTotalLabel: 'Total',
2744
- statusDisplayQueuedLabel: 'Not started',
2745
- // empty by default
2746
- tableColumnCancelHeader: '',
2747
- tableColumnStatusHeader: 'Status',
2748
- tableColumnFolderHeader: 'Folder',
2749
- tableColumnNameHeader: 'Name',
2750
- tableColumnTypeHeader: 'Type',
2751
- tableColumnSizeHeader: 'Size',
3084
+ const Navigation = ({ items }) => {
3085
+ return (React__namespace.createElement(uiReact.Breadcrumbs.Container, null, items.map((item, i) => {
3086
+ return (React__namespace.createElement(uiReact.Breadcrumbs.Item, { key: i },
3087
+ React__namespace.createElement(uiReact.Breadcrumbs.Link, { as: item.isCurrent ? 'span' : 'button', isCurrent: item.isCurrent, onClick: item.onNavigate }, item.name),
3088
+ item.isCurrent ? null : React__namespace.createElement(uiReact.Breadcrumbs.Separator, null)));
3089
+ })));
2752
3090
  };
2753
- const DEFAULT_LIST_VIEW_DISPLAY_TEXT = {
2754
- loadingIndicatorLabel: 'Loading',
2755
- searchSubmitLabel: 'Submit',
2756
- searchClearLabel: 'Clear search',
2757
- getDateDisplayValue: (date) => new Intl.DateTimeFormat('en-US', {
2758
- month: 'short',
2759
- day: 'numeric',
2760
- hour: 'numeric',
2761
- year: 'numeric',
2762
- minute: 'numeric',
2763
- hourCycle: 'h12',
2764
- }).format(date),
3091
+ const LoadingIndicator = ({ isLoading, }) => {
3092
+ if (isLoading) {
3093
+ return (React__namespace.createElement(uiReact.Loader, { className: "amplify-storage-browser__loader", variation: "linear", size: "small" }));
3094
+ }
2765
3095
  };
2766
-
2767
- const DEFAULT_CREATE_FOLDER_VIEW_DISPLAY_TEXT = {
2768
- ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
2769
- title: 'Create folder',
2770
- actionStartLabel: 'Create folder',
2771
- folderNameLabel: 'Folder name',
2772
- folderNamePlaceholder: 'Folder name cannot contain "/", nor end or start with "."',
2773
- getValidationMessage: () => 'Folder name cannot contain "/", nor end or start with "."',
2774
- getActionCompleteMessage: (data) => {
2775
- const { counts } = data ?? {};
2776
- const { FAILED, OVERWRITE_PREVENTED } = counts ?? {};
2777
- if (OVERWRITE_PREVENTED) {
2778
- return {
2779
- content: 'A folder already exists with the provided name',
2780
- type: 'warning',
2781
- };
2782
- }
2783
- if (FAILED) {
2784
- return {
2785
- content: 'There was an issue creating the folder.',
2786
- type: 'error',
2787
- };
2788
- }
2789
- return { content: 'Folder created.', type: 'success' };
2790
- },
3096
+ const FolderNameField = ({ onChange, label, placeholder, validationMessage, onValidate, }) => {
3097
+ const handleValidate = ({ target: { value }, }) => {
3098
+ onValidate?.(value);
3099
+ };
3100
+ return (React__namespace.createElement(uiReact.TextField, { label: label, placeholder: placeholder, errorMessage: validationMessage, hasError: !!validationMessage, onBlur: handleValidate, onChange: (event) => {
3101
+ const { value } = event.target;
3102
+ handleValidate?.(event);
3103
+ onChange?.(value);
3104
+ } }));
3105
+ };
3106
+ const DataRefresh = ({ onRefresh, }) => {
3107
+ return (React__namespace.createElement(uiReact.Button, { onClick: () => {
3108
+ onRefresh?.();
3109
+ }, "aria-label": "Refresh data" },
3110
+ React__namespace.createElement(IconElement, { className: "amplify-icon", variant: "refresh" })));
3111
+ };
3112
+ const ActionsList = ({ items, onActionSelect, isDisabled, }) => {
3113
+ return (React__namespace.createElement(uiReact.Menu, { isDisabled: isDisabled, trigger: React__namespace.createElement(uiReact.Button, { ariaLabel: "Menu Toggle" },
3114
+ React__namespace.createElement(IconElement, { className: "amplify-icon", variant: "menu" })) }, items
3115
+ .filter(({ isHidden }) => !isHidden)
3116
+ .map(({ actionType, icon, label, isDisabled }, i) => {
3117
+ return (React__namespace.createElement(uiReact.MenuItem, { key: i, size: "small", gap: "xs", isDisabled: isDisabled, onClick: () => {
3118
+ onActionSelect?.(actionType);
3119
+ } },
3120
+ icon && React__namespace.createElement(IconElement, { variant: icon }),
3121
+ label));
3122
+ })));
3123
+ };
3124
+ const StatusDisplay = ({ statuses, total, }) => {
3125
+ if (!statuses?.length) {
3126
+ return null;
3127
+ }
3128
+ return (React__namespace.createElement(uiReact.View, { as: "dl", className: `${STORAGE_BROWSER_BLOCK}__status-display` }, statuses.map(({ name, count }, i) => (React__namespace.createElement(uiReact.View, { as: "div", className: `${STORAGE_BROWSER_BLOCK}__status`, key: i },
3129
+ React__namespace.createElement(uiReact.View, { as: "dt", className: `${STORAGE_BROWSER_BLOCK}__status-label` }, name),
3130
+ React__namespace.createElement(uiReact.View, { as: "dd", className: `${STORAGE_BROWSER_BLOCK}__status-value` }, `${count}/${total}`))))));
3131
+ };
3132
+ const ActionDestination = ({ isNavigable, items, label, }) => {
3133
+ if (!items.length) {
3134
+ return null;
3135
+ }
3136
+ return (React__namespace.createElement(uiReact.View, { as: "dl", className: `${STORAGE_BROWSER_BLOCK}__destination` },
3137
+ React__namespace.createElement(uiReact.View, { as: "dt", className: `${STORAGE_BROWSER_BLOCK}__destination-label` }, label),
3138
+ React__namespace.createElement(uiReact.View, { as: "dd", className: `${STORAGE_BROWSER_BLOCK}__destination-value` },
3139
+ React__namespace.createElement(uiReact.Breadcrumbs.Container, null, items.map((item, i) => {
3140
+ return (React__namespace.createElement(uiReact.Breadcrumbs.Item, { key: i },
3141
+ isNavigable ? (React__namespace.createElement(uiReact.Breadcrumbs.Link, { as: item.isCurrent ? 'span' : 'button', isCurrent: item.isCurrent, onClick: item.onNavigate }, item.name)) : (item.name),
3142
+ item.isCurrent ? null : React__namespace.createElement(uiReact.Breadcrumbs.Separator, null)));
3143
+ })))));
3144
+ };
3145
+ const Title = ({ title }) => {
3146
+ return (React__namespace.createElement(uiReact.Heading, { className: `${STORAGE_BROWSER_BLOCK}__title`, level: 2 }, title));
3147
+ };
3148
+ const componentsDefault = {
3149
+ ActionDestination,
3150
+ ActionsList,
3151
+ DataRefresh,
3152
+ LoadingIndicator,
3153
+ Pagination,
3154
+ Navigation,
3155
+ OverwriteToggle,
3156
+ SearchField,
3157
+ SearchSubfoldersToggle,
3158
+ StatusDisplay,
3159
+ FolderNameField,
3160
+ Title,
2791
3161
  };
2792
3162
 
2793
- const DEFAULT_COPY_VIEW_DISPLAY_TEXT = {
2794
- ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
2795
- title: 'Copy',
2796
- actionStartLabel: 'Copy',
2797
- actionDestinationLabel: 'Copy destination',
2798
- getListFoldersResultsMessage: ({ folders, query, message, hasError, hasExhaustedSearch, }) => {
2799
- if (!folders?.length) {
2800
- return {
2801
- content: query
2802
- ? `No folders found matching "${query}"`
2803
- : 'No subfolders found within selected folder.',
2804
- type: 'info',
2805
- };
2806
- }
2807
- if (message && !!query) {
2808
- return { content: 'Error loading folders.', type: 'error' };
2809
- }
3163
+ const Fallback = () => (React__namespace["default"].createElement("div", { className: STORAGE_BROWSER_BLOCK_TO_BE_UPDATED },
3164
+ React__namespace["default"].createElement("div", { className: `${STORAGE_BROWSER_BLOCK_TO_BE_UPDATED}__error-boundary` }, "Something went wrong.")));
3165
+ class ErrorBoundary extends React__namespace["default"].Component {
3166
+ constructor(props) {
3167
+ super(props);
3168
+ this.state = { hasError: false };
3169
+ }
3170
+ static getDerivedStateFromError(_error) {
3171
+ // Update state so the next render will show the fallback UI.
3172
+ return { hasError: true };
3173
+ }
3174
+ render() {
3175
+ const { hasError } = this.state;
3176
+ const { children } = this.props;
2810
3177
  if (hasError) {
2811
- return { content: 'Error loading folders.', type: 'error' };
2812
- }
2813
- if (hasExhaustedSearch) {
2814
- return {
2815
- content: 'Showing results for up to the first 10,000 items.',
2816
- type: 'info',
2817
- };
2818
- }
2819
- },
2820
- loadingIndicatorLabel: 'Loading',
2821
- overwriteWarningMessage: 'Copied files will overwrite existing files at selected destination.',
2822
- searchPlaceholder: 'Search for folders',
2823
- getActionCompleteMessage: (data) => {
2824
- const { counts } = data ?? {};
2825
- const { COMPLETE, FAILED, TOTAL } = counts ?? {};
2826
- if (COMPLETE === TOTAL) {
2827
- return {
2828
- content: 'All files copied.',
2829
- type: 'success',
2830
- };
2831
- }
2832
- if (FAILED === TOTAL) {
2833
- return { content: 'All files failed to copy.', type: 'error' };
3178
+ return React__namespace["default"].createElement(Fallback, null);
2834
3179
  }
2835
- return {
2836
- content: `${COMPLETE} files copied, ${FAILED} files failed to copy.`,
2837
- type: 'error',
3180
+ return children;
3181
+ }
3182
+ }
3183
+
3184
+ const defaultValue$5 = { data: {} };
3185
+ const { useControlsContext, ControlsContextProvider } = uiReactCore.createContextUtilities({
3186
+ contextName: 'ControlsContext',
3187
+ defaultValue: defaultValue$5,
3188
+ });
3189
+
3190
+ const useActionCancel = () => {
3191
+ const { data: { actionCancelLabel, isActionCancelDisabled }, onActionCancel, } = useControlsContext();
3192
+ return {
3193
+ onCancel: onActionCancel,
3194
+ isDisabled: isActionCancelDisabled,
3195
+ label: actionCancelLabel,
3196
+ };
3197
+ };
3198
+
3199
+ function useResolvedComposable(DefaultComposable, name) {
3200
+ const { composables } = useComposables();
3201
+ const Composable = React__namespace["default"].useMemo(() => {
3202
+ const ResolvedComposable = (props) => {
3203
+ const Resolved = composables?.[name] ?? DefaultComposable;
3204
+ return React__namespace["default"].createElement(Resolved, { ...props });
2838
3205
  };
2839
- },
2840
- searchSubmitLabel: 'Submit',
2841
- searchClearLabel: 'Clear search',
3206
+ ResolvedComposable.displayName = name;
3207
+ return ResolvedComposable;
3208
+ }, [composables, DefaultComposable, name]);
3209
+ return Composable;
3210
+ }
3211
+
3212
+ const ActionCancelControl = () => {
3213
+ const props = useActionCancel();
3214
+ const Resolved = useResolvedComposable(ActionCancel, 'ActionCancel');
3215
+ return React__namespace["default"].createElement(Resolved, { ...props });
2842
3216
  };
2843
3217
 
2844
- const DEFAULT_DELETE_VIEW_DISPLAY_TEXT = {
2845
- ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
2846
- title: 'Delete',
2847
- actionStartLabel: 'Delete',
2848
- getActionCompleteMessage: (data) => {
2849
- const { counts } = data ?? {};
2850
- const { COMPLETE, FAILED, TOTAL } = counts ?? {};
2851
- if (COMPLETE === TOTAL) {
2852
- return { content: 'All files deleted.', type: 'success' };
2853
- }
2854
- if (FAILED === TOTAL) {
2855
- return { content: 'All files failed to delete.', type: 'error' };
3218
+ const getNavigationItems = ({ destinationParts, location, onNavigate, }) => {
3219
+ const { bucket, permissions, prefix = '', type } = location;
3220
+ const destinationSubpaths = [];
3221
+ return destinationParts.map((part, index) => {
3222
+ const isCurrent = index === destinationParts.length - 1;
3223
+ if (index !== 0) {
3224
+ destinationSubpaths.push(part);
2856
3225
  }
3226
+ const destinationPath = `${destinationSubpaths.concat('').join('/')}`;
3227
+ const destination = {
3228
+ id: crypto.randomUUID(),
3229
+ type,
3230
+ permissions,
3231
+ bucket,
3232
+ prefix,
3233
+ };
2857
3234
  return {
2858
- content: `${COMPLETE} files deleted, ${FAILED} files failed to delete.`,
2859
- type: 'error',
3235
+ name: part,
3236
+ ...(isCurrent && { isCurrent }),
3237
+ onNavigate: () => {
3238
+ onNavigate?.(destination, destinationPath);
3239
+ },
2860
3240
  };
2861
- },
3241
+ });
2862
3242
  };
2863
3243
 
2864
- const DEFAULT_ERROR_MESSAGE$1 = 'There was an error loading items.';
2865
- const DEFAULT_LOCATION_DETAIL_VIEW_DISPLAY_TEXT = {
2866
- ...DEFAULT_LIST_VIEW_DISPLAY_TEXT,
2867
- getListItemsResultMessage: (data) => {
2868
- const { items, hasExhaustedSearch, hasError = false, message, isLoading, } = data ?? {};
2869
- if (isLoading) {
2870
- return undefined;
2871
- }
2872
- if (hasError) {
2873
- return {
2874
- type: 'error',
2875
- content: message ?? DEFAULT_ERROR_MESSAGE$1,
2876
- };
3244
+ const getNavigationParts = ({ location, path, includeBucketInPrefix, }) => {
3245
+ const { bucket, prefix = '', type } = location;
3246
+ const trimmedPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
3247
+ const trimmedPath = path.endsWith('/') ? path.slice(0, -1) : path;
3248
+ const firstPrefixPart = [];
3249
+ if (type !== 'BUCKET') {
3250
+ if (includeBucketInPrefix) {
3251
+ firstPrefixPart.push(bucket);
2877
3252
  }
2878
- if (!items?.length && hasExhaustedSearch) {
2879
- return {
2880
- type: 'info',
2881
- content: `No results found in the first 10,000 items.`,
2882
- };
3253
+ if (trimmedPrefix) {
3254
+ if (includeBucketInPrefix) {
3255
+ firstPrefixPart.push('/');
3256
+ }
3257
+ firstPrefixPart.push(trimmedPrefix);
2883
3258
  }
2884
- if (!items?.length) {
2885
- return {
2886
- type: 'info',
2887
- content: 'No files.',
2888
- };
2889
- }
2890
- if (hasExhaustedSearch) {
2891
- return {
2892
- type: 'info',
2893
- content: `Showing results for up to the first 10,000 items.`,
2894
- };
2895
- }
2896
- // TODO: add more cases as needed
2897
- return undefined;
2898
- },
2899
- searchSubfoldersToggleLabel: 'Include subfolders',
2900
- searchPlaceholder: 'Search current folder',
2901
- tableColumnLastModifiedHeader: 'Last modified',
2902
- tableColumnNameHeader: 'Name',
2903
- tableColumnSizeHeader: 'Size',
2904
- tableColumnTypeHeader: 'Type',
2905
- selectFileLabel: 'Select file',
2906
- selectAllFilesLabel: 'Select all files',
2907
- getActionListItemLabel: (key = '') => {
2908
- switch (key) {
2909
- case 'Copy':
2910
- return 'Copy';
2911
- case 'Delete':
2912
- return 'Delete';
2913
- case 'Create folder':
2914
- return 'Create folder';
2915
- case 'Upload':
2916
- return 'Upload';
2917
- case 'Download':
2918
- return 'Download';
2919
- default:
2920
- return key;
2921
- }
2922
- },
2923
- getTitle: (location) => {
2924
- const { current, key } = location;
2925
- const { bucket = '' } = current ?? {};
2926
- return key || bucket;
2927
- },
3259
+ }
3260
+ const prefixParts = type === 'BUCKET' ? [bucket] : [firstPrefixPart.join('')];
3261
+ if (type === 'BUCKET' && trimmedPrefix) {
3262
+ prefixParts.push(trimmedPrefix);
3263
+ }
3264
+ const pathParts = trimmedPath ? trimmedPath.split('/') : [];
3265
+ return prefixParts.concat(pathParts);
2928
3266
  };
2929
3267
 
2930
- const DEFAULT_ERROR_MESSAGE = 'There was an error loading locations.';
2931
- const DEFAULT_LOCATIONS_VIEW_DISPLAY_TEXT = {
2932
- ...DEFAULT_LIST_VIEW_DISPLAY_TEXT,
2933
- title: 'Home',
2934
- searchPlaceholder: 'Filter folders and files',
2935
- getListLocationsResultMessage: (data) => {
2936
- const { isLoading, items, hasExhaustedSearch, hasError = false, message, } = data ?? {};
2937
- if (isLoading) {
2938
- return undefined;
2939
- }
2940
- if (hasError) {
2941
- return {
2942
- type: 'error',
2943
- content: message ?? DEFAULT_ERROR_MESSAGE,
2944
- };
2945
- }
2946
- if (items?.length === 0 && !hasExhaustedSearch) {
2947
- return {
2948
- type: 'info',
2949
- content: 'No folders or files.',
2950
- };
2951
- }
2952
- if (hasExhaustedSearch) {
2953
- return {
2954
- type: 'info',
2955
- content: `Showing results for up to the first 10,000 items.`,
2956
- };
2957
- }
2958
- // TODO: add more cases as needed
2959
- return undefined;
2960
- },
2961
- getPermissionName: (permissions) => {
2962
- let text = '';
2963
- if (permissions.includes('get') || permissions.includes('list')) {
2964
- text = 'Read';
2965
- }
2966
- if (permissions.includes('write') || permissions.includes('delete')) {
2967
- text = text ? 'Read/Write' : 'Write';
2968
- }
2969
- if (!text) {
2970
- text = permissions.join('/');
3268
+ const useActionDestination = () => {
3269
+ const { data, onSelectDestination } = useControlsContext();
3270
+ const { actionDestinationLabel, isActionDestinationNavigable, destination } = data;
3271
+ return React__namespace["default"].useMemo(() => {
3272
+ if (!destination?.current) {
3273
+ return { items: [] };
2971
3274
  }
2972
- return text;
2973
- },
2974
- getDownloadLabel: (fileName) => `Download ${fileName}`,
2975
- tableColumnBucketHeader: 'Bucket',
2976
- tableColumnFolderHeader: 'Folder',
2977
- tableColumnPermissionsHeader: 'Permissions',
2978
- tableColumnActionsHeader: 'Actions',
3275
+ const { current, path } = destination;
3276
+ const destinationParts = getNavigationParts({
3277
+ location: current,
3278
+ path,
3279
+ });
3280
+ return {
3281
+ label: actionDestinationLabel,
3282
+ items: getNavigationItems({
3283
+ location: current,
3284
+ destinationParts,
3285
+ onNavigate: onSelectDestination,
3286
+ }),
3287
+ isNavigable: isActionDestinationNavigable,
3288
+ };
3289
+ }, [
3290
+ actionDestinationLabel,
3291
+ isActionDestinationNavigable,
3292
+ destination,
3293
+ onSelectDestination,
3294
+ ]);
2979
3295
  };
2980
3296
 
2981
- const DEFAULT_UPLOAD_VIEW_DISPLAY_TEXT = {
2982
- ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
2983
- actionStartLabel: 'Upload',
2984
- addFilesLabel: 'Add files',
2985
- addFolderLabel: 'Add folder',
2986
- getActionCompleteMessage: (data) => {
2987
- const { counts } = data ?? {};
2988
- const { COMPLETE, FAILED, OVERWRITE_PREVENTED, CANCELED, TOTAL } = counts ?? {};
2989
- const hasPreventedOverwrite = !!OVERWRITE_PREVENTED;
2990
- const hasFailure = !!FAILED;
2991
- const hasSuccess = !!COMPLETE;
2992
- const hasCanceled = !!CANCELED;
2993
- const type = hasFailure
2994
- ? 'error'
2995
- : hasPreventedOverwrite || hasCanceled
2996
- ? 'warning'
2997
- : 'success';
2998
- const preventedOverwriteMessage = hasPreventedOverwrite
2999
- ? [
3000
- 'Overwrite prevented for',
3001
- OVERWRITE_PREVENTED === TOTAL ? 'all' : String(OVERWRITE_PREVENTED),
3002
- OVERWRITE_PREVENTED > 1 || OVERWRITE_PREVENTED === TOTAL
3003
- ? `files`
3004
- : 'file',
3005
- ].join(' ')
3006
- : undefined;
3007
- const canceledMessage = hasCanceled
3008
- ? [
3009
- CANCELED === TOTAL ? 'All' : String(CANCELED),
3010
- CANCELED > 1 || CANCELED === TOTAL ? `uploads` : 'upload',
3011
- 'canceled',
3012
- ].join(' ')
3013
- : undefined;
3014
- const failedMessage = hasFailure
3015
- ? [
3016
- FAILED === TOTAL ? 'All' : String(FAILED),
3017
- FAILED > 1 || FAILED === TOTAL ? `files` : 'file',
3018
- 'failed to upload',
3019
- ].join(' ')
3020
- : undefined;
3021
- const completedMessage = hasSuccess
3022
- ? [
3023
- COMPLETE === TOTAL ? 'All' : String(COMPLETE),
3024
- COMPLETE > 1 || COMPLETE === TOTAL ? `files` : 'file',
3025
- 'uploaded',
3026
- ].join(' ')
3027
- : undefined;
3028
- const messages = [
3029
- preventedOverwriteMessage,
3030
- failedMessage,
3031
- canceledMessage,
3032
- completedMessage,
3033
- ].filter(Boolean);
3034
- if (messages.length > 0) {
3035
- return {
3036
- content: messages.join(', ') + '.',
3037
- type,
3038
- };
3297
+ const ActionDestinationControl = () => {
3298
+ const props = useActionDestination();
3299
+ const Resolved = useResolvedComposable(ActionDestination$1, 'ActionDestination');
3300
+ return React__namespace["default"].createElement(Resolved, { ...props });
3301
+ };
3302
+
3303
+ const useActionExit = () => {
3304
+ const { data: { actionExitLabel: label, isActionExitDisabled: isDisabled }, onActionExit: onExit, } = useControlsContext();
3305
+ return { label, isDisabled, onExit };
3306
+ };
3307
+
3308
+ const ActionExitControl = () => {
3309
+ const props = useActionExit();
3310
+ const Resolved = useResolvedComposable(ActionExit, 'ActionExit');
3311
+ return React__namespace["default"].createElement(Resolved, { ...props });
3312
+ };
3313
+
3314
+ const useActionsList = () => {
3315
+ const { data: { actions, isActionsListDisabled }, onActionSelect, } = useControlsContext();
3316
+ return {
3317
+ isDisabled: isActionsListDisabled,
3318
+ items: actions ?? [],
3319
+ onActionSelect,
3320
+ };
3321
+ };
3322
+
3323
+ const ActionsListControl = () => {
3324
+ const props = useActionsList();
3325
+ const Resolved = useResolvedComposable(ActionsList$1, 'ActionsList');
3326
+ return React__namespace["default"].createElement(Resolved, { ...props });
3327
+ };
3328
+
3329
+ const useActionStart = () => {
3330
+ const { data: { actionStartLabel, isActionStartDisabled }, onActionStart, } = useControlsContext();
3331
+ return {
3332
+ label: actionStartLabel,
3333
+ isDisabled: isActionStartDisabled,
3334
+ onStart: onActionStart,
3335
+ };
3336
+ };
3337
+
3338
+ const ActionStartControl = () => {
3339
+ const props = useActionStart();
3340
+ const Resolved = useResolvedComposable(ActionStart, 'ActionStart');
3341
+ return React__namespace["default"].createElement(Resolved, { ...props });
3342
+ };
3343
+
3344
+ const useAddFiles = () => {
3345
+ const { data: { addFilesLabel, isAddFilesDisabled }, onAddFiles, } = useControlsContext();
3346
+ return {
3347
+ isDisabled: isAddFilesDisabled,
3348
+ label: addFilesLabel,
3349
+ onAddFiles,
3350
+ };
3351
+ };
3352
+
3353
+ const AddFilesControl = () => {
3354
+ const props = useAddFiles();
3355
+ const Resolved = useResolvedComposable(AddFiles, 'AddFiles');
3356
+ return React__namespace["default"].createElement(Resolved, { ...props });
3357
+ };
3358
+
3359
+ const useAddFolder = () => {
3360
+ const { data: { addFolderLabel, isAddFolderDisabled }, onAddFolder, } = useControlsContext();
3361
+ return {
3362
+ isDisabled: isAddFolderDisabled,
3363
+ label: addFolderLabel,
3364
+ onAddFolder,
3365
+ };
3366
+ };
3367
+
3368
+ const AddFolderControl = () => {
3369
+ const props = useAddFolder();
3370
+ const Resolved = useResolvedComposable(AddFolder, 'AddFolder');
3371
+ return React__namespace["default"].createElement(Resolved, { ...props });
3372
+ };
3373
+
3374
+ const useDataRefresh = () => {
3375
+ const { data: { isDataRefreshDisabled }, onRefresh, } = useControlsContext();
3376
+ return { isDisabled: isDataRefreshDisabled, onRefresh };
3377
+ };
3378
+
3379
+ const DataRefreshControl = () => {
3380
+ const props = useDataRefresh();
3381
+ const Resolved = useResolvedComposable(DataRefresh$1, 'DataRefresh');
3382
+ return React__namespace["default"].createElement(Resolved, { ...props });
3383
+ };
3384
+
3385
+ const compareContent$3 = ({ label: a }, { label: b }) => {
3386
+ if (a === undefined) {
3387
+ return b === undefined ? 0 : 1;
3388
+ }
3389
+ return b === undefined ? -1 : a.localeCompare(b);
3390
+ };
3391
+ const compareButtonData = (a, b, direction) => direction === 'ascending'
3392
+ ? compareContent$3(a.content, b.content)
3393
+ : compareContent$3(b.content, a.content);
3394
+
3395
+ const compareContent$2 = ({ value: a }, { value: b }) => {
3396
+ if (a === undefined) {
3397
+ return b === undefined ? 0 : 1;
3398
+ }
3399
+ return b === undefined ? -1 : a.getTime() - b.getTime();
3400
+ };
3401
+ const compareDateData = (a, b, direction) => direction === 'ascending'
3402
+ ? compareContent$2(a.content, b.content)
3403
+ : compareContent$2(b.content, a.content);
3404
+
3405
+ const compareContent$1 = ({ value: a }, { value: b }) => {
3406
+ if (a === undefined) {
3407
+ return b === undefined ? 0 : 1;
3408
+ }
3409
+ return b === undefined ? -1 : a - b;
3410
+ };
3411
+ const compareNumberData = (a, b, direction) => direction === 'ascending'
3412
+ ? compareContent$1(a.content, b.content)
3413
+ : compareContent$1(b.content, a.content);
3414
+
3415
+ const compareContent = ({ text: a }, { text: b }) => {
3416
+ if (a === undefined) {
3417
+ return b === undefined ? 0 : 1;
3418
+ }
3419
+ return b === undefined ? -1 : a.localeCompare(b);
3420
+ };
3421
+ const compareTextData = (a, b, direction) => direction === 'ascending'
3422
+ ? compareContent(a.content, b.content)
3423
+ : compareContent(b.content, a.content);
3424
+
3425
+ const GROUP_ORDER = [
3426
+ 'checkbox',
3427
+ 'button',
3428
+ 'date',
3429
+ 'number',
3430
+ 'text',
3431
+ ];
3432
+ const UNSORTABLE_GROUPS = ['checkbox'];
3433
+ const useDataTable = () => {
3434
+ const { data } = useControlsContext();
3435
+ const { isLoading, tableData } = data;
3436
+ const defaultSortIndex = React__namespace["default"].useMemo(() => tableData?.headers?.findIndex(({ type }) => type === 'sort') ?? -1, [tableData]);
3437
+ const [sortState, setSortState] = React__namespace["default"].useState({
3438
+ index: defaultSortIndex,
3439
+ direction: 'ascending',
3440
+ });
3441
+ const mappedHeaders = React__namespace["default"].useMemo(() => tableData?.headers.map((header, index) => {
3442
+ const { type } = header;
3443
+ switch (type) {
3444
+ case 'sort': {
3445
+ return {
3446
+ ...header,
3447
+ content: {
3448
+ ...header.content,
3449
+ onSort: () => {
3450
+ setSortState({
3451
+ index,
3452
+ direction: sortState.index === index
3453
+ ? sortState.direction === 'ascending'
3454
+ ? 'descending'
3455
+ : 'ascending'
3456
+ : 'ascending',
3457
+ });
3458
+ },
3459
+ sortDirection: sortState.index === index ? sortState.direction : undefined,
3460
+ },
3461
+ };
3462
+ }
3463
+ case 'checkbox':
3464
+ case 'text':
3465
+ default: {
3466
+ return header;
3467
+ }
3039
3468
  }
3040
- return { content: 'All files uploaded.', type };
3041
- },
3042
- getFilesValidationMessage: (data) => {
3043
- if (!data?.invalidFiles) {
3044
- return undefined;
3469
+ }), [sortState, tableData]);
3470
+ const sortedRows = React__namespace["default"].useMemo(() => {
3471
+ // Early return if there is no table data
3472
+ if (!tableData) {
3473
+ return;
3045
3474
  }
3046
- const invalidFileNames = data.invalidFiles
3047
- .map(({ file }) => file.name)
3048
- .join(', ');
3049
- if (invalidFileNames) {
3050
- return {
3051
- content: `Files larger than 160GB cannot be added to the upload queue: ${invalidFileNames}`,
3052
- type: 'warning',
3053
- };
3475
+ // Return rows as is if there are no sortable columns
3476
+ if (sortState.index < 0) {
3477
+ return tableData.rows;
3054
3478
  }
3055
- return undefined;
3056
- },
3057
- overwriteToggleLabel: 'Overwrite existing files',
3058
- statusDisplayOverwritePreventedLabel: 'Overwrite prevented',
3059
- tableColumnProgressHeader: 'Progress',
3060
- title: 'Upload',
3479
+ const { index, direction } = sortState;
3480
+ const groupedRows = {
3481
+ button: [],
3482
+ checkbox: [],
3483
+ date: [],
3484
+ number: [],
3485
+ text: [],
3486
+ };
3487
+ tableData.rows.forEach((row) => {
3488
+ const { type } = row.content[index];
3489
+ groupedRows[type].push(row);
3490
+ });
3491
+ const groupOrder = direction === 'ascending' ? GROUP_ORDER : [...GROUP_ORDER].reverse();
3492
+ return groupOrder
3493
+ .map((groupType) => {
3494
+ if (UNSORTABLE_GROUPS.includes(groupType)) {
3495
+ return groupedRows[groupType];
3496
+ }
3497
+ return groupedRows[groupType].sort((rowA, rowB) => {
3498
+ switch (groupType) {
3499
+ case 'button': {
3500
+ return compareButtonData(rowA.content[index], rowB.content[index], direction);
3501
+ }
3502
+ case 'date': {
3503
+ return compareDateData(rowA.content[index], rowB.content[index], direction);
3504
+ }
3505
+ case 'number': {
3506
+ return compareNumberData(rowA.content[index], rowB.content[index], direction);
3507
+ }
3508
+ case 'text':
3509
+ default: {
3510
+ return compareTextData(rowA.content[index], rowB.content[index], direction);
3511
+ }
3512
+ }
3513
+ });
3514
+ })
3515
+ .flat();
3516
+ }, [sortState, tableData]);
3517
+ return {
3518
+ headers: mappedHeaders ?? [],
3519
+ isLoading,
3520
+ rows: sortedRows ?? [],
3521
+ };
3061
3522
  };
3062
3523
 
3063
- const DEFAULT_DOWNLOAD_VIEW_DISPLAY_TEXT = {
3064
- ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
3065
- title: 'Download',
3066
- actionStartLabel: 'Download',
3067
- getActionCompleteMessage: (data) => {
3068
- const { counts } = data ?? {};
3069
- const { COMPLETE, FAILED, TOTAL } = counts ?? {};
3070
- if (COMPLETE === TOTAL) {
3071
- return { content: 'All files downloaded.', type: 'success' };
3072
- }
3073
- if (FAILED === TOTAL) {
3074
- return { content: 'All files failed to download.', type: 'error' };
3524
+ const DataTableControl = () => {
3525
+ const props = useDataTable();
3526
+ const Resolved = useResolvedComposable(DataTable, 'DataTable');
3527
+ return React__namespace["default"].createElement(Resolved, { ...props });
3528
+ };
3529
+
3530
+ /**
3531
+ * This hook, not to be confused with the useDropZone vended from @aws-amplify/ui-react-core, is only intended for use
3532
+ * with its corresponding DropZone control.
3533
+ */
3534
+ const useDropZone = () => {
3535
+ const { onDropFiles } = useControlsContext();
3536
+ return { onDropFiles };
3537
+ };
3538
+
3539
+ const DropZoneControl = ({ children, }) => {
3540
+ const props = useDropZone();
3541
+ const Resolved = useResolvedComposable(DropZone, 'DropZone');
3542
+ return React__namespace["default"].createElement(Resolved, { ...props }, children);
3543
+ };
3544
+
3545
+ const useFolderNameField = () => {
3546
+ const { data, onValidateFolderName, onFolderNameChange } = useControlsContext();
3547
+ const { folderNameId, folderNameLabel, folderNamePlaceholder, folderNameValidationMessage, isFolderNameDisabled, } = data;
3548
+ return {
3549
+ id: folderNameId,
3550
+ isDisabled: isFolderNameDisabled,
3551
+ label: folderNameLabel,
3552
+ onChange: onFolderNameChange,
3553
+ onValidate: onValidateFolderName,
3554
+ placeholder: folderNamePlaceholder,
3555
+ validationMessage: folderNameValidationMessage,
3556
+ };
3557
+ };
3558
+
3559
+ const FolderNameFieldControl = () => {
3560
+ const props = useFolderNameField();
3561
+ const Resolved = useResolvedComposable(FolderNameField$1, 'FolderNameField');
3562
+ return React__namespace["default"].createElement(Resolved, { ...props });
3563
+ };
3564
+
3565
+ const useLoadingIndicator = () => {
3566
+ const { data: { isLoading, loadingIndicatorLabel: label }, } = useControlsContext();
3567
+ return { isLoading, label };
3568
+ };
3569
+
3570
+ const LoadingIndicatorControl = () => {
3571
+ const props = useLoadingIndicator();
3572
+ const Resolved = useResolvedComposable(LoadingIndicator$1, 'LoadingIndicator');
3573
+ return React__namespace["default"].createElement(Resolved, { ...props });
3574
+ };
3575
+
3576
+ const useMessage = () => {
3577
+ const { data: { message = {} }, } = useControlsContext();
3578
+ return message;
3579
+ };
3580
+
3581
+ const MessageControl = () => {
3582
+ const props = useMessage();
3583
+ const Resolved = useResolvedComposable(Message, 'Message');
3584
+ return React__namespace["default"].createElement(Resolved, { ...props });
3585
+ };
3586
+
3587
+ const useNavigation = () => {
3588
+ const { data, onNavigate, onNavigateHome } = useControlsContext();
3589
+ const { location } = data;
3590
+ return React__namespace["default"].useMemo(() => {
3591
+ if (!location?.current) {
3592
+ return { items: [] };
3075
3593
  }
3594
+ const { current, path } = location;
3595
+ const destinationParts = getNavigationParts({
3596
+ location: current,
3597
+ path,
3598
+ includeBucketInPrefix: true,
3599
+ });
3600
+ const homeItem = [
3601
+ { name: 'Home', onNavigate: onNavigateHome },
3602
+ ];
3076
3603
  return {
3077
- content: `${COMPLETE} files downloaded, ${FAILED} files failed to download.`,
3078
- type: 'error',
3604
+ items: homeItem.concat(getNavigationItems({ location: current, destinationParts, onNavigate })),
3079
3605
  };
3080
- },
3606
+ }, [location, onNavigate, onNavigateHome]);
3081
3607
  };
3082
3608
 
3083
- const DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT = {
3084
- CopyView: DEFAULT_COPY_VIEW_DISPLAY_TEXT,
3085
- CreateFolderView: DEFAULT_CREATE_FOLDER_VIEW_DISPLAY_TEXT,
3086
- DeleteView: DEFAULT_DELETE_VIEW_DISPLAY_TEXT,
3087
- DownloadView: DEFAULT_DOWNLOAD_VIEW_DISPLAY_TEXT,
3088
- LocationDetailView: DEFAULT_LOCATION_DETAIL_VIEW_DISPLAY_TEXT,
3089
- LocationsView: DEFAULT_LOCATIONS_VIEW_DISPLAY_TEXT,
3090
- UploadView: DEFAULT_UPLOAD_VIEW_DISPLAY_TEXT,
3609
+ const NavigationControl = () => {
3610
+ const props = useNavigation();
3611
+ const Resolved = useResolvedComposable(Navigation$1, 'Navigation');
3612
+ return React__namespace["default"].createElement(Resolved, { ...props });
3091
3613
  };
3092
3614
 
3093
- const { DisplayTextContext, useDisplayText } = uiReactCore.createContextUtilities({
3094
- contextName: 'DisplayText',
3095
- errorMessage: '`useDisplayText` must be called inside `DisplayTextProvider`',
3096
- });
3097
- function resolveDisplayText(displayText) {
3098
- if (!displayText)
3099
- return DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT;
3100
- // override
3101
- const { CopyView, CreateFolderView, DeleteView, DownloadView, LocationDetailView, LocationsView, UploadView, } = displayText;
3615
+ const useOverwriteToggle = () => {
3616
+ const { data: { isOverwritingEnabled, isOverwriteToggleDisabled, overwriteToggleLabel, }, onToggleOverwrite, } = useControlsContext();
3102
3617
  return {
3103
- CopyView: { ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.CopyView, ...CopyView },
3104
- CreateFolderView: {
3105
- ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.CreateFolderView,
3106
- ...CreateFolderView,
3107
- },
3108
- DeleteView: {
3109
- ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.DeleteView,
3110
- ...DeleteView,
3111
- },
3112
- DownloadView: {
3113
- ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.DownloadView,
3114
- ...DownloadView,
3115
- },
3116
- LocationDetailView: {
3117
- ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.LocationDetailView,
3118
- ...LocationDetailView,
3119
- },
3120
- LocationsView: {
3121
- ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.LocationsView,
3122
- ...LocationsView,
3123
- },
3124
- UploadView: {
3125
- ...DEFAULT_STORAGE_BROWSER_DISPLAY_TEXT.UploadView,
3126
- ...UploadView,
3127
- },
3618
+ isDisabled: isOverwriteToggleDisabled,
3619
+ isOverwritingEnabled,
3620
+ label: overwriteToggleLabel,
3621
+ onToggle: onToggleOverwrite,
3128
3622
  };
3129
- }
3130
- function DisplayTextProvider({ children, displayText: _override, }) {
3131
- // do deep merge here of default and override here
3132
- const resolvedDisplayText = React__namespace["default"].useMemo(() => resolveDisplayText(_override), [_override]);
3133
- return (React__namespace["default"].createElement(DisplayTextContext.Provider, { value: resolvedDisplayText }, children));
3134
- }
3623
+ };
3135
3624
 
3136
- const isCopyViewDisplayTextKey = (value) => !!DEFAULT_COPY_VIEW_DISPLAY_TEXT[value];
3137
- const isDeleteViewDisplayTextKey = (value) => !!DEFAULT_DELETE_VIEW_DISPLAY_TEXT[value];
3138
- const isDownloadViewDisplayTextKey = (value) => !!DEFAULT_DOWNLOAD_VIEW_DISPLAY_TEXT[value];
3625
+ const OverwriteToggleControl = () => {
3626
+ const props = useOverwriteToggle();
3627
+ const Resolved = useResolvedComposable(OverwriteToggle$1, 'OverwriteToggle');
3628
+ return React__namespace["default"].createElement(Resolved, { ...props });
3629
+ };
3630
+
3631
+ const usePagination = () => {
3632
+ const { data, onPaginate } = useControlsContext();
3633
+ const { paginationData } = data;
3634
+ return { ...paginationData, onPaginate };
3635
+ };
3636
+
3637
+ const PaginationControl = () => {
3638
+ const props = usePagination();
3639
+ const Resolved = useResolvedComposable(Pagination$1, 'Pagination');
3640
+ return React__namespace["default"].createElement(Resolved, { ...props });
3641
+ };
3642
+
3643
+ const useSearchField = () => {
3644
+ const { data, onSearch, onSearchClear, onSearchQueryChange } = useControlsContext();
3645
+ const { searchPlaceholder, searchClearLabel, searchQuery, searchSubmitLabel, } = data;
3646
+ return {
3647
+ clearLabel: searchClearLabel,
3648
+ placeholder: searchPlaceholder,
3649
+ query: searchQuery,
3650
+ submitLabel: searchSubmitLabel,
3651
+ onClear: onSearchClear,
3652
+ onQueryChange: onSearchQueryChange,
3653
+ onSearch,
3654
+ };
3655
+ };
3656
+
3657
+ const SearchFieldControl = () => {
3658
+ const props = useSearchField();
3659
+ const Resolved = useResolvedComposable(SearchField$1, 'SearchField');
3660
+ return React__namespace["default"].createElement(Resolved, { ...props });
3661
+ };
3662
+
3663
+ const useSearchSubfoldersToggle = () => {
3664
+ const { data: { isSearchingSubfolders, searchSubfoldersToggleLabel }, onToggleSearchSubfolders, } = useControlsContext();
3665
+ return {
3666
+ isSearchingSubfolders,
3667
+ label: searchSubfoldersToggleLabel,
3668
+ onToggle: onToggleSearchSubfolders,
3669
+ };
3670
+ };
3671
+
3672
+ const SearchSubfoldersToggleControl = () => {
3673
+ const props = useSearchSubfoldersToggle();
3674
+ const Resolved = useResolvedComposable(SearchSubfoldersToggle$1, 'SearchSubfoldersToggle');
3675
+ return React__namespace["default"].createElement(Resolved, { ...props });
3676
+ };
3677
+
3678
+ const useStatusDisplay = () => {
3679
+ const { data } = useControlsContext();
3680
+ const { statusCounts, statusDisplayCanceledLabel, statusDisplayCompletedLabel, statusDisplayFailedLabel, statusDisplayQueuedLabel, } = data;
3681
+ if (!statusCounts?.TOTAL) {
3682
+ return { statuses: [], total: 0 };
3683
+ }
3684
+ const statuses = [
3685
+ { name: statusDisplayCompletedLabel ?? '', count: statusCounts.COMPLETE },
3686
+ { name: statusDisplayFailedLabel ?? '', count: statusCounts.FAILED },
3687
+ { name: statusDisplayCanceledLabel ?? '', count: statusCounts.CANCELED },
3688
+ { name: statusDisplayQueuedLabel ?? '', count: statusCounts.QUEUED },
3689
+ ];
3690
+ return { statuses, total: statusCounts.TOTAL };
3691
+ };
3692
+
3693
+ const StatusDisplayControl = () => {
3694
+ const props = useStatusDisplay();
3695
+ const Resolved = useResolvedComposable(StatusDisplay$1, 'StatusDisplay');
3696
+ return React__namespace["default"].createElement(Resolved, { ...props });
3697
+ };
3698
+
3699
+ const useTitle = () => {
3700
+ const { data } = useControlsContext();
3701
+ return {
3702
+ title: data?.title,
3703
+ };
3704
+ };
3705
+
3706
+ const TitleControl = () => {
3707
+ const props = useTitle();
3708
+ const Resolved = useResolvedComposable(Title$1, 'Title');
3709
+ return React__namespace["default"].createElement(Resolved, { ...props });
3710
+ };
3139
3711
 
3140
3712
  function useResolveTableData(keys, { getCell, getHeader, getRowKey }, { items, props }) {
3141
3713
  return React__namespace["default"].useMemo(() => {
@@ -4503,16 +5075,7 @@ const LocationActionView = ({ type, ...props }) => {
4503
5075
  return null;
4504
5076
  };
4505
5077
 
4506
- const LOCATION_DETAIL_VIEW_HEADERS = [
4507
- 'checkbox',
4508
- 'name',
4509
- 'type',
4510
- 'last-modified',
4511
- 'size',
4512
- 'download',
4513
- ];
4514
-
4515
- const getFileRowContent = ({ permissions, isSelected, itemLocationKey, getDateDisplayValue, lastModified, rowId, rowKey, selectFileLabel, size, onDownload, onSelect, }) => LOCATION_DETAIL_VIEW_HEADERS.map((columnKey) => {
5078
+ const getFileRowContent = ({ filePreviewEnabled, permissions, isSelected, itemLocationKey, getDateDisplayValue, lastModified, rowId, rowKey, selectFileLabel, size, onDownload, onSelect, onClick, }) => LOCATION_DETAIL_VIEW_HEADERS.map((columnKey) => {
4516
5079
  const key = `${columnKey}-${rowId}`;
4517
5080
  switch (columnKey) {
4518
5081
  case 'checkbox': {
@@ -4530,11 +5093,13 @@ const getFileRowContent = ({ permissions, isSelected, itemLocationKey, getDateDi
4530
5093
  case 'name': {
4531
5094
  return {
4532
5095
  key,
4533
- type: 'text',
5096
+ type: filePreviewEnabled ? 'button' : 'text',
4534
5097
  content: {
4535
- icon: 'file',
4536
- ariaLabel: 'file',
4537
- text: rowKey.slice(itemLocationKey.length),
5098
+ icon: getFileThumbnail(rowKey),
5099
+ ariaLabel: `${rowKey.slice(itemLocationKey.length)} file`,
5100
+ ...(filePreviewEnabled
5101
+ ? { label: rowKey.slice(itemLocationKey.length), onClick }
5102
+ : { text: rowKey.slice(itemLocationKey.length) }),
4538
5103
  },
4539
5104
  };
4540
5105
  }
@@ -4678,7 +5243,7 @@ const getHeaders$1 = ({ tableColumnLastModifiedHeader, tableColumnNameHeader, ta
4678
5243
  }
4679
5244
  });
4680
5245
 
4681
- const getLocationDetailViewTableData = ({ areAllFilesSelected, displayText, location, fileDataItems, hasFiles, pageItems, selectFileLabel, selectAllFilesLabel, getDateDisplayValue, onDownload, onNavigate, onSelect, onSelectAll, }) => {
5246
+ const getLocationDetailViewTableData = ({ filePreviewEnabled, activeFile, onSelectActiveFile, areAllFilesSelected, displayText, location, fileDataItems, hasFiles, pageItems, selectFileLabel, selectAllFilesLabel, getDateDisplayValue, onDownload, onNavigate, onSelect, onSelectAll, }) => {
4682
5247
  const { tableColumnLastModifiedHeader, tableColumnNameHeader, tableColumnSizeHeader, tableColumnTypeHeader, } = displayText;
4683
5248
  const headers = getHeaders$1({
4684
5249
  areAllFilesSelected,
@@ -4703,9 +5268,14 @@ const getLocationDetailViewTableData = ({ areAllFilesSelected, displayText, loca
4703
5268
  const onFileSelect = () => {
4704
5269
  onSelect(isSelected, locationItem);
4705
5270
  };
5271
+ const onClick = () => {
5272
+ onSelectActiveFile(locationItem);
5273
+ };
4706
5274
  return {
4707
5275
  key: id,
5276
+ active: activeFile?.id === id,
4708
5277
  content: getFileRowContent({
5278
+ filePreviewEnabled,
4709
5279
  permissions: current?.permissions ?? [],
4710
5280
  isSelected,
4711
5281
  itemLocationKey: `${current?.prefix ?? ''}${path}`,
@@ -4717,6 +5287,7 @@ const getLocationDetailViewTableData = ({ areAllFilesSelected, displayText, loca
4717
5287
  size,
4718
5288
  onDownload: onFileDownload,
4719
5289
  onSelect: onFileSelect,
5290
+ onClick,
4720
5291
  }),
4721
5292
  };
4722
5293
  }
@@ -4732,6 +5303,7 @@ const getLocationDetailViewTableData = ({ areAllFilesSelected, displayText, loca
4732
5303
  };
4733
5304
  return {
4734
5305
  key: id,
5306
+ active: false,
4735
5307
  content: getFolderRowContent({
4736
5308
  itemSubPath,
4737
5309
  rowId: id,
@@ -4747,7 +5319,7 @@ const getLocationDetailViewTableData = ({ areAllFilesSelected, displayText, loca
4747
5319
  function LocationDetailViewProvider({ children, ...props }) {
4748
5320
  const { LocationDetailView: displayText } = useDisplayText();
4749
5321
  const { LocationDetailView: { loadingIndicatorLabel, searchSubfoldersToggleLabel, selectFileLabel, selectAllFilesLabel, searchPlaceholder, searchSubmitLabel, searchClearLabel, getActionListItemLabel, getDateDisplayValue, getTitle, getListItemsResultMessage, }, } = useDisplayText();
4750
- const { actionItems, page, pageItems, hasNextPage, highestPageVisited, isLoading, isSearchSubfoldersEnabled, location, fileDataItems, hasError, hasDownloadError, message, downloadErrorMessage, searchQuery, hasExhaustedSearch, onActionSelect, onDropFiles, onRefresh, onPaginate, onDownload, onNavigate, onNavigateHome, onSelect, onToggleSelectAll, onSearch, onSearchQueryChange, onSearchClear, onToggleSearchSubfolders, } = props;
5322
+ const { actionItems, activeFile, activeFileHasNext, activeFileHasPrev, page, pageItems, hasNextPage, highestPageVisited, isLoading, isSearchSubfoldersEnabled, location, fileDataItems, hasError, hasDownloadError, message, downloadErrorMessage, searchQuery, hasExhaustedSearch, onActionSelect, onDropFiles, onRefresh, onPaginate, onDownload, onNavigate, onNavigateHome, onSelect, onSelectActiveFile, onToggleSelectAll, onSearch, onSearchQueryChange, onSearchClear, onToggleSearchSubfolders, filePreviewState, filePreviewEnabled, onRetryFilePreview, } = props;
4751
5323
  const actionsWithDisplayText = actionItems.map((item) => ({
4752
5324
  ...item,
4753
5325
  label: getActionListItemLabel(item.label),
@@ -4767,6 +5339,9 @@ function LocationDetailViewProvider({ children, ...props }) {
4767
5339
  actionItems.every(({ isHidden }) => isHidden);
4768
5340
  return (React__namespace["default"].createElement(ControlsContextProvider, { data: {
4769
5341
  actions: actionsWithDisplayText,
5342
+ activeFile,
5343
+ activeFileHasNext,
5344
+ activeFileHasPrev,
4770
5345
  isActionsListDisabled,
4771
5346
  isDataRefreshDisabled: isLoading,
4772
5347
  isLoading,
@@ -4783,7 +5358,11 @@ function LocationDetailViewProvider({ children, ...props }) {
4783
5358
  searchSubmitLabel,
4784
5359
  searchClearLabel,
4785
5360
  searchQuery,
5361
+ filePreviewState,
4786
5362
  tableData: getLocationDetailViewTableData({
5363
+ filePreviewEnabled,
5364
+ activeFile,
5365
+ onSelectActiveFile,
4787
5366
  areAllFilesSelected,
4788
5367
  displayText,
4789
5368
  location,
@@ -4800,7 +5379,150 @@ function LocationDetailViewProvider({ children, ...props }) {
4800
5379
  }),
4801
5380
  title: getTitle(location),
4802
5381
  message: messageControlContent,
4803
- }, onActionSelect: onActionSelect, onDropFiles: onDropFiles, onNavigate: onNavigate, onNavigateHome: onNavigateHome, onPaginate: onPaginate, onRefresh: onRefresh, onSearch: onSearch, onSearchQueryChange: onSearchQueryChange, onSearchClear: onSearchClear, onToggleSearchSubfolders: onToggleSearchSubfolders }, children));
5382
+ }, onActionSelect: onActionSelect, onDropFiles: onDropFiles, onNavigate: onNavigate, onNavigateHome: onNavigateHome, onPaginate: onPaginate, onRefresh: onRefresh, onSearch: onSearch, onSelectActiveFile: onSelectActiveFile, onSearchQueryChange: onSearchQueryChange, onSearchClear: onSearchClear, onToggleSearchSubfolders: onToggleSearchSubfolders, onRetryFilePreview: onRetryFilePreview }, children));
5383
+ }
5384
+
5385
+ function resolveUrlOptions(options, fileType) {
5386
+ if (!options) {
5387
+ return DEFAULT_URL_OPTIONS;
5388
+ }
5389
+ if (typeof options === 'function' && fileType) {
5390
+ return options(fileType) ?? DEFAULT_URL_OPTIONS;
5391
+ }
5392
+ return {
5393
+ ...DEFAULT_URL_OPTIONS,
5394
+ ...options,
5395
+ };
5396
+ }
5397
+
5398
+ function resolveMaxFileSize(maxFileSize, fileType) {
5399
+ if (typeof maxFileSize === 'number') {
5400
+ return maxFileSize;
5401
+ }
5402
+ if (typeof maxFileSize === 'function' && fileType) {
5403
+ try {
5404
+ const result = maxFileSize(fileType);
5405
+ if (typeof result === 'number') {
5406
+ return result;
5407
+ }
5408
+ }
5409
+ catch (error) {
5410
+ //
5411
+ }
5412
+ }
5413
+ return fileType
5414
+ ? DEFAULT_FILE_SIZE_LIMITS[fileType] || DEFAULT_FILE_SIZE_LIMIT
5415
+ : DEFAULT_FILE_SIZE_LIMIT;
5416
+ }
5417
+
5418
+ async function safeGetProperties(params) {
5419
+ try {
5420
+ return await storage.getProperties(params);
5421
+ }
5422
+ catch (error) {
5423
+ return {};
5424
+ }
5425
+ }
5426
+
5427
+ function useFilePreview({ activeFile, }) {
5428
+ const filePreviewContext = useFilePreviewContext();
5429
+ const [enabled, setEnabled] = React.useState(true);
5430
+ const getConfig = useGetActionInput();
5431
+ const [{ location }] = useStore();
5432
+ const [filePreviewContent, setFilePreviewContent] = React.useState({ isLoading: true });
5433
+ const { fileTypeResolver, urlOptions, maxFileSize } = (filePreviewContext ?? {}) || {};
5434
+ const getFilePreview = React.useCallback(async () => {
5435
+ if (!activeFile || !activeFile?.key || !location.current) {
5436
+ setEnabled(false);
5437
+ return;
5438
+ }
5439
+ const config = getConfig(location.current);
5440
+ const { accountId, customEndpoint, credentials } = config;
5441
+ const sharedOptions = {
5442
+ bucket: constructBucket(config),
5443
+ expectedBucketOwner: accountId,
5444
+ };
5445
+ try {
5446
+ setEnabled(true);
5447
+ setFilePreviewContent({
5448
+ isLoading: true,
5449
+ });
5450
+ const properties = await safeGetProperties({
5451
+ path: activeFile.key,
5452
+ options: sharedOptions,
5453
+ });
5454
+ const enrichedFile = {
5455
+ ...activeFile,
5456
+ ...properties,
5457
+ };
5458
+ const fileType = determineFileType({
5459
+ fileData: enrichedFile,
5460
+ fileTypeResolver,
5461
+ });
5462
+ const finalFile = {
5463
+ ...enrichedFile,
5464
+ fileType,
5465
+ };
5466
+ const sizeLimit = resolveMaxFileSize(maxFileSize, fileType);
5467
+ const isLimitExceeded = ((finalFile.size || activeFile?.size) ?? 0) > sizeLimit;
5468
+ if (isLimitExceeded) {
5469
+ setFilePreviewContent({
5470
+ ok: false,
5471
+ isLoading: false,
5472
+ error: 'LIMIT_EXCEEDED',
5473
+ });
5474
+ return;
5475
+ }
5476
+ const { url } = await internals.getUrl({
5477
+ path: finalFile.key,
5478
+ options: {
5479
+ customEndpoint,
5480
+ locationCredentialsProvider: credentials,
5481
+ contentDisposition: 'attachment',
5482
+ ...sharedOptions,
5483
+ ...resolveUrlOptions(urlOptions, fileType),
5484
+ },
5485
+ });
5486
+ setFilePreviewContent({
5487
+ ok: true,
5488
+ isLoading: false,
5489
+ fileData: finalFile,
5490
+ url: url.toString(),
5491
+ });
5492
+ }
5493
+ catch (error) {
5494
+ setFilePreviewContent({
5495
+ isLoading: false,
5496
+ ok: false,
5497
+ error: 'GENERIC_ERROR',
5498
+ });
5499
+ }
5500
+ }, [
5501
+ location,
5502
+ getConfig,
5503
+ fileTypeResolver,
5504
+ maxFileSize,
5505
+ urlOptions,
5506
+ activeFile,
5507
+ ]);
5508
+ React.useEffect(() => {
5509
+ getFilePreview();
5510
+ }, [getFilePreview, activeFile]);
5511
+ return React.useMemo(() => {
5512
+ if (filePreviewContext === false || !enabled) {
5513
+ return {
5514
+ optout: filePreviewContext === false,
5515
+ enabled: false,
5516
+ handleRetry: () => undefined,
5517
+ };
5518
+ }
5519
+ return {
5520
+ optout: false,
5521
+ enabled: true,
5522
+ handleRetry: () => getFilePreview(),
5523
+ ...filePreviewContent,
5524
+ };
5525
+ }, [getFilePreview, filePreviewContext, filePreviewContent, enabled]);
4804
5526
  }
4805
5527
 
4806
5528
  const DEFAULT_PAGE_SIZE$1 = 100;
@@ -4823,6 +5545,7 @@ const useLocationDetailView = (options) => {
4823
5545
  const [{ location, actionType }, storeDispatch] = useStore();
4824
5546
  const [locationItems, locationItemsDispatch] = useLocationItems();
4825
5547
  const fileItemsDispatch = useFileItems()[1];
5548
+ const [activeFile, setActiveFile] = React__namespace["default"].useState();
4826
5549
  const { current, key } = location;
4827
5550
  const { permissions, prefix } = current ?? {};
4828
5551
  const { fileDataItems } = locationItems;
@@ -4861,7 +5584,62 @@ const useLocationDetailView = (options) => {
4861
5584
  handleList({ prefix: key, options: searchOptions });
4862
5585
  locationItemsDispatch({ type: 'RESET_LOCATION_ITEMS' });
4863
5586
  };
5587
+ const { activeFileHasPrev, activeFileHasNext } = React__namespace["default"].useMemo(() => {
5588
+ if (!activeFile)
5589
+ return {
5590
+ activeFileHasNext: false,
5591
+ activeFileHasPrev: false,
5592
+ };
5593
+ const idx = pageItems.findIndex((item) => item.id === activeFile?.id);
5594
+ let pIdx = idx;
5595
+ do {
5596
+ --pIdx;
5597
+ } while (pIdx >= 0 && pageItems[pIdx].type !== 'FILE');
5598
+ let nIdx = idx;
5599
+ do {
5600
+ ++nIdx;
5601
+ } while (nIdx <= pageItems.length - 1 && pageItems[nIdx].type !== 'FILE');
5602
+ return {
5603
+ activeFileHasPrev: pIdx >= 0,
5604
+ activeFileHasNext: nIdx <= pageItems.length - 1,
5605
+ };
5606
+ }, [activeFile, pageItems]);
5607
+ const getSuitableNextItem = React.useCallback((direction, type) => {
5608
+ // first find the position
5609
+ let newIdx = pageItems.findIndex((item) => item.id === activeFile?.id);
5610
+ if (direction === 'prev') {
5611
+ do {
5612
+ --newIdx;
5613
+ } while (newIdx >= 0 && pageItems[newIdx].type !== type);
5614
+ if (pageItems[newIdx].type === type) {
5615
+ return pageItems[newIdx];
5616
+ }
5617
+ }
5618
+ else {
5619
+ do {
5620
+ ++newIdx;
5621
+ } while (newIdx <= pageItems.length - 1 &&
5622
+ pageItems[newIdx].type !== type);
5623
+ if (pageItems[newIdx].type === type) {
5624
+ return pageItems[newIdx];
5625
+ }
5626
+ }
5627
+ }, [activeFile, pageItems]);
5628
+ const onSelectActiveFile = (arg) => {
5629
+ if (arg === 'prev') {
5630
+ setActiveFile(getSuitableNextItem('prev', 'FILE'));
5631
+ }
5632
+ else if (arg === 'next') {
5633
+ setActiveFile(getSuitableNextItem('next', 'FILE'));
5634
+ }
5635
+ else {
5636
+ setActiveFile(arg);
5637
+ }
5638
+ };
4864
5639
  const { searchQuery, isSearchingSubfolders: isSearchSubfoldersEnabled, onSearchQueryChange, onSearchSubmit, onToggleSearchSubfolders, resetSearch, } = useSearch({ onSearch });
5640
+ const { handleRetry, optout, ...filePreviewState } = useFilePreview({
5641
+ activeFile,
5642
+ });
4865
5643
  const onRefresh = () => {
4866
5644
  if (hasInvalidPrefix)
4867
5645
  return;
@@ -4871,6 +5649,7 @@ const useLocationDetailView = (options) => {
4871
5649
  prefix: key,
4872
5650
  options: { ...listOptions, refresh: true },
4873
5651
  });
5652
+ setActiveFile(undefined);
4874
5653
  locationItemsDispatch({ type: 'RESET_LOCATION_ITEMS' });
4875
5654
  };
4876
5655
  React__namespace["default"].useEffect(() => {
@@ -4881,6 +5660,7 @@ const useLocationDetailView = (options) => {
4881
5660
  options: { ...listOptions, refresh: true },
4882
5661
  });
4883
5662
  handleReset();
5663
+ setActiveFile(undefined);
4884
5664
  }, [handleList, handleReset, listOptions, hasInvalidPrefix, key]);
4885
5665
  const { actionConfigs } = useActionConfigs();
4886
5666
  const actionItems = React__namespace["default"].useMemo(() => {
@@ -4905,6 +5685,10 @@ const useLocationDetailView = (options) => {
4905
5685
  return {
4906
5686
  actionItems,
4907
5687
  actionType,
5688
+ activeFile,
5689
+ activeFileHasNext,
5690
+ activeFileHasPrev,
5691
+ onSelectActiveFile,
4908
5692
  page: currentPage,
4909
5693
  pageItems,
4910
5694
  location,
@@ -4917,8 +5701,13 @@ const useLocationDetailView = (options) => {
4917
5701
  downloadErrorMessage: getDownloadErrorMessageFromFailedDownloadTask(task),
4918
5702
  isLoading,
4919
5703
  isSearchSubfoldersEnabled,
4920
- onPaginate: handlePaginate,
5704
+ onPaginate: (p) => {
5705
+ handlePaginate(p);
5706
+ setActiveFile(undefined);
5707
+ },
4921
5708
  searchQuery,
5709
+ filePreviewState,
5710
+ filePreviewEnabled: !optout,
4922
5711
  hasExhaustedSearch,
4923
5712
  onRefresh,
4924
5713
  onActionExit: () => {
@@ -4936,6 +5725,7 @@ const useLocationDetailView = (options) => {
4936
5725
  resetSearch();
4937
5726
  storeDispatch({ type: 'CHANGE_LOCATION', location, path });
4938
5727
  locationItemsDispatch({ type: 'RESET_LOCATION_ITEMS' });
5728
+ setActiveFile(undefined);
4939
5729
  },
4940
5730
  onDropFiles: (files) => {
4941
5731
  fileItemsDispatch({ type: 'ADD_FILES', files });
@@ -4968,7 +5758,10 @@ const useLocationDetailView = (options) => {
4968
5758
  ? { type: 'RESET_LOCATION_ITEMS' }
4969
5759
  : { type: 'SET_LOCATION_ITEMS', items: fileItems });
4970
5760
  },
4971
- onSearch: onSearchSubmit,
5761
+ onSearch: () => {
5762
+ setActiveFile(undefined);
5763
+ onSearchSubmit();
5764
+ },
4972
5765
  onSearchClear: () => {
4973
5766
  resetSearch();
4974
5767
  if (hasInvalidPrefix)
@@ -4977,10 +5770,29 @@ const useLocationDetailView = (options) => {
4977
5770
  handleReset();
4978
5771
  },
4979
5772
  onSearchQueryChange,
5773
+ onRetryFilePreview: handleRetry,
4980
5774
  onToggleSearchSubfolders,
4981
5775
  };
4982
5776
  };
4983
5777
 
5778
+ function FilePreviewControl() {
5779
+ const { data, onRetryFilePreview, onSelectActiveFile } = useControlsContext();
5780
+ const { filePreviewState, activeFile, activeFileHasNext, activeFileHasPrev } = data;
5781
+ const props = {
5782
+ filePreview: filePreviewState,
5783
+ activeFile,
5784
+ activeFileHasNext,
5785
+ activeFileHasPrev,
5786
+ onRetryFilePreview,
5787
+ onSelectActiveFile,
5788
+ };
5789
+ const Resolved = useResolvedComposable(FilePreview, 'FilePreview');
5790
+ if (!activeFile) {
5791
+ return null;
5792
+ }
5793
+ return React__namespace["default"].createElement(Resolved, { ...props });
5794
+ }
5795
+
4984
5796
  const LocationDetailView = ({ className, ...props }) => {
4985
5797
  const state = useLocationDetailView(props);
4986
5798
  const { hasError } = state;
@@ -4995,10 +5807,12 @@ const LocationDetailView = ({ className, ...props }) => {
4995
5807
  React__namespace["default"].createElement(PaginationControl, null),
4996
5808
  React__namespace["default"].createElement(DataRefreshControl, null),
4997
5809
  React__namespace["default"].createElement(ActionsListControl, null)),
4998
- hasError ? null : (React__namespace["default"].createElement(DropZoneControl, null,
4999
- React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__data-table` },
5000
- React__namespace["default"].createElement(LoadingIndicatorControl, null),
5001
- React__namespace["default"].createElement(DataTableControl, null)))),
5810
+ hasError ? null : (React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__content-with-preview` },
5811
+ React__namespace["default"].createElement(DropZoneControl, null,
5812
+ React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__data-table` },
5813
+ React__namespace["default"].createElement(LoadingIndicatorControl, null),
5814
+ React__namespace["default"].createElement(DataTableControl, null))),
5815
+ React__namespace["default"].createElement(FilePreviewControl, null))),
5002
5816
  React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__footer` },
5003
5817
  React__namespace["default"].createElement(MessageControl, null)))));
5004
5818
  };
@@ -5015,6 +5829,7 @@ LocationDetailView.Refresh = DataRefreshControl;
5015
5829
  LocationDetailView.Search = SearchFieldControl;
5016
5830
  LocationDetailView.SearchSubfoldersToggle = SearchSubfoldersToggleControl;
5017
5831
  LocationDetailView.Title = TitleControl;
5832
+ LocationDetailView.FilePreview = FilePreviewControl;
5018
5833
 
5019
5834
  const getHeaders = ({ hasObjectLocations, tableColumnActionsHeader, tableColumnBucketHeader, tableColumnFolderHeader, tableColumnPermissionsHeader, }) => {
5020
5835
  const headers = [
@@ -5343,7 +6158,7 @@ const useView = (type) => {
5343
6158
  return USE_VIEW_HOOKS[type]();
5344
6159
  };
5345
6160
 
5346
- function createProvider({ actions, components, config, options, }) {
6161
+ function createProvider({ actions, components, config, options, filePreview = {}, }) {
5347
6162
  const { accountId, customEndpoint, registerAuthListener, getLocationCredentials, region, listLocations, } = config;
5348
6163
  const resolvedActions = {
5349
6164
  default: {
@@ -5386,7 +6201,8 @@ function createProvider({ actions, components, config, options, }) {
5386
6201
  React__namespace["default"].createElement(ViewsProvider, { actions: resolvedActions, views: views },
5387
6202
  React__namespace["default"].createElement(ComponentsProvider, { composables: composables },
5388
6203
  React__namespace["default"].createElement(LocationItemsProvider, null,
5389
- React__namespace["default"].createElement(FileItemsProvider, { validateFile: validateFile }, children))))))))));
6204
+ React__namespace["default"].createElement(FileItemsProvider, { validateFile: validateFile },
6205
+ React__namespace["default"].createElement(FilePreviewProvider, { filePreview: filePreview }, children)))))))))));
5390
6206
  }
5391
6207
  return Provider;
5392
6208
  }