Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions i18n/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1812,6 +1812,8 @@ boxui.shareMenu.shortcutOnly = Shortcut Only
boxui.shareMenu.viewAndDownload = View and Download
# Description of permissions granted to users who have access to the shared link
boxui.shareMenu.viewOnly = View Only
# Text for metadata button that will open the metadata side panel
boxui.subHeader.metadata = Metadata
# Error message for empty time formats. "HH:MM A" should be localized.
boxui.timeInput.emptyTimeError = Required field. Enter a time in the format HH:MM A.
# Error message for invalid time formats. "HH:MM A" should be localized.
Expand Down
81 changes: 46 additions & 35 deletions src/elements/common/sub-header/SubHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import SubHeaderLeft from './SubHeaderLeft';
import SubHeaderRight from './SubHeaderRight';
import type { ViewMode } from '../flowTypes';
import type { View, Collection } from '../../../common/types/core';
import { VIEW_MODE_LIST } from '../../../constants';
import { VIEW_MODE_LIST, VIEW_METADATA } from '../../../constants';
import { useFeatureEnabled } from '../feature-checking';

import './SubHeader.scss';

Expand Down Expand Up @@ -51,39 +52,49 @@ const SubHeader = ({
rootName,
view,
viewMode = VIEW_MODE_LIST,
}: SubHeaderProps) => (
<PageHeader.Root className="be-sub-header" data-testid="be-sub-header" variant="inline">
<PageHeader.StartElements>
<SubHeaderLeft
currentCollection={currentCollection}
isSmall={isSmall}
onItemClick={onItemClick}
portalElement={portalElement}
rootId={rootId}
rootName={rootName}
view={view}
/>
</PageHeader.StartElements>
<PageHeader.EndElements>
<SubHeaderRight
canCreateNewFolder={canCreateNewFolder}
canUpload={canUpload}
currentCollection={currentCollection}
gridColumnCount={gridColumnCount}
gridMaxColumns={gridMaxColumns}
gridMinColumns={gridMinColumns}
maxGridColumnCountForWidth={maxGridColumnCountForWidth}
onCreate={onCreate}
onGridViewSliderChange={onGridViewSliderChange}
onSortChange={onSortChange}
onUpload={onUpload}
onViewModeChange={onViewModeChange}
portalElement={portalElement}
view={view}
viewMode={viewMode}
/>
</PageHeader.EndElements>
</PageHeader.Root>
);
}: SubHeaderProps) => {
const isMetadataViewV2Feature = useFeatureEnabled('contentExplorer.metadataViewV2');

if (view === VIEW_METADATA && !isMetadataViewV2Feature) {
return null;
}

return (
<PageHeader.Root className="be-sub-header" data-testid="be-sub-header" variant="inline">
<PageHeader.StartElements>
{view !== VIEW_METADATA && !isMetadataViewV2Feature && (
<SubHeaderLeft
currentCollection={currentCollection}
isSmall={isSmall}
onItemClick={onItemClick}
portalElement={portalElement}
rootId={rootId}
rootName={rootName}
view={view}
/>
)}
</PageHeader.StartElements>
<PageHeader.EndElements>
<SubHeaderRight
canCreateNewFolder={canCreateNewFolder}
canUpload={canUpload}
currentCollection={currentCollection}
gridColumnCount={gridColumnCount}
gridMaxColumns={gridMaxColumns}
gridMinColumns={gridMinColumns}
maxGridColumnCountForWidth={maxGridColumnCountForWidth}
onCreate={onCreate}
onGridViewSliderChange={onGridViewSliderChange}
onSortChange={onSortChange}
onUpload={onUpload}
onViewModeChange={onViewModeChange}
portalElement={portalElement}
view={view}
viewMode={viewMode}
/>
</PageHeader.EndElements>
</PageHeader.Root>
);
};

export default SubHeader;
65 changes: 43 additions & 22 deletions src/elements/common/sub-header/SubHeaderRight.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import * as React from 'react';
import { Button } from '@box/blueprint-web';
import { Pencil } from '@box/blueprint-web-assets/icons/Fill';
import { useIntl } from 'react-intl';
import Sort from './Sort';
import Add from './Add';
import GridViewSlider from '../../../components/grid-view/GridViewSlider';
import ViewModeChangeButton from './ViewModeChangeButton';
import { VIEW_FOLDER, VIEW_MODE_GRID } from '../../../constants';
import { VIEW_FOLDER, VIEW_MODE_GRID, VIEW_METADATA } from '../../../constants';
import { useFeatureEnabled } from '../feature-checking';

