mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-09-13 14:22:25 +03:00
Merge 5095594297
into 31c6d13fdf
This commit is contained in:
commit
6d7999dad9
6 changed files with 112 additions and 24 deletions
|
@ -209,13 +209,11 @@ export function RenderMessageContent({
|
||||||
<MVideo
|
<MVideo
|
||||||
content={getContent()}
|
content={getContent()}
|
||||||
renderAsFile={renderFile}
|
renderAsFile={renderFile}
|
||||||
renderVideoContent={({ body, info, mimeType, url, encInfo }) => (
|
renderVideoContent={({ body, info, ...props }) => (
|
||||||
<VideoContent
|
<VideoContent
|
||||||
body={body}
|
body={body}
|
||||||
info={info}
|
info={info}
|
||||||
mimeType={mimeType}
|
{...props}
|
||||||
url={url}
|
|
||||||
encInfo={encInfo}
|
|
||||||
renderThumbnail={
|
renderThumbnail={
|
||||||
mediaAutoLoad
|
mediaAutoLoad
|
||||||
? () => (
|
? () => (
|
||||||
|
|
|
@ -224,6 +224,8 @@ type RenderVideoContentProps = {
|
||||||
mimeType: string;
|
mimeType: string;
|
||||||
url: string;
|
url: string;
|
||||||
encInfo?: IEncryptedFile;
|
encInfo?: IEncryptedFile;
|
||||||
|
markedAsSpoiler?: boolean;
|
||||||
|
spoilerReason?: string;
|
||||||
};
|
};
|
||||||
type MVideoProps = {
|
type MVideoProps = {
|
||||||
content: IVideoContent;
|
content: IVideoContent;
|
||||||
|
@ -274,6 +276,8 @@ export function MVideo({ content, renderAsFile, renderVideoContent, outlined }:
|
||||||
mimeType: safeMimeType,
|
mimeType: safeMimeType,
|
||||||
url: mxcUrl,
|
url: mxcUrl,
|
||||||
encInfo: content.file,
|
encInfo: content.file,
|
||||||
|
markedAsSpoiler: content[MATRIX_SPOILER_PROPERTY_NAME],
|
||||||
|
spoilerReason: content[MATRIX_SPOILER_REASON_PROPERTY_NAME],
|
||||||
})}
|
})}
|
||||||
</AttachmentBox>
|
</AttachmentBox>
|
||||||
</Attachment>
|
</Attachment>
|
||||||
|
|
|
@ -214,7 +214,7 @@ export const ImageContent = as<'div', ImageContentProps>(
|
||||||
)}
|
)}
|
||||||
{(srcState.status === AsyncStatus.Loading || srcState.status === AsyncStatus.Success) &&
|
{(srcState.status === AsyncStatus.Loading || srcState.status === AsyncStatus.Success) &&
|
||||||
!load &&
|
!load &&
|
||||||
!markedAsSpoiler && (
|
!blurred && (
|
||||||
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">
|
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">
|
||||||
<Spinner variant="Secondary" />
|
<Spinner variant="Secondary" />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
Badge,
|
Badge,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
|
Chip,
|
||||||
Icon,
|
Icon,
|
||||||
Icons,
|
Icons,
|
||||||
Spinner,
|
Spinner,
|
||||||
|
@ -47,6 +48,8 @@ type VideoContentProps = {
|
||||||
info: IVideoInfo & IThumbnailContent;
|
info: IVideoInfo & IThumbnailContent;
|
||||||
encInfo?: EncryptedAttachmentInfo;
|
encInfo?: EncryptedAttachmentInfo;
|
||||||
autoPlay?: boolean;
|
autoPlay?: boolean;
|
||||||
|
markedAsSpoiler?: boolean;
|
||||||
|
spoilerReason?: string;
|
||||||
renderThumbnail?: () => ReactNode;
|
renderThumbnail?: () => ReactNode;
|
||||||
renderVideo: (props: RenderVideoProps) => ReactNode;
|
renderVideo: (props: RenderVideoProps) => ReactNode;
|
||||||
};
|
};
|
||||||
|
@ -60,6 +63,8 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||||
info,
|
info,
|
||||||
encInfo,
|
encInfo,
|
||||||
autoPlay,
|
autoPlay,
|
||||||
|
markedAsSpoiler,
|
||||||
|
spoilerReason,
|
||||||
renderThumbnail,
|
renderThumbnail,
|
||||||
renderVideo,
|
renderVideo,
|
||||||
...props
|
...props
|
||||||
|
@ -72,6 +77,7 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||||
|
|
||||||
const [load, setLoad] = useState(false);
|
const [load, setLoad] = useState(false);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
|
const [blurred, setBlurred] = useState(markedAsSpoiler ?? false);
|
||||||
|
|
||||||
const [srcState, loadSrc] = useAsyncCallback(
|
const [srcState, loadSrc] = useAsyncCallback(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
|
@ -114,11 +120,15 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{renderThumbnail && !load && (
|
{renderThumbnail && !load && (
|
||||||
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">
|
<Box
|
||||||
|
className={classNames(css.AbsoluteContainer, blurred && css.Blur)}
|
||||||
|
alignItems="Center"
|
||||||
|
justifyContent="Center"
|
||||||
|
>
|
||||||
{renderThumbnail()}
|
{renderThumbnail()}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{!autoPlay && srcState.status === AsyncStatus.Idle && (
|
{!autoPlay && !blurred && srcState.status === AsyncStatus.Idle && (
|
||||||
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">
|
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">
|
||||||
<Button
|
<Button
|
||||||
variant="Secondary"
|
variant="Secondary"
|
||||||
|
@ -133,7 +143,7 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{srcState.status === AsyncStatus.Success && (
|
{srcState.status === AsyncStatus.Success && (
|
||||||
<Box className={css.AbsoluteContainer}>
|
<Box className={classNames(css.AbsoluteContainer, blurred && css.Blur)}>
|
||||||
{renderVideo({
|
{renderVideo({
|
||||||
title: body,
|
title: body,
|
||||||
src: srcState.data,
|
src: srcState.data,
|
||||||
|
@ -144,8 +154,39 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||||
})}
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
{blurred && !error && srcState.status !== AsyncStatus.Error && (
|
||||||
|
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">
|
||||||
|
<TooltipProvider
|
||||||
|
tooltip={
|
||||||
|
typeof spoilerReason === 'string' && (
|
||||||
|
<Tooltip variant="Secondary">
|
||||||
|
<Text>{spoilerReason}</Text>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
position="Top"
|
||||||
|
align="Center"
|
||||||
|
>
|
||||||
|
{(triggerRef) => (
|
||||||
|
<Chip
|
||||||
|
ref={triggerRef}
|
||||||
|
variant="Secondary"
|
||||||
|
radii="Pill"
|
||||||
|
size="500"
|
||||||
|
outlined
|
||||||
|
onClick={() => {
|
||||||
|
setBlurred(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text size="B300">Spoiler</Text>
|
||||||
|
</Chip>
|
||||||
|
)}
|
||||||
|
</TooltipProvider>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
{(srcState.status === AsyncStatus.Loading || srcState.status === AsyncStatus.Success) &&
|
{(srcState.status === AsyncStatus.Loading || srcState.status === AsyncStatus.Success) &&
|
||||||
!load && (
|
!load &&
|
||||||
|
!blurred && (
|
||||||
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">
|
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">
|
||||||
<Spinner variant="Secondary" />
|
<Spinner variant="Secondary" />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { ReactNode, useEffect } from 'react';
|
||||||
import { Box, Chip, Icon, IconButton, Icons, Text, color, config, toRem } from 'folds';
|
import { Box, Chip, Icon, IconButton, Icons, Text, color, config, toRem } from 'folds';
|
||||||
import { UploadCard, UploadCardError, UploadCardProgress } from './UploadCard';
|
import { UploadCard, UploadCardError, UploadCardProgress } from './UploadCard';
|
||||||
import { UploadStatus, UploadSuccess, useBindUploadAtom } from '../../state/upload';
|
import { UploadStatus, UploadSuccess, useBindUploadAtom } from '../../state/upload';
|
||||||
|
@ -13,8 +13,54 @@ import {
|
||||||
import { useObjectURL } from '../../hooks/useObjectURL';
|
import { useObjectURL } from '../../hooks/useObjectURL';
|
||||||
import { useMediaConfig } from '../../hooks/useMediaConfig';
|
import { useMediaConfig } from '../../hooks/useMediaConfig';
|
||||||
|
|
||||||
type ImagePreviewProps = { fileItem: TUploadItem; onSpoiler: (marked: boolean) => void };
|
type PreviewImageProps = {
|
||||||
function ImagePreview({ fileItem, onSpoiler }: ImagePreviewProps) {
|
fileItem: TUploadItem;
|
||||||
|
};
|
||||||
|
function PreviewImage({ fileItem }: PreviewImageProps) {
|
||||||
|
const { originalFile, metadata } = fileItem;
|
||||||
|
const fileUrl = useObjectURL(originalFile);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
objectFit: 'contain',
|
||||||
|
width: '100%',
|
||||||
|
height: toRem(152),
|
||||||
|
filter: metadata.markedAsSpoiler ? 'blur(44px)' : undefined,
|
||||||
|
}}
|
||||||
|
alt={originalFile.name}
|
||||||
|
src={fileUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreviewVideoProps = {
|
||||||
|
fileItem: TUploadItem;
|
||||||
|
};
|
||||||
|
function PreviewVideo({ fileItem }: PreviewVideoProps) {
|
||||||
|
const { originalFile, metadata } = fileItem;
|
||||||
|
const fileUrl = useObjectURL(originalFile);
|
||||||
|
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/media-has-caption
|
||||||
|
<video
|
||||||
|
style={{
|
||||||
|
objectFit: 'contain',
|
||||||
|
width: '100%',
|
||||||
|
height: toRem(152),
|
||||||
|
filter: metadata.markedAsSpoiler ? 'blur(44px)' : undefined,
|
||||||
|
}}
|
||||||
|
src={fileUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type MediaPreviewProps = {
|
||||||
|
fileItem: TUploadItem;
|
||||||
|
onSpoiler: (marked: boolean) => void;
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
function MediaPreview({ fileItem, onSpoiler, children }: MediaPreviewProps) {
|
||||||
const { originalFile, metadata } = fileItem;
|
const { originalFile, metadata } = fileItem;
|
||||||
const fileUrl = useObjectURL(originalFile);
|
const fileUrl = useObjectURL(originalFile);
|
||||||
|
|
||||||
|
@ -27,16 +73,7 @@ function ImagePreview({ fileItem, onSpoiler }: ImagePreviewProps) {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
{children}
|
||||||
style={{
|
|
||||||
objectFit: 'contain',
|
|
||||||
width: '100%',
|
|
||||||
height: toRem(152),
|
|
||||||
filter: fileItem.metadata.markedAsSpoiler ? 'blur(44px)' : undefined,
|
|
||||||
}}
|
|
||||||
src={fileUrl}
|
|
||||||
alt={originalFile.name}
|
|
||||||
/>
|
|
||||||
<Box
|
<Box
|
||||||
justifyContent="End"
|
justifyContent="End"
|
||||||
style={{
|
style={{
|
||||||
|
@ -136,7 +173,14 @@ export function UploadCardRenderer({
|
||||||
bottom={
|
bottom={
|
||||||
<>
|
<>
|
||||||
{fileItem.originalFile.type.startsWith('image') && (
|
{fileItem.originalFile.type.startsWith('image') && (
|
||||||
<ImagePreview fileItem={fileItem} onSpoiler={handleSpoiler} />
|
<MediaPreview fileItem={fileItem} onSpoiler={handleSpoiler}>
|
||||||
|
<PreviewImage fileItem={fileItem} />
|
||||||
|
</MediaPreview>
|
||||||
|
)}
|
||||||
|
{fileItem.originalFile.type.startsWith('video') && (
|
||||||
|
<MediaPreview fileItem={fileItem} onSpoiler={handleSpoiler}>
|
||||||
|
<PreviewVideo fileItem={fileItem} />
|
||||||
|
</MediaPreview>
|
||||||
)}
|
)}
|
||||||
{upload.status === UploadStatus.Idle && !fileSizeExceeded && (
|
{upload.status === UploadStatus.Idle && !fileSizeExceeded && (
|
||||||
<UploadCardProgress sentBytes={0} totalBytes={file.size} />
|
<UploadCardProgress sentBytes={0} totalBytes={file.size} />
|
||||||
|
|
|
@ -82,7 +82,7 @@ export const getVideoMsgContent = async (
|
||||||
item: TUploadItem,
|
item: TUploadItem,
|
||||||
mxc: string
|
mxc: string
|
||||||
): Promise<IContent> => {
|
): Promise<IContent> => {
|
||||||
const { file, originalFile, encInfo } = item;
|
const { file, originalFile, encInfo, metadata } = item;
|
||||||
|
|
||||||
const [videoError, videoEl] = await to(loadVideoElement(getVideoFileUrl(originalFile)));
|
const [videoError, videoEl] = await to(loadVideoElement(getVideoFileUrl(originalFile)));
|
||||||
if (videoError) console.warn(videoError);
|
if (videoError) console.warn(videoError);
|
||||||
|
@ -91,6 +91,7 @@ export const getVideoMsgContent = async (
|
||||||
msgtype: MsgType.Video,
|
msgtype: MsgType.Video,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
body: file.name,
|
body: file.name,
|
||||||
|
[MATRIX_SPOILER_PROPERTY_NAME]: metadata.markedAsSpoiler,
|
||||||
};
|
};
|
||||||
if (videoEl) {
|
if (videoEl) {
|
||||||
const [thumbError, thumbContent] = await to(
|
const [thumbError, thumbContent] = await to(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue