@scality/data-browser-library 1.0.3 → 1.0.5

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 (51) hide show
  1. package/dist/components/DataBrowserUI.js +18 -8
  2. package/dist/components/__tests__/BucketList.test.js +74 -1
  3. package/dist/components/__tests__/ObjectList.test.js +94 -2
  4. package/dist/components/buckets/BucketCreate.d.ts +1 -0
  5. package/dist/components/buckets/BucketCreate.js +57 -7
  6. package/dist/components/buckets/BucketDetails.js +0 -1
  7. package/dist/components/buckets/BucketLifecycleFormPage.js +209 -213
  8. package/dist/components/buckets/BucketList.js +25 -4
  9. package/dist/components/buckets/BucketReplicationFormPage.js +9 -3
  10. package/dist/components/buckets/DeleteBucketConfigRuleButton.js +1 -1
  11. package/dist/components/buckets/notifications/BucketNotificationList.js +1 -1
  12. package/dist/components/objects/DeleteObjectButton.d.ts +1 -0
  13. package/dist/components/objects/DeleteObjectButton.js +11 -5
  14. package/dist/components/objects/ObjectDetails/FormComponents.d.ts +9 -0
  15. package/dist/components/objects/ObjectDetails/FormComponents.js +37 -0
  16. package/dist/components/objects/ObjectDetails/ObjectMetadata.js +182 -204
  17. package/dist/components/objects/ObjectDetails/ObjectSummary.js +22 -5
  18. package/dist/components/objects/ObjectDetails/ObjectTags.js +109 -154
  19. package/dist/components/objects/ObjectDetails/__tests__/ObjectMetadata.test.d.ts +1 -0
  20. package/dist/components/objects/ObjectDetails/__tests__/ObjectMetadata.test.js +230 -0
  21. package/dist/components/objects/ObjectDetails/__tests__/ObjectTags.test.d.ts +1 -0
  22. package/dist/components/objects/ObjectDetails/__tests__/ObjectTags.test.js +342 -0
  23. package/dist/components/objects/ObjectDetails/__tests__/formUtils.test.d.ts +1 -0
  24. package/dist/components/objects/ObjectDetails/__tests__/formUtils.test.js +202 -0
  25. package/dist/components/objects/ObjectDetails/index.d.ts +2 -1
  26. package/dist/components/objects/ObjectDetails/index.js +12 -16
  27. package/dist/components/objects/ObjectList.d.ts +3 -2
  28. package/dist/components/objects/ObjectList.js +204 -104
  29. package/dist/components/objects/ObjectPage.js +22 -5
  30. package/dist/components/ui/ArrayFieldActions.js +0 -2
  31. package/dist/components/ui/FilterFormSection.js +17 -36
  32. package/dist/components/ui/FormGrid.d.ts +7 -0
  33. package/dist/components/ui/FormGrid.js +37 -0
  34. package/dist/components/ui/Table.elements.js +1 -0
  35. package/dist/config/types.d.ts +45 -2
  36. package/dist/hooks/__tests__/usePresigningS3Client.test.d.ts +1 -0
  37. package/dist/hooks/__tests__/usePresigningS3Client.test.js +104 -0
  38. package/dist/hooks/factories/index.d.ts +1 -1
  39. package/dist/hooks/factories/index.js +2 -2
  40. package/dist/hooks/factories/useCreateS3MutationHook.d.ts +2 -1
  41. package/dist/hooks/factories/useCreateS3MutationHook.js +10 -3
  42. package/dist/hooks/factories/useCreateS3QueryHook.d.ts +1 -0
  43. package/dist/hooks/factories/useCreateS3QueryHook.js +9 -6
  44. package/dist/hooks/index.d.ts +1 -0
  45. package/dist/hooks/index.js +2 -1
  46. package/dist/hooks/presignedOperations.js +4 -4
  47. package/dist/hooks/useBucketLocations.d.ts +6 -0
  48. package/dist/hooks/useBucketLocations.js +45 -0
  49. package/dist/hooks/usePresigningS3Client.d.ts +13 -0
  50. package/dist/hooks/usePresigningS3Client.js +21 -0
  51. package/package.json +4 -4
