collections: add LinkBlockItem

This commit is contained in:
Liam Fitzgerald 2021-05-26 13:18:48 +10:00
parent 0353fcf6f6
commit 5f8249fab0
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
5 changed files with 227 additions and 4 deletions

View File

@ -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 = () => (
<Col gapY="2" p="2" width="500px" backgroundColor="white">
<LinkBlockItem url="https://media.urbit.org/site/posts/essays/value-of-address-space-pt1.jpg" />
<LinkBlockItem url="https://media.urbit.org/site/posts/essays/ocean.jpeg" />
</Col>
);
Image.parameters = {
design: {
type: 'figma',
url:
'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8228%3A11'
}
};
export const Fallback = () => (
<Col gapY="2" p="2" width="500px" backgroundColor="white">
<LinkBlockItem url="https://www.are.na/edouard-urcades/edouard" />
<LinkBlockItem url="https://thejaymo.net" />
</Col>
);
Fallback.parameters = {
design: {
type: 'figma',
url:
'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8228%3A57'
}
};
export const Audio = () => (
<Col gapY="2" p="2" width="500px" backgroundColor="white">
<LinkBlockItem
title="Artist · Track"
url="https://rovnys-public.s3.amazonaws.com/urbit-from-the-outside-in-1.m4a"
/>
</Col>
);
Audio.parameters = {
design: {
type: 'figma',
url:
'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8229%3A0'
}
};
export const Youtube = () => (
<Col gapY="2" p="2" width="500px" backgroundColor="white">
<LinkBlockItem
title="Artist · Track"
url="https://www.youtube.com/watch?v=M04AKTCDavc&t=1s"
/>
</Col>
);
Youtube.parameters = {
design: {
type: 'figma',
url:
'https://www.figma.com/file/ovD1mlsYDa0agyYTdvCmGr/Landscape?node-id=8229%3A0'
}
};

View File

@ -9,7 +9,7 @@ import {
} from '~/views/apps/links/components/LinkInput'; } from '~/views/apps/links/components/LinkInput';
export default { export default {
title: 'Input/Collections', title: 'Collections/Input',
component: LinkInput, component: LinkInput,
decorators: [withDesign] decorators: [withDesign]
} as Meta; } as Meta;

View File

@ -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 (
<Center
border="1"
borderColor="lightGray"
borderRadius="1"
height="256px"
width="256px"
>
{isImage ? (
<BaseImage
style={{ objectFit: 'contain' }}
height="100%"
src={url}
width="100%"
/>
) : isAudio ? (
<AudioPlayer title={title} url={url} />
) : youtube ? (
<BaseImage
style={{ objectFit: 'contain' }}
height="100%"
src={`https://img.youtube.com/vi/${youtube}/${0}.jpg`}
width="100%"
/>
) : (
<Row overflow="hidden" gapX="2" alignItems="center" p="2">
<Icon color="gray" icon="ArrowExternal" />
<Text
gray
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
>
{url}
</Text>
</Row>
)}
</Center>
);
}

View File

@ -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<HTMLAudioElement>();
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 (
<Row
backgroundColor="white"
alignItems="center"
justifyContent="center"
height="100%"
width="100%"
position="relative"
>
<Row p="2" position="absolute" left="0" top="0">
<Text lineHeight="tall">{title}</Text>
</Row>
<audio ref={ref} src={url} preload="metadata" />
<Action backgroundColor="white" height="unset" onClick={playPause}>
<Icon
height="32px"
width="32px"
color="black"
icon={playing ? 'LargeBullet' : 'TriangleEast'}
/>
</Action>
<Row p="2" position="absolute" right="0" bottom="0">
<Text lineHeight="tall">
{formatTime(progress)} / {formatTime(duration)}
</Text>
</Row>
</Row>
);
}

View File

@ -32,9 +32,9 @@ interface RemoteContentState {
showArrow: boolean; showArrow: boolean;
} }
const IMAGE_REGEX = new RegExp(/(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)$/i); export const IMAGE_REGEX = new RegExp(/(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)$/i);
const AUDIO_REGEX = new RegExp(/(mp3|wav|ogg)$/i); export const AUDIO_REGEX = new RegExp(/(mp3|wav|ogg|m4a)$/i);
const VIDEO_REGEX = new RegExp(/(mov|mp4|ogv)$/i); export const VIDEO_REGEX = new RegExp(/(mov|mp4|ogv)$/i);
const TruncatedText = styled(Text)` const TruncatedText = styled(Text)`
white-space: pre; white-space: pre;