sforkowany z mirror/soapbox
Add StillVideo component
rodzic
fd2bb2e16f
commit
49b996a901
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import StillVideo from 'soapbox/components/still-video';
|
||||||
import { Icon } from 'soapbox/components/ui';
|
import { Icon } from 'soapbox/components/ui';
|
||||||
import { MIMETYPE_ICONS } from 'soapbox/components/upload';
|
import { MIMETYPE_ICONS } from 'soapbox/components/upload';
|
||||||
|
|
||||||
|
@ -8,15 +9,15 @@ import type { Attachment } from 'soapbox/types/entities';
|
||||||
const defaultIcon = require('@tabler/icons/paperclip.svg');
|
const defaultIcon = require('@tabler/icons/paperclip.svg');
|
||||||
|
|
||||||
interface IMediaPreview {
|
interface IMediaPreview {
|
||||||
className?: string
|
|
||||||
attachment: Attachment
|
attachment: Attachment
|
||||||
|
withExt?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a generic preview for an attachment depending on its media type.
|
* Displays a generic preview for an attachment depending on its media type.
|
||||||
* It fills its container and is expected to be sized by its parent.
|
* It fills its container and is expected to be sized by its parent.
|
||||||
*/
|
*/
|
||||||
const MediaPreview: React.FC<IMediaPreview> = ({ className, attachment }) => {
|
const MediaPreview: React.FC<IMediaPreview> = ({ attachment, withExt }) => {
|
||||||
const mimeType = attachment.pleroma.get('mime_type') as string | undefined;
|
const mimeType = attachment.pleroma.get('mime_type') as string | undefined;
|
||||||
|
|
||||||
switch (attachment.type) {
|
switch (attachment.type) {
|
||||||
|
@ -31,14 +32,10 @@ const MediaPreview: React.FC<IMediaPreview> = ({ className, attachment }) => {
|
||||||
);
|
);
|
||||||
case 'video':
|
case 'video':
|
||||||
return (
|
return (
|
||||||
<video
|
<StillVideo
|
||||||
className='pointer-events-none h-full w-full object-cover'
|
className='h-full w-full object-cover'
|
||||||
src={attachment.preview_url}
|
src={attachment.url}
|
||||||
autoPlay
|
withExt={withExt}
|
||||||
playsInline
|
|
||||||
controls={false}
|
|
||||||
muted
|
|
||||||
loop
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import ExtensionBadge from './extension-badge';
|
||||||
|
|
||||||
|
interface IStillVideo {
|
||||||
|
src: string
|
||||||
|
className?: string
|
||||||
|
hoverToPlay?: boolean
|
||||||
|
playbackRate?: number
|
||||||
|
withExt?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Displays a frozen frame of a video unless hovered. */
|
||||||
|
const StillVideo: React.FC<IStillVideo> = ({
|
||||||
|
src,
|
||||||
|
className,
|
||||||
|
hoverToPlay = true,
|
||||||
|
playbackRate = 3,
|
||||||
|
withExt = true,
|
||||||
|
}) => {
|
||||||
|
// https://stackoverflow.com/a/4695156
|
||||||
|
const ext = src.split('.').pop()?.toUpperCase();
|
||||||
|
|
||||||
|
const handleMouseEnter: React.MouseEventHandler<HTMLVideoElement> = ({ currentTarget: video }) => {
|
||||||
|
if (hoverToPlay) {
|
||||||
|
video.playbackRate = playbackRate;
|
||||||
|
video.play();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave: React.MouseEventHandler<HTMLVideoElement> = ({ currentTarget: video }) => {
|
||||||
|
if (hoverToPlay) {
|
||||||
|
video.pause();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick: React.MouseEventHandler<HTMLVideoElement> = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(className, 'relative isolate overflow-hidden')}>
|
||||||
|
<video
|
||||||
|
className='h-full w-full object-cover'
|
||||||
|
src={src}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
onClick={handleClick}
|
||||||
|
playsInline
|
||||||
|
controls={false}
|
||||||
|
muted
|
||||||
|
loop
|
||||||
|
/>
|
||||||
|
|
||||||
|
{(withExt && ext) && (
|
||||||
|
<div className='pointer-events-none absolute left-2 bottom-2 opacity-90'>
|
||||||
|
<ExtensionBadge ext={ext} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StillVideo;
|
|
@ -1,55 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { Icon } from 'soapbox/components/ui';
|
|
||||||
import { MIMETYPE_ICONS } from 'soapbox/components/upload';
|
|
||||||
|
|
||||||
import type { Attachment } from 'soapbox/types/entities';
|
|
||||||
|
|
||||||
const defaultIcon = require('@tabler/icons/paperclip.svg');
|
|
||||||
|
|
||||||
interface IChatUploadPreview {
|
|
||||||
className?: string
|
|
||||||
attachment: Attachment
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a generic preview for an upload depending on its media type.
|
|
||||||
* It fills its container and is expected to be sized by its parent.
|
|
||||||
*/
|
|
||||||
const ChatUploadPreview: React.FC<IChatUploadPreview> = ({ className, attachment }) => {
|
|
||||||
const mimeType = attachment.pleroma.get('mime_type') as string | undefined;
|
|
||||||
|
|
||||||
switch (attachment.type) {
|
|
||||||
case 'image':
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
className='pointer-events-none h-full w-full object-cover'
|
|
||||||
src={attachment.preview_url}
|
|
||||||
alt=''
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case 'video':
|
|
||||||
return (
|
|
||||||
<video
|
|
||||||
className='pointer-events-none h-full w-full object-cover'
|
|
||||||
src={attachment.preview_url}
|
|
||||||
autoPlay
|
|
||||||
playsInline
|
|
||||||
controls={false}
|
|
||||||
muted
|
|
||||||
loop
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<div className='pointer-events-none flex h-full w-full items-center justify-center'>
|
|
||||||
<Icon
|
|
||||||
className='mx-auto my-12 h-16 w-16 text-gray-800 dark:text-gray-200'
|
|
||||||
src={MIMETYPE_ICONS[mimeType || ''] || defaultIcon}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ChatUploadPreview;
|
|
|
@ -28,7 +28,7 @@ const ChatUpload: React.FC<IChatUpload> = ({ attachment, onDelete }) => {
|
||||||
<div className='relative isolate inline-block h-24 w-24 overflow-hidden rounded-lg bg-gray-200 dark:bg-primary-900'>
|
<div className='relative isolate inline-block h-24 w-24 overflow-hidden rounded-lg bg-gray-200 dark:bg-primary-900'>
|
||||||
<Blurhash hash={attachment.blurhash} className='absolute inset-0 -z-10 h-full w-full' />
|
<Blurhash hash={attachment.blurhash} className='absolute inset-0 -z-10 h-full w-full' />
|
||||||
|
|
||||||
<div className='absolute right-[6px] top-[6px]'>
|
<div className='absolute right-[6px] top-[6px] z-10'>
|
||||||
<RemoveButton onClick={onDelete} />
|
<RemoveButton onClick={onDelete} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ const ChatUpload: React.FC<IChatUpload> = ({ attachment, onDelete }) => {
|
||||||
onClick={clickable ? handleOpenModal : undefined}
|
onClick={clickable ? handleOpenModal : undefined}
|
||||||
className={clsx('h-full w-full', { 'cursor-zoom-in': clickable, 'cursor-default': !clickable })}
|
className={clsx('h-full w-full', { 'cursor-zoom-in': clickable, 'cursor-default': !clickable })}
|
||||||
>
|
>
|
||||||
<MediaPreview attachment={attachment} />
|
<MediaPreview attachment={attachment} withExt={false} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Ładowanie…
Reference in New Issue