@@ -1,7 +1,9 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { ConstrainedText, FormattedDateTime, Icon, Link, PrettyBytes, Text, Toggle, Wrap, spacing, useToast } from "@scality/core-ui";
2
+ import { ConstrainedText, FormattedDateTime, Icon, Link, PrettyBytes, SearchInput, Text, Toggle, Wrap, spacing, useToast } from "@scality/core-ui";
3
3
  import { Box, Table } from "@scality/core-ui/dist/next";
4
4
  import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
5
+ import { useLocation, useNavigate } from "react-router";
6
+ import styled_components from "styled-components";
5
7
  import { useDataBrowserUICustomization } from "../../contexts/DataBrowserUICustomizationContext.js";
6
8
  import { useSearchObjects, useSearchObjectsVersions } from "../../hooks/index.js";
7
9
  import { useGetPresignedDownload } from "../../hooks/presignedOperations.js";
@@ -13,6 +15,19 @@ import MetadataSearch from "../search/MetadataSearch.js";
13
15
  import CreateFolderButton from "./CreateFolderButton.js";
14
16
  import DeleteObjectButton from "./DeleteObjectButton.js";
15
17
  import UploadButton from "./UploadButton.js";
18
+ const TableWithAlignedRows = styled_components.div`
19
+ height: 100%;
20
+ flex: 1;
21
+ .tr {
22
+ gap: ${spacing.r16};
23
+ }
24
+ `;
25
+ const TruncatedName = styled_components.div`
26
+ overflow: hidden;
27
+ text-overflow: ellipsis;
28
+ white-space: nowrap;
29
+ min-width: 0;
30
+ `;
16
31
  const DEFAULT_PAGE_SIZE = 20;
17
32
  const SEARCH_QUERY_PARAM = 'search';
18
33
  const NAME_COLUMN_FLEX = '2';
@@ -26,6 +41,21 @@ const getVersionTextColor = (row)=>{
26
41
  const isVersion = 'version' === row.original.type;
27
42
  return !isLatest && isVersion ? 'infoPrimary' : 'textPrimary';
28
43
  };
