Skip to content

Commit a1ab4cb

Browse files
committed
feat(content-explorer): Disable selection while editing
1 parent 2e9dd6a commit a1ab4cb

File tree

12 files changed

+187
-78
lines changed

12 files changed

+187
-78
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@
137137
"@box/languages": "^1.0.0",
138138
"@box/metadata-editor": "^0.122.12",
139139
"@box/metadata-filter": "^1.30.1",
140-
"@box/metadata-view": "^0.54.0",
140+
"@box/metadata-view": "^0.59.0",
141141
"@box/react-virtualized": "^9.22.3-rc-box.10",
142142
"@box/types": "^0.2.1",
143143
"@box/unified-share-modal": "^0.52.0",
@@ -307,7 +307,7 @@
307307
"@box/item-icon": "^0.27.1",
308308
"@box/metadata-editor": "^0.122.12",
309309
"@box/metadata-filter": "^1.30.1",
310-
"@box/metadata-view": "^0.54.0",
310+
"@box/metadata-view": "^0.59.0",
311311
"@box/react-virtualized": "^9.22.3-rc-box.10",
312312
"@box/types": "^0.2.1",
313313
"@box/unified-share-modal": "^0.52.0",

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: 59 additions & 40 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,
@@ -1024,21 +1030,16 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
10241030
validateSelectedItemIds = (items: BoxItem[]): void => {
10251031
const { selectedItemIds } = this.state;
10261032

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>();
1033+
let validSelectedIds = new Set<string>();
10331034

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

10401041
if (!isEqual(validSelectedIds, selectedItemIds)) {
1041-
this.setState({ selectedItemIds: validSelectedIds });
1042+
this.handleSelectedIdsChange(validSelectedIds);
10421043
}
10431044
};
10441045

@@ -1511,6 +1512,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
15111512
isShareModalOpen: false,
15121513
isUploadModalOpen: false,
15131514
isPreviewModalOpen: false,
1515+
sidePanelState: SidePanelState.CLOSED,
15141516
});
15151517

15161518
const {
@@ -1652,22 +1654,14 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
16521654
'hasError' | 'currentCollection' | 'metadataTemplate'
16531655
> => {
16541656
const { metadataViewProps } = this.props;
1655-
const { onSelectionChange } = metadataViewProps ?? {};
16561657
const { currentPageNumber, markers, selectedItemIds } = this.state;
16571658
const hasNextMarker: boolean = !!markers[currentPageNumber + 1];
16581659
const hasPrevMarker: boolean = currentPageNumber === 1 || !!markers[currentPageNumber - 1];
16591660

16601661
return {
16611662
...metadataViewProps,
16621663
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-
},
1664+
onSelectionChange: this.handleSelectedIdsChange,
16711665
paginationProps: {
16721666
onMarkerBasedPageChange: this.markerBasedPaginate,
16731667
hasNextMarker,
@@ -1754,11 +1748,27 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
17541748
});
17551749
};
17561750

1757-
clearSelectedItemIds = () => {
1751+
handleSelectedIdsChange = (ids: Selection, allowDuringEditing: boolean = false) => {
1752+
const { onSelectionChange: onSelectionChangeExternal } = this.props.metadataViewProps ?? {};
1753+
1754+
if (!allowDuringEditing && this.state.sidePanelState === SidePanelState.EDITING) {
1755+
return;
1756+
}
1757+
1758+
onSelectionChangeExternal?.(ids);
1759+
17581760
this.setState({
1759-
selectedItemIds: new Set(),
1760-
isMetadataSidePanelOpen: false,
1761+
selectedItemIds: ids,
17611762
});
1763+
1764+
const isSelectionEmpty = ids !== 'all' && ids.size === 0;
1765+
if (isSelectionEmpty) {
1766+
this.closeSidePanel();
1767+
}
1768+
};
1769+
1770+
clearSelectedItemIds = () => {
1771+
this.handleSelectedIdsChange(new Set(), true);
17621772
};
17631773

17641774
/**
@@ -1767,20 +1777,23 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
17671777
* @private
17681778
* @return {void}
17691779
*/
1770-
onMetadataSidePanelToggle = () => {
1780+
onSidePanelToggle = () => {
17711781
this.setState(prevState => ({
1772-
isMetadataSidePanelOpen: !prevState.isMetadataSidePanelOpen,
1782+
sidePanelState:
1783+
prevState.sidePanelState === SidePanelState.CLOSED ? SidePanelState.OPEN : SidePanelState.CLOSED,
17731784
}));
17741785
};
17751786

1776-
/**
1777-
* Close metadata side panel
1778-
*
1779-
* @private
1780-
* @return {void}
1781-
*/
1782-
closeMetadataSidePanel = () => {
1783-
this.setState({ isMetadataSidePanelOpen: false });
1787+
closeSidePanel = () => {
1788+
this.setState({
1789+
sidePanelState: SidePanelState.CLOSED,
1790+
});
1791+
};
1792+
1793+
onMetadataEditingChange = (isEditing: boolean) => {
1794+
this.setState({
1795+
sidePanelState: isEditing ? SidePanelState.EDITING : SidePanelState.OPEN,
1796+
});
17841797
};
17851798

17861799
filterMetadata = (fields: ExternalFilterValues) => {
@@ -1848,7 +1861,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
18481861
isCreateFolderModalOpen,
18491862
isDeleteModalOpen,
18501863
isLoading,
1851-
isMetadataSidePanelOpen,
1864+
sidePanelState,
18521865
isPreviewModalOpen,
18531866
isRenameModalOpen,
18541867
isShareModalOpen,
@@ -1878,6 +1891,9 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
18781891

18791892
const metadataViewProps = this.getMetadataViewProps();
18801893

1894+
const isSidePanelOpen = sidePanelState !== SidePanelState.CLOSED;
1895+
const isEditing = sidePanelState === SidePanelState.EDITING;
1896+
18811897
/* eslint-disable jsx-a11y/no-static-element-interactions */
18821898
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
18831899
return (
@@ -1911,7 +1927,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
19111927
onGridViewSliderChange={this.onGridViewSliderChange}
19121928
onItemClick={this.fetchFolder}
19131929
onSortChange={this.sort}
1914-
onMetadataSidePanelToggle={this.onMetadataSidePanelToggle}
1930+
onSidePanelToggle={this.onSidePanelToggle}
19151931
onViewModeChange={this.changeViewMode}
19161932
portalElement={this.rootElement}
19171933
selectedItemIds={selectedItemIds}
@@ -1927,6 +1943,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
19271943
currentCollection={currentCollection}
19281944
features={features}
19291945
gridColumnCount={Math.min(gridColumnCount, maxGridColumnCount)}
1946+
isEditing={isEditing}
19301947
isMedium={isMedium}
19311948
isSmall={isSmall}
19321949
isTouch={isTouch}
@@ -1964,11 +1981,13 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
19641981
</Footer>
19651982
)}
19661983
</div>
1967-
{isDefaultViewMetadata && isMetadataViewV2Feature && isMetadataSidePanelOpen && (
1984+
{isDefaultViewMetadata && isMetadataViewV2Feature && isSidePanelOpen && (
19681985
<MetadataSidePanel
19691986
currentCollection={currentCollection}
1987+
isEditing={isEditing}
19701988
metadataTemplate={metadataTemplate}
1971-
onClose={this.closeMetadataSidePanel}
1989+
onClose={this.closeSidePanel}
1990+
onEditingChange={this.onMetadataEditingChange}
19721991
onUpdate={this.updateMetadataV2}
19731992
refreshCollection={this.refreshCollection}
19741993
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)