mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-03-12 14:10:01 +01:00
Show image preview in upload window (#2231)
* memoize metadata callback properly * add image preview on upload * show spoiler image button inside image preview
This commit is contained in:
parent
ccfe30cd68
commit
5c94471956
2 changed files with 71 additions and 38 deletions
|
@ -1,5 +1,5 @@
|
||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Chip, Icon, IconButton, Icons, Text, Tooltip, TooltipProvider, color } 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';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
|
@ -10,11 +10,60 @@ import {
|
||||||
TUploadItem,
|
TUploadItem,
|
||||||
TUploadMetadata,
|
TUploadMetadata,
|
||||||
} from '../../state/room/roomInputDrafts';
|
} from '../../state/room/roomInputDrafts';
|
||||||
|
import { useObjectURL } from '../../hooks/useObjectURL';
|
||||||
|
|
||||||
|
type ImagePreviewProps = { fileItem: TUploadItem; onSpoiler: (marked: boolean) => void };
|
||||||
|
function ImagePreview({ fileItem, onSpoiler }: ImagePreviewProps) {
|
||||||
|
const { originalFile, metadata } = fileItem;
|
||||||
|
const fileUrl = useObjectURL(originalFile);
|
||||||
|
|
||||||
|
return fileUrl ? (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
borderRadius: config.radii.R300,
|
||||||
|
overflow: 'hidden',
|
||||||
|
backgroundColor: 'black',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
objectFit: 'contain',
|
||||||
|
width: '100%',
|
||||||
|
height: toRem(152),
|
||||||
|
filter: fileItem.metadata.markedAsSpoiler ? 'blur(44px)' : undefined,
|
||||||
|
}}
|
||||||
|
src={fileUrl}
|
||||||
|
alt={originalFile.name}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
justifyContent="End"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: config.space.S100,
|
||||||
|
left: config.space.S100,
|
||||||
|
right: config.space.S100,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Chip
|
||||||
|
variant={metadata.markedAsSpoiler ? 'Warning' : 'Secondary'}
|
||||||
|
fill="Soft"
|
||||||
|
radii="Pill"
|
||||||
|
aria-pressed={metadata.markedAsSpoiler}
|
||||||
|
before={<Icon src={Icons.EyeBlind} size="50" />}
|
||||||
|
onClick={() => onSpoiler(!metadata.markedAsSpoiler)}
|
||||||
|
>
|
||||||
|
<Text size="B300">Spoiler</Text>
|
||||||
|
</Chip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
type UploadCardRendererProps = {
|
type UploadCardRendererProps = {
|
||||||
isEncrypted?: boolean;
|
isEncrypted?: boolean;
|
||||||
fileItem: TUploadItem;
|
fileItem: TUploadItem;
|
||||||
setMetadata: (metadata: TUploadMetadata) => void;
|
setMetadata: (fileItem: TUploadItem, metadata: TUploadMetadata) => void;
|
||||||
onRemove: (file: TUploadContent) => void;
|
onRemove: (file: TUploadContent) => void;
|
||||||
onComplete?: (upload: UploadSuccess) => void;
|
onComplete?: (upload: UploadSuccess) => void;
|
||||||
};
|
};
|
||||||
|
@ -33,9 +82,9 @@ export function UploadCardRenderer({
|
||||||
|
|
||||||
if (upload.status === UploadStatus.Idle) startUpload();
|
if (upload.status === UploadStatus.Idle) startUpload();
|
||||||
|
|
||||||
const toggleSpoiler = useCallback(() => {
|
const handleSpoiler = (marked: boolean) => {
|
||||||
setMetadata({ ...metadata, markedAsSpoiler: !metadata.markedAsSpoiler });
|
setMetadata(fileItem, { ...metadata, markedAsSpoiler: marked });
|
||||||
}, [setMetadata, metadata]);
|
};
|
||||||
|
|
||||||
const removeUpload = () => {
|
const removeUpload = () => {
|
||||||
cancelUpload();
|
cancelUpload();
|
||||||
|
@ -66,31 +115,6 @@ export function UploadCardRenderer({
|
||||||
<Text size="B300">Retry</Text>
|
<Text size="B300">Retry</Text>
|
||||||
</Chip>
|
</Chip>
|
||||||
)}
|
)}
|
||||||
{file.type.startsWith('image') && (
|
|
||||||
<TooltipProvider
|
|
||||||
tooltip={
|
|
||||||
<Tooltip variant="SurfaceVariant">
|
|
||||||
<Text>Mark as Spoiler</Text>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
position="Top"
|
|
||||||
align="Center"
|
|
||||||
>
|
|
||||||
{(triggerRef) => (
|
|
||||||
<IconButton
|
|
||||||
ref={triggerRef}
|
|
||||||
onClick={toggleSpoiler}
|
|
||||||
aria-label="Mark as Spoiler"
|
|
||||||
variant="SurfaceVariant"
|
|
||||||
radii="Pill"
|
|
||||||
size="300"
|
|
||||||
aria-pressed={metadata.markedAsSpoiler}
|
|
||||||
>
|
|
||||||
<Icon src={Icons.EyeBlind} size="200" />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</TooltipProvider>
|
|
||||||
)}
|
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={removeUpload}
|
onClick={removeUpload}
|
||||||
aria-label="Cancel Upload"
|
aria-label="Cancel Upload"
|
||||||
|
@ -104,6 +128,9 @@ export function UploadCardRenderer({
|
||||||
}
|
}
|
||||||
bottom={
|
bottom={
|
||||||
<>
|
<>
|
||||||
|
{fileItem.originalFile.type.startsWith('image') && (
|
||||||
|
<ImagePreview fileItem={fileItem} onSpoiler={handleSpoiler} />
|
||||||
|
)}
|
||||||
{upload.status === UploadStatus.Idle && (
|
{upload.status === UploadStatus.Idle && (
|
||||||
<UploadCardProgress sentBytes={0} totalBytes={file.size} />
|
<UploadCardProgress sentBytes={0} totalBytes={file.size} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -70,6 +70,7 @@ import { useFilePasteHandler } from '../../hooks/useFilePasteHandler';
|
||||||
import { useFileDropZone } from '../../hooks/useFileDrop';
|
import { useFileDropZone } from '../../hooks/useFileDrop';
|
||||||
import {
|
import {
|
||||||
TUploadItem,
|
TUploadItem,
|
||||||
|
TUploadMetadata,
|
||||||
roomIdToMsgDraftAtomFamily,
|
roomIdToMsgDraftAtomFamily,
|
||||||
roomIdToReplyDraftAtomFamily,
|
roomIdToReplyDraftAtomFamily,
|
||||||
roomIdToUploadItemsAtomFamily,
|
roomIdToUploadItemsAtomFamily,
|
||||||
|
@ -220,6 +221,17 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||||
[roomId, editor, setMsgDraft]
|
[roomId, editor, setMsgDraft]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleFileMetadata = useCallback(
|
||||||
|
(fileItem: TUploadItem, metadata: TUploadMetadata) => {
|
||||||
|
setSelectedFiles({
|
||||||
|
type: 'REPLACE',
|
||||||
|
item: fileItem,
|
||||||
|
replacement: { ...fileItem, metadata },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setSelectedFiles]
|
||||||
|
);
|
||||||
|
|
||||||
const handleRemoveUpload = useCallback(
|
const handleRemoveUpload = useCallback(
|
||||||
(upload: TUploadContent | TUploadContent[]) => {
|
(upload: TUploadContent | TUploadContent[]) => {
|
||||||
const uploads = Array.isArray(upload) ? upload : [upload];
|
const uploads = Array.isArray(upload) ? upload : [upload];
|
||||||
|
@ -433,13 +445,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||||
key={index}
|
key={index}
|
||||||
isEncrypted={!!fileItem.encInfo}
|
isEncrypted={!!fileItem.encInfo}
|
||||||
fileItem={fileItem}
|
fileItem={fileItem}
|
||||||
setMetadata={(metadata) =>
|
setMetadata={handleFileMetadata}
|
||||||
setSelectedFiles({
|
|
||||||
type: 'REPLACE',
|
|
||||||
item: fileItem,
|
|
||||||
replacement: { ...fileItem, metadata },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onRemove={handleRemoveUpload}
|
onRemove={handleRemoveUpload}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
Loading…
Reference in a new issue