44
+ const pinFolderFirst = (rowA, rowB, desc)=>{
45
+ const aIsFolder = 'folder' === rowA.original.type;
46
+ const bIsFolder = 'folder' === rowB.original.type;
47
+ if (aIsFolder === bIsFolder) return 0;
48
+ if (aIsFolder) return desc ? 1 : -1;
49
+ return desc ? -1 : 1;
50
+ };
51
+ const pinLatestVersion = (rowA, rowB, desc)=>{
52
+ const aKey = rowA.original.Key || '';
53
+ const bKey = rowB.original.Key || '';
54
+ if (!aKey || !bKey || aKey !== bKey) return 0;
55
+ if (rowA.original.IsLatest && !rowB.original.IsLatest) return desc ? 1 : -1;
56
+ if (!rowA.original.IsLatest && rowB.original.IsLatest) return desc ? -1 : 1;
57
+ return 0;
58
+ };
29
59
  const removePrefix = (path, prefix)=>{
30
60
  if (!prefix) return path;
31
61
  if (path.startsWith(prefix)) return path.slice(prefix.length);
@@ -55,16 +85,20 @@ const createNameColumn = (onPrefixChange, onDownload)=>({
55
85
  Header: 'Name',
56
86
  accessor: 'displayName',
57
87
  id: 'name',
58
- sortType: (rowA, rowB)=>{
59
- const aIsFolder = 'folder' === rowA.original.type;
60
- const bIsFolder = 'folder' === rowB.original.type;
61
- if (aIsFolder && !bIsFolder) return -1;
62
- if (!aIsFolder && bIsFolder) return 1;
88
+ sortType: (rowA, rowB, _columnId, desc)=>{
89
+ const folderOrder = pinFolderFirst(rowA, rowB, desc);
90
+ if (0 !== folderOrder) return folderOrder;
63
91
  const aName = String(rowA.values.displayName || '');
64
92
  const bName = String(rowB.values.displayName || '');
65
- return aName.localeCompare(bName, 'en', {
93
+ const nameComparison = aName.localeCompare(bName, 'en', {
66
94
  sensitivity: 'base'
67
95
  });
96
+ if (0 !== nameComparison) return nameComparison;
97
+ const pinOrder = pinLatestVersion(rowA, rowB, desc);
98
+ if (0 !== pinOrder) return pinOrder;
99
+ const aTime = rowA.original.LastModified ? new Date(rowA.original.LastModified).getTime() : 0;
100
+ const bTime = rowB.original.LastModified ? new Date(rowB.original.LastModified).getTime() : 0;
101
+ return bTime - aTime;
68
102
  },
69
103
  Cell: ({ value, row })=>{
70
104
  const isFolder = 'folder' === row.original.type;
@@ -75,24 +109,25 @@ const createNameColumn = (onPrefixChange, onDownload)=>({
75
109
  iconName = isFolder ? 'Folder' : isDeleteMarker ? 'Deletion-marker' : 'File';
76
110
  const shouldIndent = isVersion && !isLatest;
77
111
  const isLegalHoldEnabled = isObjectLike(row.original) && Boolean(row.original.isLegalHoldEnabled);
78
- return /*#__PURE__*/ jsx(ConstrainedText, {
79
- text: /*#__PURE__*/ jsxs(Box, {
80
- display: "flex",
81
- alignItems: "center",
82
- gap: spacing.r8,
83
- paddingLeft: shouldIndent ? spacing.r24 : 0,
84
- children: [
85
- /*#__PURE__*/ jsx(Icon, {
86
- name: iconName,
87
- size: "sm",
88
- color: getVersionTextColor(row)
89
- }),
90
- isLegalHoldEnabled && /*#__PURE__*/ jsx(Icon, {
91
- name: "Rebalance",
92
- size: "sm",
93
- color: getVersionTextColor(row)
94
- }),
95
- isDeleteMarker ? /*#__PURE__*/ jsx(Text, {
112
+ return /*#__PURE__*/ jsxs(Box, {
113
+ display: "flex",
114
+ alignItems: "center",
115
+ gap: spacing.r8,
116
+ paddingLeft: shouldIndent ? spacing.r24 : 0,
117
+ minWidth: 0,
118
+ children: [
119
+ /*#__PURE__*/ jsx(Icon, {
120
+ name: iconName,
121
+ size: "sm",
122
+ color: getVersionTextColor(row)
123
+ }),
124
+ isLegalHoldEnabled && /*#__PURE__*/ jsx(Icon, {
125
+ name: "Rebalance",
126
+ size: "sm",
127
+ color: getVersionTextColor(row)
128
+ }),
129
+ /*#__PURE__*/ jsx(TruncatedName, {
130
+ children: isDeleteMarker ? /*#__PURE__*/ jsx(Text, {
96
131
  color: getVersionTextColor(row),
97
132
  children: value
98
133
  }) : /*#__PURE__*/ jsx(Text, {
@@ -106,14 +141,14 @@ const createNameColumn = (onPrefixChange, onDownload)=>({
106
141
  children: value
107
142
  })
108
143
  })
109
- ]
110
- }),
111
- lineClamp: 2
144
+ })
145
+ ]
112
146
  });
113
147
  },
114
148
  cellStyle: {
115
149
  flex: NAME_COLUMN_FLEX,
116
- width: 'unset'
150
+ width: 'unset',
151
+ minWidth: 0
117
152
  }
118
153
  });
119
154
  const createVersionIdColumn = ()=>({
@@ -132,7 +167,11 @@ const createVersionIdColumn = ()=>({
132
167
  return /*#__PURE__*/ jsx(ConstrainedText, {
133
168
  text: versionId,
134
169
  lineClamp: 1,
135
- color: textColor
170
+ color: textColor,
171
+ tooltipStyle: {
172
+ maxWidth: '20rem',
173
+ wordBreak: 'break-all'
174
+ }
136
175
  });
137
176
  },
138
177
  cellStyle: {
@@ -143,10 +182,25 @@ const createVersionIdColumn = ()=>({
143
182
  maxWidth: '8rem'
144
183
  }
145
184
  });
146
- const createLastModifiedColumn = ()=>({
185
+ const createLastModifiedColumn = (groupLatestModified)=>({
147
186
  Header: 'Modified on',
148
187
  accessor: 'LastModified',
149
188
  id: 'lastModified',
189
+ sortType: (rowA, rowB, _columnId, desc)=>{
190
+ const folderOrder = pinFolderFirst(rowA, rowB, desc);
191
+ if (0 !== folderOrder) return folderOrder;
192
+ const pinOrder = pinLatestVersion(rowA, rowB, desc);
193
+ if (0 !== pinOrder) return pinOrder;
194
+ const aKey = rowA.original.Key || '';
195
+ const bKey = rowB.original.Key || '';
196
+ const sameKey = '' !== aKey && '' !== bKey && aKey === bKey;
197
+ const a = !sameKey && groupLatestModified?.get(aKey) || rowA.original.LastModified;
198
+ const b = !sameKey && groupLatestModified?.get(bKey) || rowB.original.LastModified;
199
+ if (!a && !b) return 0;
200
+ if (!a) return -1;
201
+ if (!b) return 1;
202
+ return new Date(a).getTime() - new Date(b).getTime();
203
+ },
150
204
  Cell: ({ value, row })=>{
151
205
  if ('folder' === row.original.type || null == value) return /*#__PURE__*/ jsx(Text, {
152
206
  children: "-"
@@ -162,7 +216,6 @@ const createLastModifiedColumn = ()=>({
162
216
  cellStyle: {
163
217
  flex: LAST_MODIFIED_COLUMN_FLEX,
164
218
  textAlign: 'right',
165
- paddingRight: spacing.r16,
166
219
  width: 'unset',
167
220
  minWidth: '10rem'
168
221
  }
@@ -190,7 +243,6 @@ const createSizeColumn = ()=>({
190
243
  cellStyle: {
191
244
  flex: SIZE_COLUMN_FLEX,
192
245
  textAlign: 'right',
193
- paddingRight: spacing.r16,
194
246
  width: 'unset'
195
247
  }
196
248
  });
@@ -239,12 +291,16 @@ function createOverrideMap(customItems) {
239
291
  item
240
292
  ]));
241
293
  }
242
- const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange })=>{
294
+ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange, onSelectedObjectsChange })=>{
243
295
  const { extraObjectListColumns, extraObjectListActions } = useDataBrowserUICustomization();
244
296
  const invalidateQueries = useInvalidateQueries();
245
297
  const [showVersions, setShowVersions] = useState(false);
246
298
  const isMetadataSearchEnabled = useFeatures('metadatasearch');
247
- const metadataSearchQuery = useQueryParams().get('metadatasearch');
299
+ const queryParams = useQueryParams();
300
+ const metadataSearchQuery = queryParams.get('metadatasearch');
301
+ const { search: locationSearch, pathname } = useLocation();
302
+ const navigate = useNavigate();
303
+ const [searchValue, setSearchValue] = useState(queryParams.get(SEARCH_QUERY_PARAM) || '');
248
304
  const [selectedObjects, setSelectedObjects] = useState([]);
249
305
  const { mutateAsync: getPresignedDownload } = useGetPresignedDownload();
250
306
  const downloadingRef = useRef(new Set());
@@ -338,7 +394,8 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange })=>{
338
394
  const versionSearchParams = useMemo(()=>({
339
395
  Bucket: bucketName,
340
396
  Prefix: prefix,
341
- MaxKeys: DEFAULT_PAGE_SIZE
397
+ MaxKeys: DEFAULT_PAGE_SIZE,
398
+ Delimiter: '/'
342
399
  }), [
343
400
  bucketName,
344
401
  prefix
@@ -348,6 +405,7 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange })=>{
348
405
  });
349
406
  useEffect(()=>{
350
407
  setSelectedObjects([]);
408
+ onObjectSelectRef.current(null);
351
409
  }, [
352
410
  prefix,
353
411
  showVersions
@@ -429,9 +487,10 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange })=>{
429
487
  }, [
430
488
  prefix
431
489
  ]);
432
- const tableData = useMemo(()=>{
490
+ const { tableData, versionGroupLatestModified } = useMemo(()=>{
433
491
  let folders = [];
434
492
  const items = [];
493
+ const latestModifiedMap = new Map();
435
494
  if (showVersions) {
436
495
  folders = listObjectsQuery.data?.pages ? processFolders(listObjectsQuery.data.pages) : [];
437
496
  const objectsWithVersions = new Set();
@@ -467,6 +526,8 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange })=>{
467
526
  const bTime = b.LastModified?.getTime() ?? 0;
468
527
  return bTime - aTime;
469
528
  });
529
+ const latestItem = allItems[0];
530
+ if (latestItem?.Key && latestItem.LastModified) latestModifiedMap.set(latestItem.Key, latestItem.LastModified);
470
531
  allItems.forEach((item)=>{
471
532
  if (!item.Key) return;
472
533
  if (item.Key.endsWith('/')) return;
@@ -506,11 +567,13 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange })=>{
506
567
  });
507
568
  });
508
569
  }
509
- const allItems = [
510
- ...folders,
511
- ...items
512
- ];
513
- return allItems;
570
+ return {
571
+ tableData: [
572
+ ...folders,
573
+ ...items
574
+ ],
575
+ versionGroupLatestModified: latestModifiedMap
576
+ };
514
577
  }, [
515
578
  showVersions,
516
579
  listObjectsQuery.data,
@@ -520,7 +583,9 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange })=>{
520
583
  createObjectItem
521
584
  ]);
522
585
  const versionIdColumn = useMemo(()=>createVersionIdColumn(), []);
523
- const lastModifiedColumn = useMemo(()=>createLastModifiedColumn(), []);
586
+ const lastModifiedColumn = useMemo(()=>createLastModifiedColumn(versionGroupLatestModified), [
587
+ versionGroupLatestModified
588
+ ]);
524
589
  const sizeColumn = useMemo(()=>createSizeColumn(), []);
525
590
  const storageClassColumn = useMemo(()=>createStorageClassColumn(), []);
526
591
  const nameColumn = useMemo(()=>createNameColumn(handlePrefixChange, handleDownload), [
@@ -587,6 +652,19 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange })=>{
587
652
  }, [
588
653
  invalidateQueries
589
654
  ]);
655
+ const handleSearchChange = useCallback((value)=>{
656
+ setSearchValue(value);
657
+ const params = new URLSearchParams(locationSearch);
658
+ if (value) params.set(SEARCH_QUERY_PARAM, value);
659
+ else params.delete(SEARCH_QUERY_PARAM);
660
+ navigate(`${pathname}?${params.toString()}`, {
661
+ replace: true
662
+ });
663
+ }, [
664
+ locationSearch,
665
+ pathname,
666
+ navigate
667
+ ]);
590
668
  const handleUploadError = useCallback(()=>{}, []);
591
669
  const handleFolderError = useCallback(()=>{}, []);
592
670
  const renderUploadAction = useCallback(()=>/*#__PURE__*/ jsx(UploadButton, {
@@ -611,12 +689,21 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange })=>{
611
689
  handleFolderSuccess,
612
690
  handleFolderError
613
691
  ]);
692
+ const handleDeleteSuccess = useCallback(()=>{
693
+ setSelectedObjects([]);
694
+ onSelectedObjectsChange?.([]);
695
+ onObjectSelectRef.current(null);
696
+ }, [
697
+ onSelectedObjectsChange
698
+ ]);
614
699
  const renderDeleteAction = useCallback(()=>/*#__PURE__*/ jsx(DeleteObjectButton, {
615
700
  objects: selectedObjects,
616
- bucketName: bucketName
701
+ bucketName: bucketName,
702
+ onDeleteSuccess: handleDeleteSuccess
617
703
  }), [
618
704
  selectedObjects,
619
- bucketName
705
+ bucketName,
706
+ handleDeleteSuccess
620
707
  ]);
621
708
  const actions = useMemo(()=>{
622
709
  const defaultActionsMap = {
@@ -662,73 +749,86 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange })=>{
662
749
  const handleMultiSelectionChanged = useCallback((rows)=>{
663
750
  const objects = rows.map((row)=>row.original);
664
751
  setSelectedObjects(objects);
665
- }, []);
752
+ onSelectedObjectsChange?.(objects);
753
+ }, [
754
+ onSelectedObjectsChange
755
+ ]);
666
756
  const handleSingleRowSelected = useCallback((row)=>{
667
757
  handleObjectSelect(row.original);
758
+ setSelectedObjects([
759
+ row.original
760
+ ]);
761
+ onSelectedObjectsChange?.([
762
+ row.original
763
+ ]);
668
764
  }, [
669
- handleObjectSelect
765
+ handleObjectSelect,
766
+ onSelectedObjectsChange
670
767
  ]);
671
768
  const handleToggleAll = useCallback((selected)=>{
672
- selected ? setSelectedObjects(tableData) : setSelectedObjects([]);
769
+ const objects = selected ? tableData : [];
770
+ setSelectedObjects(objects);
771
+ onSelectedObjectsChange?.(objects);
673
772
  }, [
674
- tableData
773
+ tableData,
774
+ onSelectedObjectsChange
675
775
  ]);
676
776
  const tableStatus = isLoading ? 'loading' : error ? 'error' : 'success';
677
- const entityName = useMemo(()=>({
678
- en: {
679
- singular: 'object',
680
- plural: 'objects'
681
- }
682
- }), []);
683
- return /*#__PURE__*/ jsxs(Table, {
684
- columns: columns,
685
- data: tableData,
686
- status: tableStatus,
687
- onBottom: handleReachBottom,
688
- entityName: entityName,
689
- children: [
690
- /*#__PURE__*/ jsxs(Wrap, {
691
- padding: spacing.r16,
692
- children: [
693
- isMetadataSearchEnabled ? /*#__PURE__*/ jsx(MetadataSearch, {
694
- isError: !!error
695
- }) : /*#__PURE__*/ jsx(Table.SearchWithQueryParams, {
696
- queryParams: SEARCH_QUERY_PARAM
697
- }),
698
- /*#__PURE__*/ jsxs(Box, {
699
- display: "flex",
700
- justifyContent: "space-between",
701
- alignItems: "center",
702
- gap: spacing.r8,
703
- children: [
704
- /*#__PURE__*/ jsx(Box, {
705
- display: "flex",
706
- alignItems: "center",
707
- gap: spacing.r8,
708
- children: actions.map((action)=>/*#__PURE__*/ jsx(Fragment, {
709
- children: action.render()
710
- }, action.id))
711
- }),
712
- /*#__PURE__*/ jsx(Toggle, {
713
- toggle: showVersions,
714
- onChange: (e)=>setShowVersions(e.target.checked),
715
- label: "List Versions",
716
- "aria-label": showVersions ? 'Hide object versions' : 'Show object versions',
717
- "aria-pressed": showVersions
718
- })
719
- ]
720
- })
721
- ]
722
- }),
723
- /*#__PURE__*/ jsx(Table.MultiSelectableContent, {
724
- rowHeight: "h40",
725
- onMultiSelectionChanged: handleMultiSelectionChanged,
726
- onSingleRowSelected: handleSingleRowSelected,
727
- onToggleAll: handleToggleAll,
728
- separationLineVariant: "backgroundLevel1",
729
- isLoadingMoreItems: isFetchingNextPage
730
- })
731
- ]
777
+ return /*#__PURE__*/ jsx(TableWithAlignedRows, {
778
+ children: /*#__PURE__*/ jsxs(Table, {
779
+ columns: columns,
780
+ data: tableData,
781
+ status: tableStatus,
782
+ onBottom: handleReachBottom,
783
+ globalFilter: isMetadataSearchEnabled ? void 0 : searchValue,
784
+ children: [
785
+ /*#__PURE__*/ jsxs(Wrap, {
786
+ padding: spacing.r16,
787
+ children: [
788
+ isMetadataSearchEnabled ? /*#__PURE__*/ jsx(MetadataSearch, {
789
+ isError: !!error
790
+ }) : /*#__PURE__*/ jsx(SearchInput, {
791
+ value: searchValue,
792
+ placeholder: "Search",
793
+ size: "1",
794
+ onChange: (e)=>handleSearchChange(e.target.value),
795
+ onReset: ()=>handleSearchChange('')
796
+ }),
797
+ /*#__PURE__*/ jsxs(Box, {
798
+ display: "flex",
799
+ justifyContent: "space-between",
800
+ alignItems: "center",
801
+ gap: spacing.r8,
802
+ children: [
803
+ /*#__PURE__*/ jsx(Box, {
804
+ display: "flex",
805
+ alignItems: "center",
806
+ gap: spacing.r8,
807
+ children: actions.map((action)=>/*#__PURE__*/ jsx(Fragment, {
808
+ children: action.render()
809
+ }, action.id))
810
+ }),
811
+ /*#__PURE__*/ jsx(Toggle, {
812
+ toggle: showVersions,
813
+ onChange: (e)=>setShowVersions(e.target.checked),
814
+ label: "List Versions",
815
+ "aria-label": showVersions ? 'Hide object versions' : 'Show object versions',
816
+ "aria-pressed": showVersions
817
+ })
818
+ ]
819
+ })
820
+ ]
821
+ }),
822
+ /*#__PURE__*/ jsx(Table.MultiSelectableContent, {
823
+ rowHeight: "h40",
824
+ onMultiSelectionChanged: handleMultiSelectionChanged,
825
+ onSingleRowSelected: handleSingleRowSelected,
826
+ onToggleAll: handleToggleAll,
827
+ separationLineVariant: "backgroundLevel1",
828
+ isLoadingMoreItems: isFetchingNextPage
829
+ })
830
+ ]
831
+ })
732
832
  });
733
833
  };
734
834
  export { ObjectList, isObjectLike };
@@ -10,11 +10,13 @@ const ObjectPage = ()=>{
10
10
  const navigate = useDataBrowserNavigate();
11
11
  const [searchParams] = useSearchParams();
12
12
  const [item, setItem] = useState(null);
13
+ const [multipleSelected, setMultipleSelected] = useState(false);
13
14
  const prefix = searchParams.get('prefix') || '';
14
15
  const handlePrefixChange = useCallback((newPrefix)=>{
15
16
  const newSearchParams = new URLSearchParams(searchParams);
16
17
  newSearchParams.set('prefix', newPrefix);
17
18
  setItem(null);
19
+ setMultipleSelected(false);
18
20
  navigate(`?${newSearchParams.toString()}`, {
19
21
  replace: true
20
22
  });
@@ -22,12 +24,25 @@ const ObjectPage = ()=>{
22
24
  navigate,
23
25
  searchParams
24
26
  ]);
27
+ const handleObjectSelect = useCallback((object)=>{
28
+ setItem(object);
29
+ setMultipleSelected(false);
30
+ }, []);
31
+ const handleSelectedObjectsChange = useCallback((objects)=>{
32
+ if (1 === objects.length) {
33
+ setItem(objects[0]);
34
+ setMultipleSelected(false);
35
+ } else if (objects.length > 1) {
36
+ setItem(null);
37
+ setMultipleSelected(true);
38
+ } else {
39
+ setItem(null);
40
+ setMultipleSelected(false);
41
+ }
42
+ }, []);
25
43
  if (!bucketName) return /*#__PURE__*/ jsx("div", {
26
44
  children: "Bucket name is required"
27
45
  });
28
- const handleObjectSelect = (object)=>{
29
- setItem(object);
30
- };
31
46
  return /*#__PURE__*/ jsx(BrowserPageLayout, {
32
47
  iconName: "Bucket",
33
48
  title: bucketName,
@@ -35,10 +50,12 @@ const ObjectPage = ()=>{
35
50
  bucketName: bucketName,
36
51
  prefix: prefix,
37
52
  onObjectSelect: handleObjectSelect,
38
- onPrefixChange: handlePrefixChange
53
+ onPrefixChange: handlePrefixChange,
54
+ onSelectedObjectsChange: handleSelectedObjectsChange
39
55
  }),
40
56
  rightPanel: /*#__PURE__*/ jsx(ObjectDetails, {
41
- item: item
57
+ item: item,
58
+ multipleSelected: multipleSelected
42
59
  }),
43
60
  withArrowNavigation: true,
44
61
  arrowNavigationPath: `/buckets/${bucketName}`
@@ -1,11 +1,9 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Icon, spacing } from "@scality/core-ui";
3
- import { convertSizeToRem } from "@scality/core-ui/dist/components/inputv2/inputv2";
4
3
  import { Box, Button } from "@scality/core-ui/dist/next";
5
4
  function ArrayFieldActions({ onRemove, onAdd, canRemove, canAdd = true, showAdd, removeLabel = 'Remove', addLabel = 'Add' }) {
6
5
  return /*#__PURE__*/ jsxs(Box, {
7
6
  display: "flex",
8
- width: convertSizeToRem('1/2'),
9
7
  gap: spacing.r8,
10
8
  justifyContent: "flex-start",
11
9
  children: [
@@ -1,8 +1,9 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { FormGroup, FormSection, Stack, Text } from "@scality/core-ui";
2
+ import { FormGroup, FormSection, Stack } from "@scality/core-ui";
3
3
  import { convertRemToPixels } from "@scality/core-ui/dist/components/tablev2/TableUtils";
4
- import { Box, Input, Select } from "@scality/core-ui/dist/next";
4
+ import { Input, Select } from "@scality/core-ui/dist/next";
5
5
  import { ArrayFieldActions } from "./ArrayFieldActions.js";
6
+ import { FormCell, FormColumnHeaders, FormRow } from "./FormGrid.js";
6
7
  const filterTypeOptions = [
7
8
  {
8
9
  value: 'none',
@@ -68,42 +69,22 @@ function FilterFormSection({ filterType, onFilterTypeChange, prefixRegister, tag
68
69
  direction: "vertical",
69
70
  gap: "r8",
70
71
  children: [
71
- /*#__PURE__*/ jsxs(Stack, {
72
- gap: "r8",
73
- children: [
74
- /*#__PURE__*/ jsx(Box, {
75
- flex: "1",
76
- children: /*#__PURE__*/ jsx(Text, {
77
- color: "textSecondary",
78
- children: "Key"
79
- })
80
- }),
81
- /*#__PURE__*/ jsx(Box, {
82
- flex: "1",
83
- children: /*#__PURE__*/ jsx(Text, {
84
- color: "textSecondary",
85
- children: "Value"
86
- })
87
- }),
88
- /*#__PURE__*/ jsx(Box, {
89
- width: "80px"
90
- })
91
- ]
92
- }),
93
- tagFields.map((field, index)=>/*#__PURE__*/ jsxs(Stack, {
94
- gap: "r8",
72
+ /*#__PURE__*/ jsx(FormColumnHeaders, {}),
73
+ tagFields.map((field, index)=>/*#__PURE__*/ jsxs(FormRow, {
95
74
  children: [
96
- /*#__PURE__*/ jsx(Input, {
97
- id: `tag-key-${index}`,
98
- placeholder: "Key",
99
- size: "1/2",
100
- ...tagKeyRegister(index)
75
+ /*#__PURE__*/ jsx(FormCell, {
76
+ children: /*#__PURE__*/ jsx(Input, {
77
+ id: `tag-key-${index}`,
78
+ placeholder: "Key",
79
+ ...tagKeyRegister(index)
80
+ })
101
81
  }),
102
- /*#__PURE__*/ jsx(Input, {
103
- id: `tag-value-${index}`,
104
- placeholder: "Value",
105
- size: "1/2",
106
- ...tagValueRegister(index)
82
+ /*#__PURE__*/ jsx(FormCell, {
83
+ children: /*#__PURE__*/ jsx(Input, {
84
+ id: `tag-value-${index}`,
85
+ placeholder: "Value",
86
+ ...tagValueRegister(index)
87
+ })
107
88
  }),
108
89
  /*#__PURE__*/ jsx(ArrayFieldActions, {
109
90
  showAdd: index === tagFields.length - 1,
@@ -0,0 +1,7 @@
1
+ export declare const FormRow: import("styled-components").StyledComponent<"div", any, {
2
+ columns?: string;
3
+ }, never>;
4
+ export declare const FormCell: import("styled-components").StyledComponent<"div", any, {}, never>;
5
+ export declare const FormColumnHeaders: ({ wideKey }: {
6
+ wideKey?: boolean;
7
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Text, spacing } from "@scality/core-ui";
3
+ import styled_components from "styled-components";
4
+ const ACTIONS_WIDTH = `calc(${spacing.r32} * 2 + ${spacing.r8})`;
5
+ const FormRow = styled_components.div`
6
+ display: grid;
7
+ grid-template-columns: ${({ columns })=>columns || '1fr 1fr'} ${ACTIONS_WIDTH};
8
+ gap: ${spacing.r8};
9
+ align-items: center;
10
+ `;
11
+ const FormCell = styled_components.div`
12
+ min-width: 0;
13
+ > * {
14
+ width: 100% !important;
15
+ }
16
+ `;
17
+ const FormColumnHeaders = ({ wideKey })=>/*#__PURE__*/ jsxs(FormRow, {
18
+ columns: wideKey ? '3fr auto 2fr' : void 0,
19
+ children: [
20
+ /*#__PURE__*/ jsx(Text, {
21
+ color: "textPrimary",
22
+ children: "Key"
23
+ }),
24
+ wideKey && /*#__PURE__*/ jsx("span", {
25
+ style: {
26
+ visibility: 'hidden'
27
+ },
28
+ children: ":"
29
+ }),
30
+ /*#__PURE__*/ jsx(Text, {
31
+ color: "textPrimary",
32
+ children: "Value"
33
+ }),
34
+ /*#__PURE__*/ jsx("span", {})
35
+ ]
36
+ });
37
+ export { FormCell, FormColumnHeaders, FormRow };