Skip to content

Commit

Permalink
refactor(content-preview): convert preview-header to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
devin-ai-integration[bot] committed Feb 25, 2025
1 parent 8cf5e9f commit 697accc
Show file tree
Hide file tree
Showing 4 changed files with 375 additions and 0 deletions.
27 changes: 27 additions & 0 deletions src/elements/content-preview/preview-header/FileInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import FileIcon from '../../../icons/file-icon/FileIcon';
import type { BoxItem, BoxItemVersion } from '../../../common/types/core';
import './FileInfo.scss';

export interface FileInfoProps {
file: BoxItem | null;
version: BoxItemVersion | null;
}

const FileInfo = ({ file, version }: FileInfoProps) => {
// Opt to show version over the file object since it is more specific
const displayItem = version || file;

return (
<div className="bcpr-FileInfo">
{displayItem && (
<>
<FileIcon dimension={24} extension={displayItem.extension} />
<span className="bcpr-FileInfo-name">{displayItem.name}</span>
</>
)}
</div>
);
};

export default FileInfo;
178 changes: 178 additions & 0 deletions src/elements/content-preview/preview-header/PreviewHeader.js.flow
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
* @flow
* @file Preview header component
* @author Box
*/

import * as React from 'react';
import { injectIntl } from 'react-intl';
import type { IntlShape } from 'react-intl';
import classNames from 'classnames';
import getProp from 'lodash/get';
import AsyncLoad from '../../common/async-load';
import FileInfo from './FileInfo';
import IconClose from '../../../icons/general/IconClose';
import IconDownload from '../../../icons/general/IconDownloadSolid';
import IconDrawAnnotationMode from '../../../icons/annotations/IconDrawAnnotation';
import IconPointAnnotation from '../../../icons/annotations/IconPointAnnotation';
import IconPrint from '../../../icons/general/IconPrint';
import Logo from '../../common/header/Logo';
import messages from '../../common/messages';
import PlainButton from '../../../components/plain-button/PlainButton';
import { bdlGray50 } from '../../../styles/variables';
import type { BoxItem, BoxItemVersion } from '../../../common/types/core';

import './PreviewHeader.scss';

type Props = {
canAnnotate: boolean,
canDownload: boolean,
canPrint?: boolean,
contentAnswersProps?: ContentAnswersProps,
contentOpenWithProps?: ContentOpenWithProps,
file?: BoxItem,
intl: IntlShape,
logoUrl?: string,
onClose?: Function,
onDownload: Function,
onPrint: Function,
selectedVersion: ?BoxItemVersion,
token: ?string,
};

const LoadableContentAnswers = AsyncLoad({
// $FlowFixMe TypeScript component
loader: () => import(/* webpackMode: "lazy", webpackChunkName: "content-answers" */ '../../common/content-answers'),
});
const LoadableContentOpenWith = AsyncLoad({
loader: () => import(/* webpackMode: "lazy", webpackChunkName: "content-open-with" */ '../../content-open-with'),
});