import type { ViewMode } from '../flowTypes';
import type { SortBy, SortDirection, View, Collection } from '../../../common/types/core';

import messages from './messages';

import './SubHeaderRight.scss';

export interface SubHeaderRightProps {
Expand Down Expand Up @@ -43,36 +51,49 @@ const SubHeaderRight = ({
view,
viewMode,
}: SubHeaderRightProps) => {
const { formatMessage } = useIntl();
const isMetadataViewV2Feature = useFeatureEnabled('contentExplorer.metadataViewV2');
const { items = [] }: Collection = currentCollection;
const hasGridView: boolean = !!gridColumnCount;
const hasItems: boolean = items.length > 0;
const isFolder: boolean = view === VIEW_FOLDER;
const showSort: boolean = isFolder && hasItems;
const showAdd: boolean = (!!canUpload || !!canCreateNewFolder) && isFolder;
const isMetadataView: boolean = view === VIEW_METADATA;
return (
<div className="be-sub-header-right">
{hasItems && viewMode === VIEW_MODE_GRID && (
<GridViewSlider
columnCount={gridColumnCount}
gridMaxColumns={gridMaxColumns}
gridMinColumns={gridMinColumns}
maxColumnCount={maxGridColumnCountForWidth}
onChange={onGridViewSliderChange}
/>
{!isMetadataView && (
<>
{hasItems && viewMode === VIEW_MODE_GRID && (
<GridViewSlider
columnCount={gridColumnCount}
gridMaxColumns={gridMaxColumns}
gridMinColumns={gridMinColumns}
maxColumnCount={maxGridColumnCountForWidth}
onChange={onGridViewSliderChange}
/>
)}
{hasItems && hasGridView && (
<ViewModeChangeButton viewMode={viewMode} onViewModeChange={onViewModeChange} />
)}
{showSort && <Sort onSortChange={onSortChange} portalElement={portalElement} />}
{showAdd && (
<Add
isDisabled={!isFolder}
onCreate={onCreate}
onUpload={onUpload}
portalElement={portalElement}
showCreate={canCreateNewFolder}
showUpload={canUpload}
/>
)}
</>
)}
{hasItems && hasGridView && (
<ViewModeChangeButton viewMode={viewMode} onViewModeChange={onViewModeChange} />
)}
{showSort && <Sort onSortChange={onSortChange} portalElement={portalElement} />}
{showAdd && (
<Add
isDisabled={!isFolder}
onCreate={onCreate}
onUpload={onUpload}
portalElement={portalElement}
showCreate={canCreateNewFolder}
showUpload={canUpload}
/>

{isMetadataView && isMetadataViewV2Feature && (
<Button icon={Pencil} size="large" variant="primary">
{formatMessage(messages.metadata)}
</Button>
)}
</div>
);
Expand Down
11 changes: 11 additions & 0 deletions src/elements/common/sub-header/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineMessages } from 'react-intl';

const messages = defineMessages({
metadata: {
defaultMessage: 'Metadata',
description: 'Text for metadata button that will open the metadata side panel',
id: 'boxui.subHeader.metadata',
},
});

export default messages;
8 changes: 3 additions & 5 deletions src/elements/content-explorer/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,21 @@ const Content = ({
const isMetadataBasedView = view === VIEW_METADATA;
const isListView = !isMetadataBasedView && viewMode === VIEW_MODE_LIST; // Folder view or Recents view
const isGridView = !isMetadataBasedView && viewMode === VIEW_MODE_GRID; // Folder view or Recents view

const isMetadataViewV2Feature = isFeatureEnabled(features, 'contentExplorer.metadataViewV2');
return (
<div className="bce-content">
{view === VIEW_ERROR || view === VIEW_SELECTED ? null : <ProgressBar percent={percentLoaded} />}

{isViewEmpty && <EmptyView view={view} isLoading={percentLoaded !== 100} />}
{!isFeatureEnabled(features, 'contentExplorer.metadataViewV2') && !isViewEmpty && isMetadataBasedView && (
{!isMetadataViewV2Feature && !isViewEmpty && isMetadataBasedView && (
<MetadataBasedItemList
currentCollection={currentCollection}
fieldsToShow={fieldsToShow}
onMetadataUpdate={onMetadataUpdate}
{...rest}
/>
)}
{isFeatureEnabled(features, 'contentExplorer.metadataViewV2') && !isViewEmpty && isMetadataBasedView && (
<MetadataView />
)}
{isMetadataViewV2Feature && !isViewEmpty && isMetadataBasedView && <MetadataView />}
{!isViewEmpty && isListView && (
<ItemList
items={items}
Expand Down
50 changes: 24 additions & 26 deletions src/elements/content-explorer/ContentExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1649,32 +1649,30 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
<div id={this.id} className={styleClassName} ref={measureRef} data-testid="content-explorer">
<ThemingStyles selector={`#${this.id}`} theme={theme} />
<div className="be-app-element" onKeyDown={this.onKeyDown} tabIndex={0}>
{!isDefaultViewMetadata && (
<>
<Header view={view} logoUrl={logoUrl} onSearch={this.search} />
<SubHeader
view={view}
viewMode={viewMode}
rootId={rootFolderId}
isSmall={isSmall}
rootName={rootName}
currentCollection={currentCollection}
canUpload={allowUpload}
canCreateNewFolder={allowCreate}
gridColumnCount={gridColumnCount}
gridMaxColumns={GRID_VIEW_MAX_COLUMNS}
gridMinColumns={GRID_VIEW_MIN_COLUMNS}
maxGridColumnCountForWidth={maxGridColumnCount}
onUpload={this.upload}
onCreate={this.createFolder}
onGridViewSliderChange={this.onGridViewSliderChange}
onItemClick={this.fetchFolder}
onSortChange={this.sort}
onViewModeChange={this.changeViewMode}
portalElement={this.rootElement}
/>
</>
)}
{!isDefaultViewMetadata && <Header view={view} logoUrl={logoUrl} onSearch={this.search} />}

<SubHeader
view={view}
viewMode={viewMode}
rootId={rootFolderId}
isSmall={isSmall}
rootName={rootName}
currentCollection={currentCollection}
canUpload={allowUpload}
canCreateNewFolder={allowCreate}
gridColumnCount={gridColumnCount}
gridMaxColumns={GRID_VIEW_MAX_COLUMNS}
gridMinColumns={GRID_VIEW_MIN_COLUMNS}
maxGridColumnCountForWidth={maxGridColumnCount}
onUpload={this.upload}
onCreate={this.createFolder}
onGridViewSliderChange={this.onGridViewSliderChange}
onItemClick={this.fetchFolder}
onSortChange={this.sort}
onViewModeChange={this.changeViewMode}
portalElement={this.rootElement}
/>

<Content
canDelete={canDelete}
canDownload={canDownload}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ContentExplorerComponent as ContentExplorer, ContentExplorerProps } fro
import { mockRecentItems, mockRootFolder, mockRootFolderSharedLink } from '../../common/__mocks__/mockRootFolder';
import { mockMetadata, mockSchema } from '../../common/__mocks__/mockMetadata';
import mockSubFolder from '../../common/__mocks__/mockSubfolder';
import { FeatureProvider } from '../../common/feature-checking';

jest.mock('../../../utils/Xhr', () => {
return jest.fn().mockImplementation(() => {
Expand Down Expand Up @@ -71,8 +72,12 @@ jest.mock('../../common/preview-dialog/PreviewDialog', () => props => {
describe('elements/content-explorer/ContentExplorer', () => {
let rootElement: HTMLDivElement;

const renderComponent = (props: Partial<ContentExplorerProps> = {}) => {
return render(<ContentExplorer defaultView="list" rootFolderId="69083462919" token="token" {...props} />);
const renderComponent = ({ features, ...props }: Partial<ContentExplorerProps> = {}) => {
return render(
<FeatureProvider features={features}>
<ContentExplorer defaultView="list" rootFolderId="69083462919" token="token" {...props} />
</FeatureProvider>,
);
};

beforeEach(() => {
Expand Down Expand Up @@ -410,6 +415,34 @@ describe('elements/content-explorer/ContentExplorer', () => {
expect(screen.getByText('Healthcare')).toBeInTheDocument();
expect(screen.getByText('November 1, 2023')).toBeInTheDocument();
});
describe('Metadata View V2', () => {
test('should render metadata view button', async () => {
renderComponent({
defaultView: 'metadata',
features: {
contentExplorer: {
metadataViewV2: true,
},
},
});

// two separate promises need to be resolved before the component is ready
await waitFor(() => {
expect(screen.getByText('Please wait while the items load...')).toBeInTheDocument();
});

await waitFor(() => {
expect(screen.getByTestId('content-explorer')).toBeInTheDocument();
});

expect(screen.queryByRole('searchbox', { name: 'Search files and folders' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Preview Test Folder' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Switch to Grid View' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Sort' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Add' })).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Metadata' })).toBeInTheDocument();
});
});
});

describe('Preview', () => {
Expand Down