From 5f8249fab02653f342d7371b5864b6ee3b56dac8 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 26 May 2021 13:18:48 +1000 Subject: [PATCH] collections: add LinkBlockItem --- .../src/stories/LinkBlockItem.stories.tsx | 76 ++++++++++++++++++ .../src/stories/LinkInput.stories.tsx | 2 +- .../apps/links/components/LinkBlockItem.tsx | 70 +++++++++++++++++ .../src/views/components/AudioPlayer.tsx | 77 +++++++++++++++++++ .../src/views/components/RemoteContent.tsx | 6 +- 5 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 pkg/interface/src/stories/LinkBlockItem.stories.tsx create mode 100644 pkg/interface/src/views/apps/links/components/LinkBlockItem.tsx create mode 100644 pkg/interface/src/views/components/AudioPlayer.tsx diff --git a/pkg/interface/src/stories/LinkBlockItem.stories.tsx b/pkg/interface/src/stories/LinkBlockItem.stories.tsx new file mode 100644 index 0000000000..b89ec4736b --- /dev/null +++ b/pkg/interface/src/stories/LinkBlockItem.stories.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { Meta } from '@storybook/react'; +import { withDesign } from 'storybook-addon-designs'; + +import { Col } from '@tlon/indigo-react'; +import { LinkBlockItem } from '~/views/apps/links/components/LinkBlockItem'; + +export default { + title: 'Collections/BlockItem', + component: LinkBlockItem, + decorators: [withDesign] +} as Meta; + +export const Image = () => ( + + + + +); + +Image.parameters = { + design: { + type: 'figma', + url: + 'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8228%3A11' + } +}; + +export const Fallback = () => ( + + + + +); + +Fallback.parameters = { + design: { + type: 'figma', + url: + 'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8228%3A57' + } +}; + +export const Audio = () => ( + + + +); + +Audio.parameters = { + design: { + type: 'figma', + url: + 'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8229%3A0' + } +}; + +export const Youtube = () => ( + + + +); + +Youtube.parameters = { + design: { + type: 'figma', + url: + 'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8229%3A0' + } +}; diff --git a/pkg/interface/src/stories/LinkInput.stories.tsx b/pkg/interface/src/stories/LinkInput.stories.tsx index b1ed720e00..093e6f260d 100644 --- a/pkg/interface/src/stories/LinkInput.stories.tsx +++ b/pkg/interface/src/stories/LinkInput.stories.tsx @@ -9,7 +9,7 @@ import { } from '~/views/apps/links/components/LinkInput'; export default { - title: 'Input/Collections', + title: 'Collections/Input', component: LinkInput, decorators: [withDesign] } as Meta; diff --git a/pkg/interface/src/views/apps/links/components/LinkBlockItem.tsx b/pkg/interface/src/views/apps/links/components/LinkBlockItem.tsx new file mode 100644 index 0000000000..d6b6c07774 --- /dev/null +++ b/pkg/interface/src/views/apps/links/components/LinkBlockItem.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { + BaseImage, + Icon, + Center, + Row, + Text +} from '@tlon/indigo-react'; +import { AUDIO_REGEX, IMAGE_REGEX } from '~/views/components/RemoteContent'; +import { AudioPlayer } from '~/views/components/AudioPlayer'; + +export interface LinkBlockItemProps { + url: string; + title?: string; +} + +function getYoutubeId(str: string): string | null { + const youtube = str.match(/youtube\.com.*(\?v=|\/embed\/)(.{11})/); + if(!youtube) { + return null; + } + return youtube.pop(); +} + +export function LinkBlockItem(props: LinkBlockItemProps) { + const { url, title } = props; + + const isImage = IMAGE_REGEX.test(url); + const isAudio = AUDIO_REGEX.test(url); + const youtube = getYoutubeId(url); + return ( +
+ {isImage ? ( + + ) : isAudio ? ( + + ) : youtube ? ( + + ) : ( + + + + {url} + + + )} +
+ ); +} diff --git a/pkg/interface/src/views/components/AudioPlayer.tsx b/pkg/interface/src/views/components/AudioPlayer.tsx new file mode 100644 index 0000000000..3e9da314b4 --- /dev/null +++ b/pkg/interface/src/views/components/AudioPlayer.tsx @@ -0,0 +1,77 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { Action, Text, Icon, Row } from '@tlon/indigo-react'; + +function formatTime(num: number) { + const minutes = Math.floor(num / 60); + const seconds = Math.floor(num % 60); + return `${minutes}:${seconds.toString().padStart(2, '0')}`; +} + +export function AudioPlayer(props: { url: string; title?: string }) { + const { url, title = '' } = props; + const ref = useRef(); + + const [playing, setPlaying] = useState(false); + + const playPause = useCallback(() => { + if (playing) { + ref.current.pause(); + } else { + ref.current.play(); + } + setPlaying(p => !p); + }, [ref, playing]); + + const [duration, setDuration] = useState(0); + const [progress, setProgress] = useState(0); + + useEffect(() => { + if (playing) { + // eslint-disable-next-line no-inner-declarations + function updateProgress() { + setProgress(ref.current.currentTime); + } + + const tim = setInterval(updateProgress, 250); + + return () => { + tim && clearTimeout(tim); + }; + } + }, [ref.current, playing]); + + useEffect(() => { + ref.current.addEventListener('loadedmetadata', () => { + setDuration(ref.current.duration); + }); + }, [ref.current]); + + return ( + + + {title} + + + ); +} diff --git a/pkg/interface/src/views/components/RemoteContent.tsx b/pkg/interface/src/views/components/RemoteContent.tsx index 960275eaae..97ba30d372 100644 --- a/pkg/interface/src/views/components/RemoteContent.tsx +++ b/pkg/interface/src/views/components/RemoteContent.tsx @@ -32,9 +32,9 @@ interface RemoteContentState { showArrow: boolean; } -const IMAGE_REGEX = new RegExp(/(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)$/i); -const AUDIO_REGEX = new RegExp(/(mp3|wav|ogg)$/i); -const VIDEO_REGEX = new RegExp(/(mov|mp4|ogv)$/i); +export const IMAGE_REGEX = new RegExp(/(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)$/i); +export const AUDIO_REGEX = new RegExp(/(mp3|wav|ogg|m4a)$/i); +export const VIDEO_REGEX = new RegExp(/(mov|mp4|ogv)$/i); const TruncatedText = styled(Text)` white-space: pre;