const PreviewHeader = ({
canAnnotate,
canDownload,
canPrint,
contentAnswersProps = {},
contentOpenWithProps = {},
file,
intl,
logoUrl,
onClose,
onDownload,
onPrint,
selectedVersion,
token,
}: Props) => {
const fileId = file && file.id;
const shouldRenderAnswers = fileId && contentAnswersProps.show;
const shouldRenderOpenWith = fileId && contentOpenWithProps.show;
const currentVersionId = getProp(file, 'file_version.id');
const selectedVersionId = getProp(selectedVersion, 'id', currentVersionId);
const isPreviewingCurrentVersion = currentVersionId === selectedVersionId;

// When previewing an older version the close button returns the user to the current version
const closeMsg = isPreviewingCurrentVersion
? intl.formatMessage(messages.close)
: intl.formatMessage(messages.back);
const printMsg = intl.formatMessage(messages.print);
const downloadMsg = intl.formatMessage(messages.download);
const drawMsg = intl.formatMessage(messages.drawAnnotation);
const pointMsg = intl.formatMessage(messages.pointAnnotation);

return (
<header
className={classNames('bcpr-PreviewHeader', {
'bcpr-PreviewHeader--basic': !isPreviewingCurrentVersion,
})}
>
{/*
bp-header and bp-base-header are used by box-annotations,
and must be put one level under bcpr-PreviewHeader
*/}
<div className="bcpr-PreviewHeader-content bp-header bp-base-header">
{logoUrl ? <Logo url={logoUrl} /> : <FileInfo file={file} version={selectedVersion} />}

<div className="bcpr-PreviewHeader-controls">
{isPreviewingCurrentVersion && (
<>
{shouldRenderOpenWith && (
<LoadableContentOpenWith
className="bcpr-bcow-btn"
fileId={fileId}
token={token}
{...contentOpenWithProps}
/>
)}
{shouldRenderAnswers && (
<LoadableContentAnswers
className="bcpr-PreviewHeader-contentAnswers"
file={file}
{...contentAnswersProps}
/>
)}
{canAnnotate && (
<>
<PlainButton
aria-label={drawMsg}
className="bcpr-PreviewHeader-button bp-btn-annotate-draw bp-is-hidden"
title={drawMsg}
type="button"
>
<IconDrawAnnotationMode color={bdlGray50} height={18} width={18} />
</PlainButton>
<PlainButton
aria-label={pointMsg}
className="bcpr-PreviewHeader-button bp-btn-annotate-point bp-is-hidden"
title={pointMsg}
type="button"
>
<IconPointAnnotation color={bdlGray50} height={18} width={18} />
</PlainButton>
</>
)}
{canPrint && (
<PlainButton
aria-label={printMsg}
className="bcpr-PreviewHeader-button"
onClick={onPrint}
title={printMsg}
type="button"
>
<IconPrint color={bdlGray50} height={22} width={22} />
</PlainButton>
)}
{canDownload && (
<PlainButton
aria-label={downloadMsg}
className="bcpr-PreviewHeader-button"
onClick={onDownload}
title={downloadMsg}
type="button"
>
<IconDownload color={bdlGray50} height={18} width={18} />
</PlainButton>
)}
</>
)}

{onClose && (
<PlainButton
aria-label={isPreviewingCurrentVersion && closeMsg}
className="bcpr-PreviewHeader-button bcpr-PreviewHeader-button-close"
onClick={onClose}
type="button"
>
{isPreviewingCurrentVersion ? (
<IconClose color={bdlGray50} height={24} width={24} />
) : (
closeMsg
)}
</PlainButton>
)}
</div>
</div>
</header>
);
};

export default injectIntl(PreviewHeader);
169 changes: 169 additions & 0 deletions src/elements/content-preview/preview-header/PreviewHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import React from 'react';
import { injectIntl } from 'react-intl';
import type { IntlShape } from 'react-intl';
import classNames from 'classnames';
import get from 'lodash/get';
import AsyncLoad from '../../common/async-load';
import FileInfo from './FileInfo';
import IconClose from '../../../icons/general/IconClose';
import IconDownload from '../../../icons/general/IconDownloadSolid';
import IconDrawAnnotationMode from '../../../icons/annotations/IconDrawAnnotation';
import IconPointAnnotation from '../../../icons/annotations/IconPointAnnotation';
import IconPrint from '../../../icons/general/IconPrint';
import Logo from '../../common/header/Logo';
import messages from '../../common/messages';
import PlainButton from '../../../components/plain-button/PlainButton';
import { bdlGray50 } from '../../../styles/variables';
import type { BoxItem, BoxItemVersion } from '../../../common/types/core';
import type { ContentAnswersProps } from '../../common/content-answers/ContentAnswers';
import type { ContentOpenWithProps } from '../../content-open-with/ContentOpenWith';

import './PreviewHeader.scss';

export interface PreviewHeaderProps {
canAnnotate: boolean;
canDownload: boolean;
canPrint?: boolean;
contentAnswersProps?: Partial<ContentAnswersProps>;
contentOpenWithProps?: Partial<ContentOpenWithProps>;
file?: BoxItem;
intl: IntlShape;
logoUrl?: string;
onClose?: React.MouseEventHandler<HTMLButtonElement>;
onDownload: React.MouseEventHandler<HTMLButtonElement>;
onPrint: React.MouseEventHandler<HTMLButtonElement>;
selectedVersion: BoxItemVersion | null;
token: string | null;
}

