signal-desktop/ts/components/conversation/media-gallery/MediaGallery.tsx

166 lines
4.1 KiB
TypeScript
Raw Normal View History

2018-04-11 15:57:31 +00:00
import React from 'react';
2018-07-18 00:15:34 +00:00
import classNames from 'classnames';
2018-04-11 15:57:31 +00:00
2018-04-13 00:56:05 +00:00
import moment from 'moment';
2018-04-15 01:11:40 +00:00
import { AttachmentSection } from './AttachmentSection';
2018-04-26 23:06:48 +00:00
import { EmptyState } from './EmptyState';
import { groupMediaItemsByDate } from './groupMediaItemsByDate';
import { ItemClickEvent } from './types/ItemClickEvent';
2018-04-26 23:06:48 +00:00
import { missingCaseError } from '../../../util/missingCaseError';
2019-01-14 21:49:58 +00:00
import { LocalizerType } from '../../../types/Util';
2018-04-12 20:23:26 +00:00
import { MediaItemType } from '../../LightboxGallery';
2020-08-21 15:13:55 +00:00
export interface Props {
documents: Array<MediaItemType>;
2019-01-14 21:49:58 +00:00
i18n: LocalizerType;
media: Array<MediaItemType>;
2019-11-07 21:36:16 +00:00
2018-04-15 06:16:39 +00:00
onItemClick?: (event: ItemClickEvent) => void;
2018-04-12 20:23:26 +00:00
}
interface State {
selectedTab: 'media' | 'documents';
2018-04-11 15:57:31 +00:00
}
2018-04-13 00:56:05 +00:00
const MONTH_FORMAT = 'MMMM YYYY';
2018-04-12 20:23:26 +00:00
interface TabSelectEvent {
type: 'media' | 'documents';
2018-04-12 20:23:26 +00:00
}
const Tab = ({
isSelected,
label,
onSelect,
type,
}: {
2018-04-13 20:25:52 +00:00
isSelected: boolean;
label: string;
onSelect?: (event: TabSelectEvent) => void;
type: 'media' | 'documents';
2018-04-12 20:23:26 +00:00
}) => {
const handleClick = onSelect
? () => {
onSelect({ type });
}
: undefined;
2018-04-12 20:23:26 +00:00
return (
2020-09-14 19:51:27 +00:00
// Has key events handled elsewhere
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
2018-04-12 20:23:26 +00:00
<div
2018-07-18 00:15:34 +00:00
className={classNames(
'module-media-gallery__tab',
isSelected ? 'module-media-gallery__tab--active' : null
)}
2018-04-12 20:23:26 +00:00
onClick={handleClick}
role="tab"
tabIndex={0}
2018-04-12 20:23:26 +00:00
>
{label}
</div>
);
};
export class MediaGallery extends React.Component<Props, State> {
2019-11-07 21:36:16 +00:00
public readonly focusRef: React.RefObject<HTMLDivElement> = React.createRef();
2018-04-12 20:23:26 +00:00
2020-09-14 19:51:27 +00:00
constructor(props: Props) {
super(props);
this.state = {
selectedTab: 'media',
};
}
public componentDidMount(): void {
2019-11-07 21:36:16 +00:00
// When this component is created, it's initially not part of the DOM, and then it's
// added off-screen and animated in. This ensures that the focus takes.
setTimeout(() => {
if (this.focusRef.current) {
this.focusRef.current.focus();
}
});
}
2020-09-14 19:51:27 +00:00
public render(): JSX.Element {
2018-04-12 20:23:26 +00:00
const { selectedTab } = this.state;
2018-04-11 15:57:31 +00:00
return (
<div className="module-media-gallery" tabIndex={-1} ref={this.focusRef}>
2018-07-18 00:15:34 +00:00
<div className="module-media-gallery__tab-container">
2018-04-12 20:23:26 +00:00
<Tab
label="Media"
type="media"
isSelected={selectedTab === 'media'}
onSelect={this.handleTabSelect}
/>
<Tab
label="Documents"
type="documents"
isSelected={selectedTab === 'documents'}
onSelect={this.handleTabSelect}
/>
</div>
2018-07-18 00:15:34 +00:00
<div className="module-media-gallery__content">
{this.renderSections()}
</div>
2018-04-12 20:23:26 +00:00
</div>
2018-04-11 15:57:31 +00:00
);
}
2018-04-12 20:23:26 +00:00
2019-01-14 21:49:58 +00:00
private readonly handleTabSelect = (event: TabSelectEvent): void => {
2018-04-13 20:25:52 +00:00
this.setState({ selectedTab: event.type });
};
2018-04-13 00:56:05 +00:00
private renderSections() {
2018-04-15 06:16:39 +00:00
const { i18n, media, documents, onItemClick } = this.props;
2018-04-13 00:56:05 +00:00
const { selectedTab } = this.state;
const mediaItems = selectedTab === 'media' ? media : documents;
2018-04-13 00:56:05 +00:00
const type = selectedTab;
if (!mediaItems || mediaItems.length === 0) {
2018-04-26 23:06:48 +00:00
const label = (() => {
switch (type) {
case 'media':
return i18n('mediaEmptyState');
case 'documents':
return i18n('documentsEmptyState');
default:
throw missingCaseError(type);
}
})();
2018-04-26 23:06:48 +00:00
return <EmptyState data-test="EmptyState" label={label} />;
2018-04-13 00:56:05 +00:00
}
const now = Date.now();
const sections = groupMediaItemsByDate(now, mediaItems).map(section => {
const first = section.mediaItems[0];
const { message } = first;
const date = moment(message.received_at);
2018-04-13 20:27:06 +00:00
const header =
section.type === 'yearMonth'
? date.format(MONTH_FORMAT)
: i18n(section.type);
2018-04-13 00:56:05 +00:00
return (
2018-04-15 01:11:40 +00:00
<AttachmentSection
2018-04-13 00:56:05 +00:00
key={header}
header={header}
i18n={i18n}
type={type}
mediaItems={section.mediaItems}
2018-04-15 06:16:39 +00:00
onItemClick={onItemClick}
2018-04-13 00:56:05 +00:00
/>
);
});
2018-04-26 23:06:13 +00:00
2018-07-18 00:15:34 +00:00
return <div className="module-media-gallery__sections">{sections}</div>;
2018-04-12 20:23:26 +00:00
}
2018-04-11 15:57:31 +00:00
}