From fc1c3aabf5cdb161794fe2a9590c4d5a9c98f65a Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Thu, 12 Apr 2018 16:23:26 -0400 Subject: [PATCH] Add scaffolding for media gallery --- styleguide.config.js | 5 + .../media-gallery/AttachmentListSection.tsx | 56 ++++++++++ .../media-gallery/DocumentListEntry.md | 19 ++++ .../media-gallery/DocumentListEntry.tsx | 97 ++++++++++++++++ .../media-gallery/ImageThumbnail.tsx | 44 ++++++++ .../media-gallery/LoadingIndicator.tsx | 13 +++ .../media-gallery/MediaGallery.md | 33 ++++++ .../media-gallery/MediaGallery.tsx | 105 +++++++++++++++++- .../media-gallery/propTypes/Message.tsx | 12 ++ ts/types/Attachment.ts | 2 +- 10 files changed, 382 insertions(+), 4 deletions(-) create mode 100644 ts/components/conversation/media-gallery/AttachmentListSection.tsx create mode 100644 ts/components/conversation/media-gallery/DocumentListEntry.md create mode 100644 ts/components/conversation/media-gallery/DocumentListEntry.tsx create mode 100644 ts/components/conversation/media-gallery/ImageThumbnail.tsx create mode 100644 ts/components/conversation/media-gallery/LoadingIndicator.tsx create mode 100644 ts/components/conversation/media-gallery/MediaGallery.md create mode 100644 ts/components/conversation/media-gallery/propTypes/Message.tsx diff --git a/styleguide.config.js b/styleguide.config.js index ea2955031..af5f8fdf1 100644 --- a/styleguide.config.js +++ b/styleguide.config.js @@ -12,6 +12,11 @@ module.exports = { description: 'Everything necessary to render a conversation', components: 'ts/components/conversation/*.tsx', }, + { + name: 'Media Gallery', + description: 'Display media and documents in a conversation', + components: 'ts/components/conversation/media-gallery/*.tsx', + }, { name: 'Utility', description: 'Utility components used across the application', diff --git a/ts/components/conversation/media-gallery/AttachmentListSection.tsx b/ts/components/conversation/media-gallery/AttachmentListSection.tsx new file mode 100644 index 000000000..785198eed --- /dev/null +++ b/ts/components/conversation/media-gallery/AttachmentListSection.tsx @@ -0,0 +1,56 @@ +import React from 'react'; + +import { ImageThumbnail } from './ImageThumbnail'; +import { DocumentListEntry } from './DocumentListEntry'; +import { Message } from './propTypes/Message'; + +const styles = { + container: { + width: '100%', + }, + header: { + fontFamily: '', + }, + itemContainer: { + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'flex-start', + alignItems: 'flex-start', + } as React.CSSProperties, +}; + +interface Props { + i18n: (value: string) => string; + header?: string; + type: 'media' | 'documents'; + messages: Array; +} + +export class AttachmentListSection extends React.Component { + public renderItems() { + const { i18n, messages, type } = this.props; + const Component = type === 'media' ? ImageThumbnail : DocumentListEntry; + + return messages.map((message) => ( + + )); + } + + public render() { + const { header } = this.props; + + return ( +
+
{header}
+
+ {this.renderItems()} +
+
+ ); + } +} diff --git a/ts/components/conversation/media-gallery/DocumentListEntry.md b/ts/components/conversation/media-gallery/DocumentListEntry.md new file mode 100644 index 000000000..776eaa841 --- /dev/null +++ b/ts/components/conversation/media-gallery/DocumentListEntry.md @@ -0,0 +1,19 @@ +DocumentListEntry example: + +```js + + + +``` diff --git a/ts/components/conversation/media-gallery/DocumentListEntry.tsx b/ts/components/conversation/media-gallery/DocumentListEntry.tsx new file mode 100644 index 000000000..5f84b2fdb --- /dev/null +++ b/ts/components/conversation/media-gallery/DocumentListEntry.tsx @@ -0,0 +1,97 @@ +import React from 'react'; + +import moment from 'moment'; +import formatFileSize from 'filesize'; + +// import { LoadingIndicator } from './LoadingIndicator'; + + +interface Props { + fileName: string | null; + fileSize?: number; + i18n: (key: string, values?: Array) => string; + timestamp: number; +} + +const styles = { + container: { + width: '100%', + height: 72, + borderBottomWidth: 1, + borderBottomColor: '#ccc', + borderBottomStyle: 'solid', + }, + itemContainer: { + display: 'flex', + flexDirection: 'row', + flexWrap: 'nowrap', + alignItems: 'center', + height: '100%', + } as React.CSSProperties, + itemMetadata: { + display: 'inline-flex', + flexDirection: 'column', + flexGrow: 1, + flexShrink: 0, + marginLeft: 8, + marginRight: 8, + } as React.CSSProperties, + itemDate: { + display: 'inline-block', + flexShrink: 0, + }, + itemIcon: { + flexShrink: 0, + }, + itemFileSize: { + display: 'inline-block', + marginTop: 8, + fontSize: '80%', + }, +}; + +export class DocumentListEntry extends React.Component { + public renderContent() { + const { fileName, fileSize, timestamp } = this.props; + + // if (!attachment.data) { + // return ; + // } + + return ( +
+ +
+ {fileName} + + {typeof fileSize === 'number' ? formatFileSize(fileSize) : ''} + +
+
+ {moment(timestamp).format('dddd, MMMM D, Y')} +
+
+ ); + } + + public render() { + return ( +
+ {this.renderContent()} +
+ ); + } +} diff --git a/ts/components/conversation/media-gallery/ImageThumbnail.tsx b/ts/components/conversation/media-gallery/ImageThumbnail.tsx new file mode 100644 index 000000000..5635b3011 --- /dev/null +++ b/ts/components/conversation/media-gallery/ImageThumbnail.tsx @@ -0,0 +1,44 @@ +import React from 'react'; + +import { LoadingIndicator } from './LoadingIndicator'; +import { Message } from './propTypes/Message'; + +interface Props { + message: Message; + i18n: (value: string) => string; +} + +const styles = { + container: { + backgroundColor: '#f3f3f3', + marginRight: 4, + marginBottom: 4, + width: 94, + height: 94, + }, +}; + +export class ImageThumbnail extends React.Component { + public renderContent() { + const { i18n, message } = this.props; + + if (!message.imageUrl) { + return ; + } + + return ( + {`${i18n('messageCaption')}: + ); + } + + public render() { + return ( +
+ {this.renderContent()} +
+ ); + } +} diff --git a/ts/components/conversation/media-gallery/LoadingIndicator.tsx b/ts/components/conversation/media-gallery/LoadingIndicator.tsx new file mode 100644 index 000000000..349560a8e --- /dev/null +++ b/ts/components/conversation/media-gallery/LoadingIndicator.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +export const LoadingIndicator = () => { + return ( +
+
+ + + +
+
+ ); +}; diff --git a/ts/components/conversation/media-gallery/MediaGallery.md b/ts/components/conversation/media-gallery/MediaGallery.md new file mode 100644 index 000000000..5ba41bd81 --- /dev/null +++ b/ts/components/conversation/media-gallery/MediaGallery.md @@ -0,0 +1,33 @@ +```jsx +const YEAR_MS = 1 * 12 * 30 * 24 * 60 * 60 * 1000; +const tokens = ['foo', 'bar', 'baz', 'qux', 'quux']; +const fileExtensions = ['docx', 'pdf', 'txt', 'mp3', 'wmv', 'tiff']; +const createRandomMessage = (props) => { + const now = Date.now(); + const fileName = + `${_.sample(tokens)}${_.sample(tokens)}.${_.sample(fileExtensions)}`; + return { + id: _.random(now).toString(), + received_at: _.random(now - YEAR_MS, now), + attachments: [{ + fileName, + data: null, + }], + + // TODO: Revisit + imageUrl: 'https://placekitten.com/94/94', + ...props, + }; +}; + +const startTime = Date.now(); +const messages = _.sortBy( + _.range(30).map(createRandomMessage), + message => -message.received_at +); + + key} + messages={messages} +/> +``` diff --git a/ts/components/conversation/media-gallery/MediaGallery.tsx b/ts/components/conversation/media-gallery/MediaGallery.tsx index 6134835e4..3ecb7215e 100644 --- a/ts/components/conversation/media-gallery/MediaGallery.tsx +++ b/ts/components/conversation/media-gallery/MediaGallery.tsx @@ -1,13 +1,112 @@ import React from 'react'; +import { AttachmentListSection } from './AttachmentListSection'; +import { Message } from './propTypes/Message'; + + +type AttachmentType = 'media' | 'documents'; + interface Props { - number: number; + i18n: (key: string, values?: Array) => string; + messages: Array; } -export class MediaGallery extends React.Component { +interface State { + selectedTab: AttachmentType; +} + +const COLOR_GREY = '#f3f3f3'; + +const tabStyle = { + width: '100%', + backgroundColor: COLOR_GREY, + padding: 20, + textAlign: 'center', +}; + +const styles = { + tabContainer: { + cursor: 'pointer', + display: 'flex', + width: '100%', + }, + tab: { + default: tabStyle, + active: { + ...tabStyle, + borderBottom: '2px solid #08f', + }, + }, + attachmentsContainer: { + padding: 20, + }, +}; + +interface TabSelectEvent { + type: AttachmentType; +} + +const Tab = ({ + isSelected, + label, + onSelect, + type, +}: { + isSelected: boolean, + label: string, + onSelect?: (event: TabSelectEvent) => void, + type: AttachmentType, +}) => { + const handleClick = onSelect ? + () => onSelect({ type }) : undefined; + + return ( +
+ {label} +
+ ); +}; + + +export class MediaGallery extends React.Component { + public state: State = { + selectedTab: 'media', + }; + public render() { + const { selectedTab } = this.state; + return ( -
Hello Media Gallery! Number: {this.props.number}
+
+
+ + +
+
+ +
+
); } + + private handleTabSelect = (event: TabSelectEvent): void => { + this.setState({selectedTab: event.type}); + } } diff --git a/ts/components/conversation/media-gallery/propTypes/Message.tsx b/ts/components/conversation/media-gallery/propTypes/Message.tsx new file mode 100644 index 000000000..094a5a6eb --- /dev/null +++ b/ts/components/conversation/media-gallery/propTypes/Message.tsx @@ -0,0 +1,12 @@ +export interface Message { + id: string; + body?: string; + received_at: number; + attachments: Array<{ + data?: ArrayBuffer; + fileName?: string; + }>; + + // TODO: Revisit + imageUrl: string; +} diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index 4f384d1c3..44138ae22 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -19,7 +19,7 @@ export interface Attachment { // key?: ArrayBuffer; // digest?: ArrayBuffer; // flags?: number; -}; +} export const isVisualMedia = (attachment: Attachment): boolean => { const { contentType } = attachment;