// Using proper types for AsyncLoad components
const LoadableContentAnswers = AsyncLoad({
loader: () => import('../../common/content-answers').then(module => module.default),
}) as React.ComponentType<ContentAnswersProps>;
const LoadableContentOpenWith = AsyncLoad({
loader: () => import('../../content-open-with').then(module => module.default),
}) as React.ComponentType<ContentOpenWithProps>;

const PreviewHeader = ({
canAnnotate,
canDownload,
canPrint,
contentAnswersProps = {} as Partial<ContentAnswersProps>,
contentOpenWithProps = {} as Partial<ContentOpenWithProps>,
file,
intl,
logoUrl,
onClose,
onDownload,
onPrint,
selectedVersion,
token,
}: PreviewHeaderProps) => {
const fileId = file && file.id;
const shouldRenderAnswers = fileId && contentAnswersProps.show;
const shouldRenderOpenWith = fileId && contentOpenWithProps.show;
const currentVersionId = get(file, 'file_version.id');
const selectedVersionId = get(selectedVersion, 'id', currentVersionId);
const isPreviewingCurrentVersion = currentVersionId === selectedVersionId;

// When previewing an older version the close button returns the user to the current version
const closeMsg = isPreviewingCurrentVersion
? intl.formatMessage(messages.close)
: intl.formatMessage(messages.back);
const printMsg = intl.formatMessage(messages.print);
const downloadMsg = intl.formatMessage(messages.download);
const drawMsg = intl.formatMessage(messages.drawAnnotation);
const pointMsg = intl.formatMessage(messages.pointAnnotation);

return (
<header
className={classNames('bcpr-PreviewHeader', {
'bcpr-PreviewHeader--basic': !isPreviewingCurrentVersion,
})}
>
{/*
bp-header and bp-base-header are used by box-annotations,
and must be put one level under bcpr-PreviewHeader
*/}
<div className="bcpr-PreviewHeader-content bp-header bp-base-header">
{logoUrl ? <Logo url={logoUrl} /> : <FileInfo file={file || null} version={selectedVersion} />}

<div className="bcpr-PreviewHeader-controls">
{isPreviewingCurrentVersion && (
<>
{shouldRenderOpenWith && (
<LoadableContentOpenWith
className="bcpr-bcow-btn"
fileId={fileId}
token={token}
{...contentOpenWithProps}
/>
)}
{shouldRenderAnswers && (
<LoadableContentAnswers
className="bcpr-PreviewHeader-contentAnswers"
file={file}
{...contentAnswersProps}
/>
)}
{canAnnotate && (
<>
<PlainButton
aria-label={drawMsg}
className="bcpr-PreviewHeader-button bp-btn-annotate-draw bp-is-hidden"
title={drawMsg}
>
<IconDrawAnnotationMode color={bdlGray50} height={18} width={18} />
</PlainButton>
<PlainButton
aria-label={pointMsg}
className="bcpr-PreviewHeader-button bp-btn-annotate-point bp-is-hidden"
title={pointMsg}
>
<IconPointAnnotation color={bdlGray50} height={18} width={18} />
</PlainButton>
</>
)}
{canPrint && (
<PlainButton
aria-label={printMsg}
className="bcpr-PreviewHeader-button"
onClick={onPrint}
title={printMsg}
>
<IconPrint color={bdlGray50} height={22} width={22} />
</PlainButton>
)}
{canDownload && (
<PlainButton
aria-label={downloadMsg}
className="bcpr-PreviewHeader-button"
onClick={onDownload}
title={downloadMsg}
>
<IconDownload color={bdlGray50} height={18} width={18} />
</PlainButton>
)}
</>
)}

{onClose && (
<PlainButton
aria-label={isPreviewingCurrentVersion && closeMsg}
className="bcpr-PreviewHeader-button bcpr-PreviewHeader-button-close"
onClick={onClose}
>
{isPreviewingCurrentVersion ? (
<IconClose color={bdlGray50} height={24} width={24} />
) : (
closeMsg
)}
</PlainButton>
)}
</div>
</div>
</header>
);
};

export default injectIntl(PreviewHeader);
1 change: 1 addition & 0 deletions src/elements/content-preview/preview-header/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './PreviewHeader';

0 comments on commit 697accc

Please sign in to comment.