box-ui-elements
Version:
Box UI Elements
483 lines (443 loc) • 20.1 kB
JavaScript
/**
* @flow
* @file Content Sidebar Panels component
* @author Box
*/
import * as React from 'react';
import flow from 'lodash/flow';
import noop from 'lodash/noop';
import { matchPath, Redirect, Route, Switch, type Location } from 'react-router-dom';
import SidebarUtils from './SidebarUtils';
import withSidebarAnnotations from './withSidebarAnnotations';
import { withAnnotatorContext } from '../common/annotator-context';
import { withAPIContext } from '../common/api-context';
import { getFeatureConfig, withFeatureConsumer, isFeatureEnabled } from '../common/feature-checking';
import { withRouterAndRef } from '../common/routing';
import {
ORIGIN_ACTIVITY_SIDEBAR,
ORIGIN_DETAILS_SIDEBAR,
ORIGIN_DOCGEN_SIDEBAR,
ORIGIN_METADATA_SIDEBAR,
ORIGIN_METADATA_SIDEBAR_REDESIGN,
ORIGIN_SKILLS_SIDEBAR,
ORIGIN_VERSIONS_SIDEBAR,
SIDEBAR_VIEW_ACTIVITY,
SIDEBAR_VIEW_DETAILS,
SIDEBAR_VIEW_METADATA,
SIDEBAR_VIEW_SKILLS,
SIDEBAR_VIEW_VERSIONS,
SIDEBAR_VIEW_DOCGEN,
SIDEBAR_VIEW_METADATA_REDESIGN,
SIDEBAR_VIEW_BOXAI,
ORIGIN_BOXAI_SIDEBAR,
} from '../../constants';
import type { DetailsSidebarProps } from './DetailsSidebar';
import type { DocGenSidebarProps } from './DocGenSidebar/DocGenSidebar';
import type { ActivitySidebarProps } from './ActivitySidebar';
import type { BoxAISidebarProps } from './BoxAISidebar';
import type { MetadataSidebarProps } from './MetadataSidebar';
import type { VersionsSidebarProps } from './versions';
import type { User, BoxItem } from '../../common/types/core';
import type { Errors } from '../common/flowTypes';
import type { FeatureConfig } from '../common/feature-checking';
import type { BoxAISidebarCache } from './types/BoxAISidebarTypes';
type Props = {
activitySidebarProps: ActivitySidebarProps,
boxAISidebarProps: BoxAISidebarProps,
currentUser?: User,
currentUserError?: Errors,
defaultPanel?: string,
detailsSidebarProps: DetailsSidebarProps,
docGenSidebarProps: DocGenSidebarProps,
elementId: string,
features: FeatureConfig,
file: BoxItem,
fileId: string,
getPreview: Function,
getViewer: Function,
hasActivity: boolean,
hasBoxAI: boolean,
hasDetails: boolean,
hasDocGen: boolean,
hasMetadata: boolean,
hasSkills: boolean,
hasVersions: boolean,
isOpen: boolean,
location: Location,
metadataSidebarProps: MetadataSidebarProps,
onAnnotationSelect?: Function,
onPanelChange?: (name: string, isInitialState?: boolean) => void,
onVersionChange?: Function,
onVersionHistoryClick?: Function,
versionsSidebarProps: VersionsSidebarProps,
};
type State = {
isInitialized: boolean,
};
type ElementRefType = {
current: null | Object,
};
// TODO: place into code splitting logic
const BASE_EVENT_NAME = '_JS_LOADING';
const MARK_NAME_JS_LOADING_DETAILS = `${ORIGIN_DETAILS_SIDEBAR}${BASE_EVENT_NAME}`;
const MARK_NAME_JS_LOADING_ACTIVITY = `${ORIGIN_ACTIVITY_SIDEBAR}${BASE_EVENT_NAME}`;
const MARK_NAME_JS_LOADING_BOXAI = `${ORIGIN_BOXAI_SIDEBAR}${BASE_EVENT_NAME}`;
const MARK_NAME_JS_LOADING_SKILLS = `${ORIGIN_SKILLS_SIDEBAR}${BASE_EVENT_NAME}`;
const MARK_NAME_JS_LOADING_METADATA = `${ORIGIN_METADATA_SIDEBAR}${BASE_EVENT_NAME}`;
const MARK_NAME_JS_LOADING_METADATA_REDESIGNED = `${ORIGIN_METADATA_SIDEBAR_REDESIGN}${BASE_EVENT_NAME}`;
const MARK_NAME_JS_LOADING_DOCGEN = `${ORIGIN_DOCGEN_SIDEBAR}${BASE_EVENT_NAME}`;
const MARK_NAME_JS_LOADING_VERSIONS = `${ORIGIN_VERSIONS_SIDEBAR}${BASE_EVENT_NAME}`;
const URL_TO_FEED_ITEM_TYPE = { annotations: 'annotation', comments: 'comment', tasks: 'task' };
const LoadableDetailsSidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_DETAILS, MARK_NAME_JS_LOADING_DETAILS);
const LoadableActivitySidebar = SidebarUtils.getAsyncSidebarContent(
SIDEBAR_VIEW_ACTIVITY,
MARK_NAME_JS_LOADING_ACTIVITY,
);
const LoadableBoxAISidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_BOXAI, MARK_NAME_JS_LOADING_BOXAI);
const LoadableSkillsSidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_SKILLS, MARK_NAME_JS_LOADING_SKILLS);
const LoadableMetadataSidebar = SidebarUtils.getAsyncSidebarContent(
SIDEBAR_VIEW_METADATA,
MARK_NAME_JS_LOADING_METADATA,
);
const LoadableMetadataSidebarRedesigned = SidebarUtils.getAsyncSidebarContent(
SIDEBAR_VIEW_METADATA_REDESIGN,
MARK_NAME_JS_LOADING_METADATA,
);
const LoadableDocGenSidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_DOCGEN, MARK_NAME_JS_LOADING_DOCGEN);
const LoadableVersionsSidebar = SidebarUtils.getAsyncSidebarContent(
SIDEBAR_VIEW_VERSIONS,
MARK_NAME_JS_LOADING_VERSIONS,
);
const SIDEBAR_PATH_VERSIONS = '/:sidebar(activity|details)/versions/:versionId?';
class SidebarPanels extends React.Component<Props, State> {
boxAISidebar: ElementRefType = React.createRef();
activitySidebar: ElementRefType = React.createRef();
detailsSidebar: ElementRefType = React.createRef();
initialPanel: { current: null | string } = React.createRef();
metadataSidebar: ElementRefType = React.createRef();
state: State = { isInitialized: false };
versionsSidebar: ElementRefType = React.createRef();
boxAiSidebarCache: BoxAISidebarCache = {
agents: {
agents: [],
selectedAgent: null,
requestState: 'not_started',
},
encodedSession: null,
questions: [],
shouldShowLandingPage: true,
suggestedQuestions: [],
};
componentDidMount() {
this.setState({ isInitialized: true });
}
componentDidUpdate(prevProps: Props): void {
const { location, onVersionChange } = this.props;
const { location: prevLocation } = prevProps;
// Reset the current version id if the wrapping versions route is no longer active
if (onVersionChange && this.getVersionsMatchPath(prevLocation) && !this.getVersionsMatchPath(location)) {
onVersionChange(null);
}
}
getVersionsMatchPath = (location: Location) => {
const { pathname } = location;
return matchPath(pathname, SIDEBAR_PATH_VERSIONS);
};
handlePanelRender = (panel: string): void => {
const { onPanelChange = noop } = this.props;
// Call onPanelChange only once with the initial panel
if (!this.initialPanel.current) {
this.initialPanel.current = panel;
onPanelChange(panel, true);
}
};
setBoxAiSidebarCacheValue = (key: 'agents' | 'encodedSession' | 'questions' | 'shouldShowLandingPage' | 'suggestedQuestions', value: any) => {
this.boxAiSidebarCache[key] = value;
};
/**
* Refreshes the contents of the active sidebar
* @returns {void}
*/
refresh(shouldRefreshCache: boolean = true): void {
const { current: boxAISidebar } = this.boxAISidebar;
const { current: activitySidebar } = this.activitySidebar;
const { current: detailsSidebar } = this.detailsSidebar;
const { current: metadataSidebar } = this.metadataSidebar;
const { current: versionsSidebar } = this.versionsSidebar;
if (boxAISidebar) {
boxAISidebar.refresh();
}
if (activitySidebar) {
activitySidebar.refresh(shouldRefreshCache);
}
if (detailsSidebar) {
detailsSidebar.refresh();
}
if (metadataSidebar) {
metadataSidebar.refresh();
}
if (versionsSidebar) {
versionsSidebar.refresh();
}
}
render() {
const {
activitySidebarProps,
boxAISidebarProps,
currentUser,
currentUserError,
defaultPanel = '',
detailsSidebarProps,
docGenSidebarProps,
elementId,
features,
file,
fileId,
getPreview,
getViewer,
hasActivity,
hasBoxAI,
hasDetails,
hasDocGen,
hasMetadata,
hasSkills,
hasVersions,
isOpen,
metadataSidebarProps,
onAnnotationSelect,
onVersionChange,
onVersionHistoryClick,
versionsSidebarProps,
}: Props = this.props;
const { isInitialized } = this.state;
const isMetadataSidebarRedesignEnabled = isFeatureEnabled(features, 'metadata.redesign.enabled');
const isMetadataAiSuggestionsEnabled = isFeatureEnabled(features, 'metadata.aiSuggestions.enabled');
const { shouldBeDefaultPanel: shouldBoxAIBeDefaultPanel, showOnlyNavButton: showOnlyBoxAINavButton } =
getFeatureConfig(features, 'boxai.sidebar');
const canShowBoxAISidebarPanel = hasBoxAI && !showOnlyBoxAINavButton;
const panelsEligibility = {
[SIDEBAR_VIEW_BOXAI]: canShowBoxAISidebarPanel,
[SIDEBAR_VIEW_DOCGEN]: hasDocGen,
[SIDEBAR_VIEW_SKILLS]: hasSkills,
[SIDEBAR_VIEW_ACTIVITY]: hasActivity,
[SIDEBAR_VIEW_DETAILS]: hasDetails,
[SIDEBAR_VIEW_METADATA]: hasMetadata,
};
const showDefaultPanel: boolean = !!(defaultPanel && panelsEligibility[defaultPanel]);
if (!isOpen || (!hasBoxAI && !hasActivity && !hasDetails && !hasMetadata && !hasSkills && !hasVersions)) {
return null;
}
return (
<Switch>
{canShowBoxAISidebarPanel && (
<Route
exact
path={`/${SIDEBAR_VIEW_BOXAI}`}
render={() => {
this.handlePanelRender(SIDEBAR_VIEW_BOXAI);
return (
<LoadableBoxAISidebar
contentName={file.name}
elementId={elementId}
fileExtension={file.extension}
fileID={file.id}
hasSidebarInitialized={isInitialized}
ref={this.boxAISidebar}
startMarkName={MARK_NAME_JS_LOADING_BOXAI}
cache={this.boxAiSidebarCache}
setCacheValue={this.setBoxAiSidebarCacheValue}
{...boxAISidebarProps}
/>
);
}}
/>
)}
{hasSkills && (
<Route
exact
path={`/${SIDEBAR_VIEW_SKILLS}`}
render={() => {
this.handlePanelRender(SIDEBAR_VIEW_SKILLS);
return (
<LoadableSkillsSidebar
elementId={elementId}
key={file.id}
file={file}
getPreview={getPreview}
getViewer={getViewer}
hasSidebarInitialized={isInitialized}
startMarkName={MARK_NAME_JS_LOADING_SKILLS}
/>
);
}}
/>
)}
{/* This handles both the default activity sidebar and the activity sidebar with a
comment or task deeplink. */}
{hasActivity && (
<Route
exact
path={[
`/${SIDEBAR_VIEW_ACTIVITY}`,
`/${SIDEBAR_VIEW_ACTIVITY}/:activeFeedEntryType(annotations)/:fileVersionId/:activeFeedEntryId?`,
`/${SIDEBAR_VIEW_ACTIVITY}/:activeFeedEntryType(comments|tasks)/:activeFeedEntryId?`,
]}
render={({ match }) => {
const matchEntryType = match.params.activeFeedEntryType;
const activeFeedEntryType = matchEntryType
? URL_TO_FEED_ITEM_TYPE[matchEntryType]
: undefined;
this.handlePanelRender(SIDEBAR_VIEW_ACTIVITY);
return (
<LoadableActivitySidebar
elementId={elementId}
currentUser={currentUser}
currentUserError={currentUserError}
file={file}
hasSidebarInitialized={isInitialized}
onAnnotationSelect={onAnnotationSelect}
onVersionChange={onVersionChange}
onVersionHistoryClick={onVersionHistoryClick}
ref={this.activitySidebar}
startMarkName={MARK_NAME_JS_LOADING_ACTIVITY}
activeFeedEntryId={match.params.activeFeedEntryId}
activeFeedEntryType={match.params.activeFeedEntryId && activeFeedEntryType}
{...activitySidebarProps}
/>
);
}}
/>
)}
{hasDetails && (
<Route
exact
path={`/${SIDEBAR_VIEW_DETAILS}`}
render={() => {
this.handlePanelRender(SIDEBAR_VIEW_DETAILS);
return (
<LoadableDetailsSidebar
elementId={elementId}
fileId={fileId}
hasSidebarInitialized={isInitialized}
key={fileId}
hasVersions={hasVersions}
onVersionHistoryClick={onVersionHistoryClick}
ref={this.detailsSidebar}
startMarkName={MARK_NAME_JS_LOADING_DETAILS}
{...detailsSidebarProps}
/>
);
}}
/>
)}
{hasMetadata && (
<Route
exact
path={[
`/${SIDEBAR_VIEW_METADATA}`,
`/${SIDEBAR_VIEW_METADATA}/filteredTemplates/:filteredTemplateIds?`,
]}
render={({ match }) => {
this.handlePanelRender(SIDEBAR_VIEW_METADATA);
return isMetadataSidebarRedesignEnabled ? (
<LoadableMetadataSidebarRedesigned
elementId={elementId}
fileExtension={file.extension}
fileId={fileId}
filteredTemplateIds={
match.params.filteredTemplateIds
? match.params.filteredTemplateIds.split(',')
: []
}
hasSidebarInitialized={isInitialized}
isBoxAiSuggestionsEnabled={isMetadataAiSuggestionsEnabled}
ref={this.metadataSidebar}
startMarkName={MARK_NAME_JS_LOADING_METADATA_REDESIGNED}
{...metadataSidebarProps}
/>
) : (
<LoadableMetadataSidebar
elementId={elementId}
fileId={fileId}
hasSidebarInitialized={isInitialized}
ref={this.metadataSidebar}
startMarkName={MARK_NAME_JS_LOADING_METADATA}
{...metadataSidebarProps}
/>
);
}}
/>
)}
{hasDocGen && (
<Route
exact
path={`/${SIDEBAR_VIEW_DOCGEN}`}
render={() => {
this.handlePanelRender(SIDEBAR_VIEW_DOCGEN);
return (
<LoadableDocGenSidebar
hasSidebarInitialized={isInitialized}
startMarkName={MARK_NAME_JS_LOADING_DOCGEN}
{...docGenSidebarProps}
/>
);
}}
/>
)}
{hasVersions && (
<Route
path={SIDEBAR_PATH_VERSIONS}
render={({ match }) => {
if (match.params.sidebar) {
this.handlePanelRender(match.params.sidebar);
}
return (
<LoadableVersionsSidebar
fileId={fileId}
hasSidebarInitialized={isInitialized}
key={fileId}
onVersionChange={onVersionChange}
parentName={match.params.sidebar}
ref={this.versionsSidebar}
versionId={match.params.versionId}
{...versionsSidebarProps}
/>
);
}}
/>
)}
<Route
render={() => {
let redirect = '';
if (showDefaultPanel) {
redirect = defaultPanel;
} else if (canShowBoxAISidebarPanel && shouldBoxAIBeDefaultPanel) {
redirect = SIDEBAR_VIEW_BOXAI;
} else if (hasDocGen) {
redirect = SIDEBAR_VIEW_DOCGEN;
} else if (hasSkills) {
redirect = SIDEBAR_VIEW_SKILLS;
} else if (hasActivity) {
redirect = SIDEBAR_VIEW_ACTIVITY;
} else if (hasDetails) {
redirect = SIDEBAR_VIEW_DETAILS;
} else if (hasMetadata) {
redirect = SIDEBAR_VIEW_METADATA;
} else if (canShowBoxAISidebarPanel && !shouldBoxAIBeDefaultPanel) {
redirect = SIDEBAR_VIEW_BOXAI;
}
return <Redirect to={{ pathname: `/${redirect}`, state: { silent: true } }} />;
}}
/>
</Switch>
);
}
}
export { SidebarPanels as SidebarPanelsComponent };
export default flow([
withFeatureConsumer,
withSidebarAnnotations,
withAPIContext,
withAnnotatorContext,
withRouterAndRef,
])(SidebarPanels);