Make it possible to mark videos as spoilers

This commit is contained in:
Ginger 2025-03-06 13:06:30 -05:00
parent ccd4327dcc
commit 2fa748a470
5 changed files with 95 additions and 54 deletions

View file

@ -29,7 +29,7 @@ import { ImageViewer } from './image-viewer';
import { PdfViewer } from './Pdf-viewer'; import { PdfViewer } from './Pdf-viewer';
import { TextViewer } from './text-viewer'; import { TextViewer } from './text-viewer';
import { testMatrixTo } from '../plugins/matrix-to'; import { testMatrixTo } from '../plugins/matrix-to';
import {IImageContent} from "../../types/matrix/common"; import { IImageContent } from '../../types/matrix/common';
type RenderMessageContentProps = { type RenderMessageContentProps = {
displayName: string; displayName: string;
@ -70,7 +70,7 @@ export function RenderMessageContent({
}; };
const renderCaption = () => { const renderCaption = () => {
const content: IImageContent = getContent(); const content: IImageContent = getContent();
if(content.filename && content.filename !== content.body) { if (content.filename && content.filename !== content.body) {
return ( return (
<MText <MText
edited={edited} edited={edited}
@ -85,10 +85,10 @@ export function RenderMessageContent({
)} )}
renderUrlsPreview={urlPreview ? renderUrlsPreview : undefined} renderUrlsPreview={urlPreview ? renderUrlsPreview : undefined}
/> />
) );
} }
return null; return null;
} };
const renderFile = () => ( const renderFile = () => (
<> <>
@ -119,7 +119,6 @@ export function RenderMessageContent({
> >
<DownloadFile body={body} mimeType={mimeType} url={url} encInfo={encInfo} info={info} /> <DownloadFile body={body} mimeType={mimeType} url={url} encInfo={encInfo} info={info} />
</FileContent> </FileContent>
)} )}
outlined={outlineAttachment} outlined={outlineAttachment}
/> />
@ -208,13 +207,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
? () => ( ? () => (
@ -234,7 +231,6 @@ export function RenderMessageContent({
/> />
{renderCaption()} {renderCaption()}
</> </>
); );
} }
@ -251,7 +247,6 @@ export function RenderMessageContent({
/> />
{renderCaption()} {renderCaption()}
</> </>
); );
} }

View file

@ -222,6 +222,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;
@ -256,6 +258,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>

View file

@ -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 &&
!markedAsSpoiler && (
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center"> <Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">
<Spinner variant="Secondary" /> <Spinner variant="Secondary" />
</Box> </Box>

View file

@ -66,7 +66,7 @@ export function UploadCardRenderer({
<Text size="B300">Retry</Text> <Text size="B300">Retry</Text>
</Chip> </Chip>
)} )}
{file.type.startsWith('image') && ( {(file.type.startsWith('image') || file.type.startsWith('video')) && (
<TooltipProvider <TooltipProvider
tooltip={ tooltip={
<Tooltip variant="SurfaceVariant"> <Tooltip variant="SurfaceVariant">

View file

@ -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(