Skip to content

Commit 214b841

Browse files
committed
feat(content-explorer): Disable selection while editing and handle edge cases
1 parent 835bf3a commit 214b841

File tree

8 files changed

+176
-56
lines changed

8 files changed

+176
-56
lines changed

src/elements/common/sub-header/SubHeader.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export interface SubHeaderProps {
3030
onGridViewSliderChange?: (newSliderValue: number) => void;
3131
onItemClick: (id: string | null, triggerNavigationEvent: boolean | null) => void;
3232
onSortChange: (sortBy: string, sortDirection: string) => void;
33-
onMetadataSidePanelToggle?: () => void;
33+
onSidePanelToggle?: () => void;
3434
onUpload: () => void;
3535
onViewModeChange?: (viewMode: ViewMode) => void;
3636
portalElement?: HTMLElement;
@@ -57,7 +57,7 @@ const SubHeader = ({
5757
onCreate,
5858
onItemClick,
5959
onSortChange,
60-
onMetadataSidePanelToggle,
60+
onSidePanelToggle,
6161
onUpload,
6262
onViewModeChange,
6363
portalElement,
@@ -115,7 +115,7 @@ const SubHeader = ({
115115
onCreate={onCreate}
116116
onGridViewSliderChange={onGridViewSliderChange}
117117
onSortChange={onSortChange}
118-
onMetadataSidePanelToggle={onMetadataSidePanelToggle}
118+
onSidePanelToggle={onSidePanelToggle}
119119
onUpload={onUpload}
120120
onViewModeChange={onViewModeChange}
121121
portalElement={portalElement}

src/elements/common/sub-header/SubHeaderRight.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export interface SubHeaderRightProps {
3232
onCreate: () => void;
3333
onGridViewSliderChange: (newSliderValue: number) => void;
3434
onSortChange: (sortBy: SortBy, sortDirection: SortDirection) => void;
35-
onMetadataSidePanelToggle?: () => void;
35+
onSidePanelToggle?: () => void;
3636
onUpload: () => void;
3737
onViewModeChange?: (viewMode: ViewMode) => void;
3838
portalElement?: HTMLElement;
@@ -53,7 +53,7 @@ const SubHeaderRight = ({
5353
onCreate,
5454
onGridViewSliderChange,
5555
onSortChange,
56-
onMetadataSidePanelToggle,
56+
onSidePanelToggle,
5757
onUpload,
5858
onViewModeChange,
5959
portalElement,
@@ -107,7 +107,7 @@ const SubHeaderRight = ({
107107
{bulkItemActions && bulkItemActions.length > 0 && (
108108
<BulkItemActionMenu actions={bulkItemActions} selectedItemIds={selectedItemIds} />
109109
)}
110-
<Button icon={Pencil} size="large" variant="primary" onClick={onMetadataSidePanelToggle}>
110+
<Button icon={Pencil} size="large" variant="primary" onClick={onSidePanelToggle}>
111111
{formatMessage(messages.metadata)}
112112
</Button>
113113
</>

src/elements/content-explorer/Content.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export interface ContentProps extends Required<ItemEventHandlers>, Required<Item
3232
features?: FeatureConfig;
3333
fieldsToShow?: FieldsToShow;
3434
gridColumnCount?: number;
35+
isEditing?: boolean;
3536
isMedium: boolean;
3637
isSmall: boolean;
3738
isTouch: boolean;
@@ -59,6 +60,7 @@ const Content = ({
5960
features,
6061
fieldsToShow = [],
6162
gridColumnCount,
63+
isEditing = false,
6264
metadataTemplate,
6365
metadataViewProps,
6466
onMetadataFilter,
@@ -94,6 +96,7 @@ const Content = ({
9496
currentCollection={currentCollection}
9597
isLoading={percentLoaded !== 100}
9698
hasError={view === VIEW_ERROR}
99+
isEditing={isEditing}
97100
metadataTemplate={metadataTemplate}
98101
onMetadataFilter={onMetadataFilter}
99102
onSortChange={onSortChange}

src/elements/content-explorer/ContentExplorer.tsx

Lines changed: 69 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ export interface ContentExplorerProps {
171171
uploadHost?: string;
172172
}
173173

174+
enum SidePanelState {
175+
CLOSED = 'CLOSED',
176+
OPEN = 'OPEN',
177+
EDITING = 'EDITING',
178+
}
179+
174180
type State = {
175181
currentCollection: Collection;
176182
currentOffset: number;
@@ -182,7 +188,6 @@ type State = {
182188
isCreateFolderModalOpen: boolean;
183189
isDeleteModalOpen: boolean;
184190
isLoading: boolean;
185-
isMetadataSidePanelOpen: boolean;
186191
isPreviewModalOpen: boolean;
187192
isRenameModalOpen: boolean;
188193
isShareModalOpen: boolean;
@@ -194,6 +199,7 @@ type State = {
194199
searchQuery: string;
195200
selected?: BoxItem;
196201
selectedItemIds: Selection;
202+
sidePanelState: SidePanelState;
197203
sortBy: SortBy | string;
198204
sortDirection: SortDirection;
199205
view: View;
@@ -310,7 +316,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
310316
isCreateFolderModalOpen: false,
311317
isDeleteModalOpen: false,
312318
isLoading: false,
313-
isMetadataSidePanelOpen: false,
319+
sidePanelState: SidePanelState.CLOSED,
314320
isPreviewModalOpen: false,
315321
isRenameModalOpen: false,
316322
isShareModalOpen: false,
@@ -1022,23 +1028,21 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
10221028
* @return {void}
10231029
*/
10241030
validateSelectedItemIds = (items: BoxItem[]): void => {
1031+
const { onSelectionChange: onSelectionChangeExternal } = this.props.metadataViewProps ?? {};
10251032
const { selectedItemIds } = this.state;
10261033

1027-
if (selectedItemIds === 'all' || selectedItemIds.size === 0) {
1028-
// If all/none items are selected, no need to change anything
1029-
return;
1030-
}
1031-
1032-
const validSelectedIds = new Set<string>();
1034+
let validSelectedIds = new Set<string>();
10331035

1034-
items.forEach(item => {
1035-
if (selectedItemIds.has(item.id)) {
1036-
validSelectedIds.add(item.id);
1037-
}
1038-
});
1036+
if (selectedItemIds === 'all') {
1037+
validSelectedIds = new Set(items.map(item => item.id));
1038+
} else if (selectedItemIds.size > 0) {
1039+
validSelectedIds = new Set(items.filter(item => selectedItemIds.has(item.id)).map(item => item.id));
1040+
}
10391041

10401042
if (!isEqual(validSelectedIds, selectedItemIds)) {
1043+
onSelectionChangeExternal?.(validSelectedIds);
10411044
this.setState({ selectedItemIds: validSelectedIds });
1045+
this.closeSidePanel();
10421046
}
10431047
};
10441048

@@ -1511,6 +1515,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
15111515
isShareModalOpen: false,
15121516
isUploadModalOpen: false,
15131517
isPreviewModalOpen: false,
1518+
sidePanelState: SidePanelState.CLOSED,
15141519
});
15151520

15161521
const {
@@ -1652,22 +1657,14 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
16521657
'hasError' | 'currentCollection' | 'metadataTemplate'
16531658
> => {
16541659
const { metadataViewProps } = this.props;
1655-
const { onSelectionChange } = metadataViewProps ?? {};
16561660
const { currentPageNumber, markers, selectedItemIds } = this.state;
16571661
const hasNextMarker: boolean = !!markers[currentPageNumber + 1];
16581662
const hasPrevMarker: boolean = currentPageNumber === 1 || !!markers[currentPageNumber - 1];
16591663

16601664
return {
16611665
...metadataViewProps,
16621666
selectedKeys: selectedItemIds,
1663-
onSelectionChange: (ids: Selection) => {
1664-
onSelectionChange?.(ids);
1665-
const isSelectionEmpty = ids !== 'all' && ids.size === 0;
1666-
this.setState({
1667-
selectedItemIds: ids,
1668-
...(isSelectionEmpty && { isMetadataSidePanelOpen: false }),
1669-
});
1670-
},
1667+
onSelectionChange: this.handleSelectedIdsChange,
16711668
paginationProps: {
16721669
onMarkerBasedPageChange: this.markerBasedPaginate,
16731670
hasNextMarker,
@@ -1754,11 +1751,37 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
17541751
});
17551752
};
17561753

1754+
handleSelectedIdsChange = (ids: Selection) => {
1755+
const { onSelectionChange: onSelectionChangeExternal } = this.props.metadataViewProps ?? {};
1756+
const { sidePanelState } = this.state;
1757+
1758+
// Prevent changing selection when in edit mode
1759+
if (sidePanelState === SidePanelState.EDITING) {
1760+
return;
1761+
}
1762+
1763+
onSelectionChangeExternal?.(ids);
1764+
1765+
this.setState({
1766+
selectedItemIds: ids,
1767+
});
1768+
1769+
const isSelectionEmpty = ids !== 'all' && ids.size === 0;
1770+
if (isSelectionEmpty) {
1771+
this.closeSidePanel();
1772+
}
1773+
};
1774+
17571775
clearSelectedItemIds = () => {
1776+
const { onSelectionChange: onSelectionChangeExternal } = this.props.metadataViewProps ?? {};
1777+
1778+
onSelectionChangeExternal?.(new Set());
1779+
17581780
this.setState({
17591781
selectedItemIds: new Set(),
1760-
isMetadataSidePanelOpen: false,
17611782
});
1783+
1784+
this.closeSidePanel();
17621785
};
17631786

17641787
/**
@@ -1767,20 +1790,23 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
17671790
* @private
17681791
* @return {void}
17691792
*/
1770-
onMetadataSidePanelToggle = () => {
1793+
onSidePanelToggle = () => {
17711794
this.setState(prevState => ({
1772-
isMetadataSidePanelOpen: !prevState.isMetadataSidePanelOpen,
1795+
sidePanelState:
1796+
prevState.sidePanelState === SidePanelState.CLOSED ? SidePanelState.OPEN : SidePanelState.CLOSED,
17731797
}));
17741798
};
17751799

1776-
/**
1777-
* Close metadata side panel
1778-
*
1779-
* @private
1780-
* @return {void}
1781-
*/
1782-
closeMetadataSidePanel = () => {
1783-
this.setState({ isMetadataSidePanelOpen: false });
1800+
closeSidePanel = () => {
1801+
this.setState({
1802+
sidePanelState: SidePanelState.CLOSED,
1803+
});
1804+
};
1805+
1806+
onMetadataEditingChange = (isEditing: boolean) => {
1807+
this.setState({
1808+
sidePanelState: isEditing ? SidePanelState.EDITING : SidePanelState.OPEN,
1809+
});
17841810
};
17851811

17861812
filterMetadata = (fields: ExternalFilterValues) => {
@@ -1848,7 +1874,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
18481874
isCreateFolderModalOpen,
18491875
isDeleteModalOpen,
18501876
isLoading,
1851-
isMetadataSidePanelOpen,
1877+
sidePanelState,
18521878
isPreviewModalOpen,
18531879
isRenameModalOpen,
18541880
isShareModalOpen,
@@ -1878,6 +1904,9 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
18781904

18791905
const metadataViewProps = this.getMetadataViewProps();
18801906

1907+
const isSidePanelOpen = sidePanelState !== SidePanelState.CLOSED;
1908+
const isEditing = sidePanelState === SidePanelState.EDITING;
1909+
18811910
/* eslint-disable jsx-a11y/no-static-element-interactions */
18821911
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
18831912
return (
@@ -1911,7 +1940,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
19111940
onGridViewSliderChange={this.onGridViewSliderChange}
19121941
onItemClick={this.fetchFolder}
19131942
onSortChange={this.sort}
1914-
onMetadataSidePanelToggle={this.onMetadataSidePanelToggle}
1943+
onSidePanelToggle={this.onSidePanelToggle}
19151944
onViewModeChange={this.changeViewMode}
19161945
portalElement={this.rootElement}
19171946
selectedItemIds={selectedItemIds}
@@ -1927,6 +1956,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
19271956
currentCollection={currentCollection}
19281957
features={features}
19291958
gridColumnCount={Math.min(gridColumnCount, maxGridColumnCount)}
1959+
isEditing={isEditing}
19301960
isMedium={isMedium}
19311961
isSmall={isSmall}
19321962
isTouch={isTouch}
@@ -1964,11 +1994,13 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
19641994
</Footer>
19651995
)}
19661996
</div>
1967-
{isDefaultViewMetadata && isMetadataViewV2Feature && isMetadataSidePanelOpen && (
1997+
{isDefaultViewMetadata && isMetadataViewV2Feature && isSidePanelOpen && (
19681998
<MetadataSidePanel
19691999
currentCollection={currentCollection}
2000+
isEditing={isEditing}
19702001
metadataTemplate={metadataTemplate}
1971-
onClose={this.closeMetadataSidePanel}
2002+
onClose={this.closeSidePanel}
2003+
onEditingChange={this.onMetadataEditingChange}
19722004
onUpdate={this.updateMetadataV2}
19732005
refreshCollection={this.refreshCollection}
19742006
selectedItemIds={selectedItemIds}

src/elements/content-explorer/MetadataSidePanel.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import './MetadataSidePanel.scss';
2525
export interface MetadataSidePanelProps {
2626
currentCollection: Collection;
2727
metadataTemplate: MetadataTemplate;
28+
isEditing: boolean;
29+
onEditingChange: (isEditing: boolean) => void;
2830
onClose: () => void;
2931
onUpdate: (
3032
items: BoxItem[],
@@ -41,14 +43,15 @@ export interface MetadataSidePanelProps {
4143
const MetadataSidePanel = ({
4244
currentCollection,
4345
metadataTemplate,
46+
isEditing,
47+
onEditingChange,
4448
onClose,
4549
onUpdate,
4650
refreshCollection,
4751
selectedItemIds,
4852
}: MetadataSidePanelProps) => {
4953
const { addNotification } = useNotification();
5054
const { formatMessage } = useIntl();
51-
const [isEditing, setIsEditing] = useState<boolean>(false);
5255
const [isUnsavedChangesModalOpen, setIsUnsavedChangesModalOpen] = useState<boolean>(false);
5356

5457
const selectedItemText = useSelectedItemText(currentCollection, selectedItemIds);
@@ -59,16 +62,16 @@ const MetadataSidePanel = ({
5962
const templateInstance = useTemplateInstance(metadataTemplate, selectedItems, isEditing);
6063

6164
const handleMetadataInstanceEdit = () => {
62-
setIsEditing(true);
65+
onEditingChange(true);
6366
};
6467

6568
const handleMetadataInstanceFormCancel = () => {
66-
setIsEditing(false);
69+
onEditingChange(false);
6770
};
6871

6972
const handleMetadataInstanceFormDiscardUnsavedChanges = () => {
7073
setIsUnsavedChangesModalOpen(false);
71-
setIsEditing(false);
74+
onEditingChange(false);
7275
};
7376

7477
const handleUpdateMetadataSuccess = () => {
@@ -81,7 +84,7 @@ const MetadataSidePanel = ({
8184
typeIconAriaLabel: formatMessage(messages.success),
8285
variant: 'success',
8386
});
84-
setIsEditing(false);
87+
onEditingChange(false);
8588
refreshCollection();
8689
};
8790

src/elements/content-explorer/MetadataViewContainer.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ function transformInternalFieldsToPublic(fields: FilterValues): ExternalFilterVa
108108
export interface MetadataViewContainerProps extends Omit<MetadataViewProps, 'items' | 'actionBarProps'> {
109109
actionBarProps?: ActionBarProps;
110110
currentCollection: Collection;
111+
isEditing?: boolean;
111112
metadataTemplate: MetadataTemplate;
112113
onMetadataFilter: (fields: ExternalFilterValues) => void;
113114
/* Internally controlled onSortChange prop for the MetadataView component. */
@@ -118,6 +119,7 @@ const MetadataViewContainer = ({
118119
actionBarProps,
119120
columns,
120121
currentCollection,
122+
isEditing = false,
121123
metadataTemplate,
122124
onMetadataFilter,
123125
onSortChange: onSortChangeInternal,
@@ -265,6 +267,7 @@ const MetadataViewContainer = ({
265267
columns={newColumns}
266268
items={items}
267269
tableProps={newTableProps}
270+
areSelectionCheckboxesDisabled={isEditing}
268271
{...rest}
269272
/>
270273
);

0 commit comments

Comments
 (0)