Skip to content

Commit c08d9b5

Browse files
authored
feat(content-explorer): Disable selection while editing (#4317)
1 parent 2e9dd6a commit c08d9b5

File tree

12 files changed

+194
-79
lines changed

12 files changed

+194
-79
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: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ 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;
3839
itemActions?: ItemAction[];
3940
metadataTemplate?: MetadataTemplate;
4041
metadataViewProps?: Omit<
4142
MetadataViewContainerProps,
42-
'hasError' | 'currentCollection' | 'metadataTemplate' | 'onMetadataFilter'
43+
'hasError' | 'currentCollection' | 'metadataTemplate' | 'onMetadataFilter' | 'isEditing'
4344
>;
4445
onMetadataFilter?: (fields: ExternalFilterValues) => void;
4546
onMetadataUpdate: (
@@ -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: 65 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,17 @@ 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, true);
1043+
this.closeSidePanel();
10421044
}
10431045
};
10441046

@@ -1511,6 +1513,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
15111513
isShareModalOpen: false,
15121514
isUploadModalOpen: false,
15131515
isPreviewModalOpen: false,
1516+
sidePanelState: SidePanelState.CLOSED,
15141517
});
15151518

15161519
const {
@@ -1652,22 +1655,14 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
16521655
'hasError' | 'currentCollection' | 'metadataTemplate'
16531656
> => {
16541657
const { metadataViewProps } = this.props;
1655-
const { onSelectionChange } = metadataViewProps ?? {};
16561658
const { currentPageNumber, markers, selectedItemIds } = this.state;
16571659
const hasNextMarker: boolean = !!markers[currentPageNumber + 1];
16581660
const hasPrevMarker: boolean = currentPageNumber === 1 || !!markers[currentPageNumber - 1];
16591661

16601662
return {
16611663
...metadataViewProps,
16621664
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-
},
1665+
onSelectionChange: this.handleSelectedIdsChange,
16711666
paginationProps: {
16721667
onMarkerBasedPageChange: this.markerBasedPaginate,
16731668
hasNextMarker,
@@ -1754,11 +1749,28 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
17541749
});
17551750
};
17561751

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

17641776
/**
@@ -1767,20 +1779,27 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
17671779
* @private
17681780
* @return {void}
17691781
*/
1770-
onMetadataSidePanelToggle = () => {
1782+
onSidePanelToggle = () => {
17711783
this.setState(prevState => ({
1772-
isMetadataSidePanelOpen: !prevState.isMetadataSidePanelOpen,
1784+
sidePanelState:
1785+
prevState.sidePanelState === SidePanelState.CLOSED ? SidePanelState.OPEN : SidePanelState.CLOSED,
17731786
}));
17741787
};
17751788

1776-
/**
1777-
* Close metadata side panel
1778-
*
1779-
* @private
1780-
* @return {void}
1781-
*/
1782-
closeMetadataSidePanel = () => {
1783-
this.setState({ isMetadataSidePanelOpen: false });
1789+
closeSidePanel = () => {
1790+
this.setState({
1791+
sidePanelState: SidePanelState.CLOSED,
1792+
});
1793+
};
1794+
1795+
onSidePanelEditingChange = (isEditing: boolean) => {
1796+
const { sidePanelState } = this.state;
1797+
1798+
if (sidePanelState !== SidePanelState.CLOSED) {
1799+
this.setState({
1800+
sidePanelState: isEditing ? SidePanelState.EDITING : SidePanelState.OPEN,
1801+
});
1802+
}
17841803
};
17851804

17861805
filterMetadata = (fields: ExternalFilterValues) => {
@@ -1848,7 +1867,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
18481867
isCreateFolderModalOpen,
18491868
isDeleteModalOpen,
18501869
isLoading,
1851-
isMetadataSidePanelOpen,
1870+
sidePanelState,
18521871
isPreviewModalOpen,
18531872
isRenameModalOpen,
18541873
isShareModalOpen,
@@ -1878,6 +1897,9 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
18781897

18791898
const metadataViewProps = this.getMetadataViewProps();
18801899

1900+
const isSidePanelOpen = sidePanelState !== SidePanelState.CLOSED;
1901+
const isEditing = sidePanelState === SidePanelState.EDITING;
1902+
18811903
/* eslint-disable jsx-a11y/no-static-element-interactions */
18821904
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
18831905
return (
@@ -1911,7 +1933,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
19111933
onGridViewSliderChange={this.onGridViewSliderChange}
19121934
onItemClick={this.fetchFolder}
19131935
onSortChange={this.sort}
1914-
onMetadataSidePanelToggle={this.onMetadataSidePanelToggle}
1936+
onSidePanelToggle={this.onSidePanelToggle}
19151937
onViewModeChange={this.changeViewMode}
19161938
portalElement={this.rootElement}
19171939
selectedItemIds={selectedItemIds}
@@ -1927,6 +1949,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
19271949
currentCollection={currentCollection}
19281950
features={features}
19291951
gridColumnCount={Math.min(gridColumnCount, maxGridColumnCount)}
1952+
isEditing={isEditing}
19301953
isMedium={isMedium}
19311954
isSmall={isSmall}
19321955
isTouch={isTouch}
@@ -1964,11 +1987,13 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
19641987
</Footer>
19651988
)}
19661989
</div>
1967-
{isDefaultViewMetadata && isMetadataViewV2Feature && isMetadataSidePanelOpen && (
1990+
{isDefaultViewMetadata && isMetadataViewV2Feature && isSidePanelOpen && (
19681991
<MetadataSidePanel
19691992
currentCollection={currentCollection}
1993+
isEditing={isEditing}
19701994
metadataTemplate={metadataTemplate}
1971-
onClose={this.closeMetadataSidePanel}
1995+
onClose={this.closeSidePanel}
1996+
onEditingChange={this.onSidePanelEditingChange}
19721997
onUpdate={this.updateMetadataV2}
19731998
refreshCollection={this.refreshCollection}
19741999
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

0 commit comments

Comments
 (0)