metadata-based-explorer1
Version:
Box UI Elements
269 lines (237 loc) • 9.41 kB
Flow
/**
* @flow
* @file Content Sidebar Component
* @author Box
*/
import * as React from 'react';
import classNames from 'classnames';
import flow from 'lodash/flow';
import getProp from 'lodash/get';
import uniqueid from 'lodash/uniqueId';
import { withRouter } from 'react-router-dom';
import type { Location, RouterHistory } from 'react-router-dom';
import LoadingIndicator from '../../components/loading-indicator/LoadingIndicator';
import LocalStore from '../../utils/LocalStore';
import SidebarNav from './SidebarNav';
import SidebarPanels from './SidebarPanels';
import SidebarUtils from './SidebarUtils';
import { withFeatureConsumer } from '../common/feature-checking';
import type { FeatureConfig } from '../common/feature-checking';
import type { ActivitySidebarProps } from './ActivitySidebar';
import type { DetailsSidebarProps } from './DetailsSidebar';
import type { MetadataSidebarProps } from './MetadataSidebar';
import type { VersionsSidebarProps } from './versions';
type Props = {
activitySidebarProps: ActivitySidebarProps,
additionalTabs?: Array<AdditionalSidebarTab>,
className: string,
currentUser?: User,
detailsSidebarProps: DetailsSidebarProps,
features: FeatureConfig,
file: BoxItem,
fileId: string,
getPreview: Function,
getViewer: Function,
hasActivityFeed: boolean,
hasAdditionalTabs: boolean,
hasMetadata: boolean,
hasSkills: boolean,
hasVersions: boolean,
history: RouterHistory,
isDefaultOpen?: boolean,
isLoading?: boolean,
location: Location,
metadataEditors?: Array<MetadataEditor>,
metadataSidebarProps: MetadataSidebarProps,
onVersionChange?: Function,
onVersionHistoryClick?: Function,
versionsSidebarProps: VersionsSidebarProps,
};
type State = {
isDirty: boolean,
};
export const SIDEBAR_FORCE_KEY: 'bcs.force' = 'bcs.force';
export const SIDEBAR_FORCE_VALUE_CLOSED: 'closed' = 'closed';
export const SIDEBAR_FORCE_VALUE_OPEN: 'open' = 'open';
class Sidebar extends React.Component<Props, State> {
static defaultProps = {
isDefaultOpen: true,
isLoading: false,
};
id: string = uniqueid('bcs_');
props: Props;
sidebarPanels: { current: null | SidebarPanels } = React.createRef();
state: State;
store: LocalStore = new LocalStore();
constructor(props: Props) {
super(props);
this.state = {
isDirty: this.getLocationState('open') || false,
};
this.setForcedByLocation();
}
componentDidUpdate(prevProps: Props): void {
const { fileId, history, location }: Props = this.props;
const { fileId: prevFileId, location: prevLocation }: Props = prevProps;
const { isDirty }: State = this.state;
// User navigated to a different file without ever navigating the sidebar
if (!isDirty && fileId !== prevFileId && location.pathname !== '/') {
history.replace({ pathname: '/', state: { silent: true } });
}
// User navigated or toggled the sidebar intentionally, internally or externally
if (location !== prevLocation && !this.getLocationState('silent')) {
this.setForcedByLocation();
this.setState({ isDirty: true });
}
}
/**
* Handle version history click
*
* @param {SyntheticEvent} event - The event
* @return {void}
*/
handleVersionHistoryClick = (event: SyntheticEvent<>): void => {
const { file, history } = this.props;
const { file_version: currentVersion } = file;
const fileVersionSlug = currentVersion ? `/${currentVersion.id}` : '';
if (event.preventDefault) {
event.preventDefault();
}
history.push(`${history.location.pathname}/versions${fileVersionSlug}`);
};
/**
* Getter for location state properties.
*
* NOTE: Each location on the history stack has its own optional state object that is wholly separate from
* this component's internal state. Values on the location state object can persist even between refreshes
* when using certain history contexts, such as BrowserHistory.
*
* @param key - Optionally get a specific key value from state
* @returns {any} - The location state or state key value
*/
getLocationState(key?: string): any {
const { location } = this.props;
const { state: locationState = {} } = location;
return getProp(locationState, key);
}
/**
* Getter/setter for sidebar forced state
*
* @param isOpen - Optionally set the sidebar to open/closed
* @returns {string|null} - The sidebar open/closed state
*/
isForced(isOpen?: boolean): ?(typeof SIDEBAR_FORCE_VALUE_CLOSED | typeof SIDEBAR_FORCE_VALUE_OPEN) {
if (isOpen !== undefined) {
this.store.setItem(SIDEBAR_FORCE_KEY, isOpen ? SIDEBAR_FORCE_VALUE_OPEN : SIDEBAR_FORCE_VALUE_CLOSED);
}
return this.store.getItem(SIDEBAR_FORCE_KEY);
}
/**
* Getter for whether the sidebar has been forced open
* @returns {boolean} - True if the sidebar has been forced open
*/
isForcedOpen(): boolean {
return this.isForced() === SIDEBAR_FORCE_VALUE_OPEN;
}
/**
* Getter for whether the sidebar has been forced open/closed previously
* @returns {boolean} - True if the sidebar has been forced open/closed previously
*/
isForcedSet(): boolean {
return this.isForced() !== null;
}
/**
* Refreshes the sidebar panel
* @returns {void}
*/
refresh(): void {
const { current: sidebarPanels } = this.sidebarPanels;
if (sidebarPanels) {
sidebarPanels.refresh();
}
}
/**
* Helper to set the local store open state based on the location open state, if defined
*/
setForcedByLocation(): void {
const isLocationOpen: ?boolean = this.getLocationState('open');
if (isLocationOpen !== undefined && isLocationOpen !== null) {
this.isForced(isLocationOpen);
}
}
render() {
const {
activitySidebarProps,
additionalTabs,
className,
currentUser,
detailsSidebarProps,
file,
fileId,
getPreview,
getViewer,
hasAdditionalTabs,
hasVersions,
isDefaultOpen,
isLoading,
metadataEditors,
metadataSidebarProps,
onVersionChange,
versionsSidebarProps,
}: Props = this.props;
const isOpen = this.isForcedSet() ? this.isForcedOpen() : !!isDefaultOpen;
const hasActivity = SidebarUtils.canHaveActivitySidebar(this.props);
const hasDetails = SidebarUtils.canHaveDetailsSidebar(this.props);
const hasMetadata = SidebarUtils.shouldRenderMetadataSidebar(this.props, metadataEditors);
const hasSkills = SidebarUtils.shouldRenderSkillsSidebar(this.props, file);
const onVersionHistoryClick = hasVersions ? this.handleVersionHistoryClick : this.props.onVersionHistoryClick;
const styleClassName = classNames('be bcs', className, {
'bcs-is-open': isOpen,
});
return (
<aside id={this.id} className={styleClassName}>
{isLoading ? (
<div className="bcs-loading">
<LoadingIndicator />
</div>
) : (
<React.Fragment>
<SidebarNav
additionalTabs={additionalTabs}
fileId={fileId}
hasActivity={hasActivity}
hasAdditionalTabs={hasAdditionalTabs}
hasDetails={hasDetails}
hasMetadata={hasMetadata}
hasSkills={hasSkills}
isOpen={isOpen}
/>
<SidebarPanels
activitySidebarProps={activitySidebarProps}
currentUser={currentUser}
detailsSidebarProps={detailsSidebarProps}
file={file}
fileId={fileId}
getPreview={getPreview}
getViewer={getViewer}
hasActivity={hasActivity}
hasDetails={hasDetails}
hasMetadata={hasMetadata}
hasSkills={hasSkills}
hasVersions={hasVersions}
isOpen={isOpen}
key={file.id}
metadataSidebarProps={metadataSidebarProps}
onVersionChange={onVersionChange}
onVersionHistoryClick={onVersionHistoryClick}
ref={this.sidebarPanels}
versionsSidebarProps={versionsSidebarProps}
/>
</React.Fragment>
)}
</aside>
);
}
}
export { Sidebar as SidebarComponent };
export default flow([withFeatureConsumer, withRouter])(Sidebar);