272 lines
9.3 KiB
TypeScript
272 lines
9.3 KiB
TypeScript
import React, { useState } from 'react';
|
|
import Box from '@mui/material/Box';
|
|
import Typography from '@mui/material/Typography';
|
|
import IconButton from '@mui/material/IconButton';
|
|
import Tooltip from '@mui/material/Tooltip';
|
|
import Dialog from '@mui/material/Dialog';
|
|
import DialogContent from '@mui/material/DialogContent';
|
|
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
|
|
import DownloadIcon from '@mui/icons-material/Download';
|
|
import CloseIcon from '@mui/icons-material/Close';
|
|
|
|
interface FileParam {
|
|
name: string;
|
|
size?: number;
|
|
mimetype?: string;
|
|
id: number | string;
|
|
path?: string;
|
|
previewAvailable?: string;
|
|
}
|
|
|
|
interface FileMessageContentProps {
|
|
messageParameters: Record<string, any>;
|
|
isOwnMessage: boolean;
|
|
}
|
|
|
|
function formatFileSize(bytes?: number): string {
|
|
if (!bytes) return '';
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
}
|
|
|
|
function extractFileParams(messageParameters: Record<string, any>): FileParam[] {
|
|
const files: FileParam[] = [];
|
|
for (const key of Object.keys(messageParameters)) {
|
|
if (key === 'file' || key.startsWith('file')) {
|
|
const val = messageParameters[key];
|
|
if (val && typeof val === 'object' && val.id) {
|
|
files.push(val as FileParam);
|
|
}
|
|
}
|
|
}
|
|
return files;
|
|
}
|
|
|
|
type OverlayMode = 'image' | 'pdf' | 'video';
|
|
|
|
interface ContentOverlayProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
mode: OverlayMode;
|
|
contentUrl: string;
|
|
downloadUrl: string;
|
|
name: string;
|
|
}
|
|
|
|
const ContentOverlay: React.FC<ContentOverlayProps> = ({ open, onClose, mode, contentUrl, downloadUrl, name }) => (
|
|
<Dialog
|
|
open={open}
|
|
onClose={onClose}
|
|
maxWidth="lg"
|
|
fullWidth
|
|
PaperProps={{ sx: { bgcolor: mode === 'image' ? 'black' : 'background.paper', m: 1 } }}
|
|
>
|
|
<DialogContent sx={{ p: 0, position: 'relative', display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 200 }}>
|
|
{mode === 'image' && (
|
|
<Box
|
|
component="img"
|
|
src={contentUrl}
|
|
alt={name}
|
|
sx={{ maxWidth: '100%', maxHeight: '85vh', display: 'block', objectFit: 'contain' }}
|
|
/>
|
|
)}
|
|
{mode === 'pdf' && (
|
|
<Box component="iframe" src={contentUrl} sx={{ width: '100%', height: '80vh', border: 'none', display: 'block' }} title={name} />
|
|
)}
|
|
{mode === 'video' && (
|
|
<Box
|
|
component="video"
|
|
controls
|
|
src={contentUrl}
|
|
sx={{ maxWidth: '100%', maxHeight: '85vh', display: 'block' }}
|
|
/>
|
|
)}
|
|
<Tooltip title="Schlie\u00DFen">
|
|
<IconButton
|
|
onClick={onClose}
|
|
size="small"
|
|
sx={{ position: 'absolute', top: 8, right: 8, bgcolor: 'rgba(0,0,0,0.5)', color: 'white', '&:hover': { bgcolor: 'rgba(0,0,0,0.75)' } }}
|
|
>
|
|
<CloseIcon fontSize="small" />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<Tooltip title="Herunterladen">
|
|
<IconButton
|
|
component="a"
|
|
href={downloadUrl}
|
|
download={name}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
size="small"
|
|
sx={{ position: 'absolute', top: 8, right: 44, bgcolor: 'rgba(0,0,0,0.5)', color: 'white', '&:hover': { bgcolor: 'rgba(0,0,0,0.75)' } }}
|
|
>
|
|
<DownloadIcon fontSize="small" />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<Typography
|
|
variant="caption"
|
|
sx={{ position: 'absolute', bottom: 8, left: 0, right: 0, textAlign: 'center', color: 'rgba(255,255,255,0.7)', px: 2 }}
|
|
noWrap
|
|
>
|
|
{name}
|
|
</Typography>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
|
|
const FileMessageContent: React.FC<FileMessageContentProps> = ({ messageParameters, isOwnMessage }) => {
|
|
const [overlayFile, setOverlayFile] = useState<{ file: FileParam; mode: OverlayMode } | null>(null);
|
|
const files = extractFileParams(messageParameters);
|
|
if (files.length === 0) return null;
|
|
|
|
const getOverlayMode = (file: FileParam): OverlayMode | null => {
|
|
const mime = file.mimetype ?? '';
|
|
if (mime.startsWith('image/') && file.previewAvailable === 'yes') return 'image';
|
|
if (mime === 'application/pdf') return 'pdf';
|
|
if (mime.startsWith('video/')) return 'video';
|
|
return null;
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{files.map((file, idx) => {
|
|
const downloadUrl = `/api/nextcloud/talk/files/${file.id}/download${file.path ? `?path=${encodeURIComponent(file.path)}` : ''}`;
|
|
const thumbUrl = `/api/nextcloud/talk/files/${file.id}/preview?w=400&h=400`;
|
|
const mime = file.mimetype ?? '';
|
|
const isImage = mime.startsWith('image/') && file.previewAvailable === 'yes';
|
|
const isAudio = mime.startsWith('audio/');
|
|
const overlayMode = getOverlayMode(file);
|
|
|
|
if (isImage) {
|
|
return (
|
|
<Box key={idx} sx={{ mt: 0.5 }}>
|
|
<Box
|
|
component="img"
|
|
src={thumbUrl}
|
|
alt={file.name}
|
|
onClick={() => setOverlayFile({ file, mode: 'image' })}
|
|
sx={{
|
|
maxWidth: '100%',
|
|
maxHeight: 200,
|
|
borderRadius: 1,
|
|
display: 'block',
|
|
cursor: 'zoom-in',
|
|
'&:hover': { opacity: 0.9 },
|
|
}}
|
|
/>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mt: 0.25 }}>
|
|
<Typography variant="caption" sx={{ opacity: 0.7, fontSize: '0.7rem' }} noWrap>
|
|
{file.name}
|
|
</Typography>
|
|
<Tooltip title="Herunterladen">
|
|
<IconButton
|
|
size="small"
|
|
component="a"
|
|
href={downloadUrl}
|
|
download={file.name}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
sx={{ color: 'inherit', p: 0.25, flexShrink: 0 }}
|
|
>
|
|
<DownloadIcon sx={{ fontSize: '0.9rem' }} />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (isAudio) {
|
|
return (
|
|
<Box key={idx} sx={{ mt: 0.5 }}>
|
|
<Box
|
|
component="audio"
|
|
controls
|
|
src={downloadUrl}
|
|
sx={{ width: '100%', display: 'block' }}
|
|
/>
|
|
<Typography variant="caption" sx={{ opacity: 0.7, fontSize: '0.7rem', display: 'block', mt: 0.25 }} noWrap>
|
|
{file.name}
|
|
</Typography>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box
|
|
key={idx}
|
|
onClick={() => {
|
|
if (overlayMode) {
|
|
setOverlayFile({ file, mode: overlayMode });
|
|
} else {
|
|
window.open(downloadUrl, '_blank', 'noopener,noreferrer');
|
|
}
|
|
}}
|
|
sx={{
|
|
mt: 0.5,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 1,
|
|
p: 0.75,
|
|
borderRadius: 1,
|
|
cursor: 'pointer',
|
|
bgcolor: isOwnMessage
|
|
? 'rgba(255,255,255,0.15)'
|
|
: (theme: any) => theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)',
|
|
'&:hover': {
|
|
bgcolor: isOwnMessage
|
|
? 'rgba(255,255,255,0.25)'
|
|
: (theme: any) => theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)',
|
|
},
|
|
}}
|
|
>
|
|
<InsertDriveFileIcon fontSize="small" sx={{ opacity: 0.8, flexShrink: 0 }} />
|
|
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
<Typography variant="body2" noWrap title={file.name} sx={{ fontWeight: 500, lineHeight: 1.2 }}>
|
|
{file.name}
|
|
</Typography>
|
|
{file.size && (
|
|
<Typography variant="caption" sx={{ opacity: 0.7 }}>
|
|
{formatFileSize(file.size)}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
<Tooltip title="Herunterladen">
|
|
<IconButton
|
|
size="small"
|
|
component="a"
|
|
href={downloadUrl}
|
|
download={file.name}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
onClick={(e) => e.stopPropagation()}
|
|
sx={{ flexShrink: 0, color: 'inherit' }}
|
|
>
|
|
<DownloadIcon fontSize="small" />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</Box>
|
|
);
|
|
})}
|
|
|
|
{overlayFile && (
|
|
<ContentOverlay
|
|
open
|
|
onClose={() => setOverlayFile(null)}
|
|
mode={overlayFile.mode}
|
|
contentUrl={
|
|
overlayFile.mode === 'image'
|
|
? `/api/nextcloud/talk/files/${overlayFile.file.id}/preview?w=1200&h=1200`
|
|
: `/api/nextcloud/talk/files/${overlayFile.file.id}/download${overlayFile.file.path ? `?path=${encodeURIComponent(overlayFile.file.path)}` : ''}`
|
|
}
|
|
downloadUrl={`/api/nextcloud/talk/files/${overlayFile.file.id}/download${overlayFile.file.path ? `?path=${encodeURIComponent(overlayFile.file.path)}` : ''}`}
|
|
name={overlayFile.file.name}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default FileMessageContent;
|