/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see .
*/
import * as MediaViewerActionCreators from '@app/actions/MediaViewerActionCreators';
import * as ModalActionCreators from '@app/actions/ModalActionCreators';
import {modal} from '@app/actions/ModalActionCreators';
import styles from '@app/components/channel/ChannelAttachmentArea.module.css';
import EmbedVideo from '@app/components/channel/embeds/media/EmbedVideo';
import {computeHorizontalDropPosition} from '@app/components/layout/dnd/DndDropPosition';
import {type AttachmentDragItem, type AttachmentDropResult, DND_TYPES} from '@app/components/layout/types/DndTypes';
import {AttachmentEditModal} from '@app/components/modals/AttachmentEditModal';
import * as Modal from '@app/components/modals/Modal';
import FocusRing from '@app/components/uikit/focus_ring/FocusRing';
import {Scroller} from '@app/components/uikit/Scroller';
import {Tooltip} from '@app/components/uikit/tooltip/Tooltip';
import {useTextareaAttachments} from '@app/hooks/useCloudUpload';
import {type CloudAttachment, CloudUpload} from '@app/lib/CloudUpload';
import {ComponentDispatch} from '@app/lib/ComponentDispatch';
import MessageStore from '@app/stores/MessageStore';
import MobileLayoutStore from '@app/stores/MobileLayoutStore';
import {isEmbeddableImageFile} from '@app/utils/EmbeddableImageTypes';
import {formatFileSize} from '@app/utils/FileUtils';
import {MessageAttachmentFlags} from '@fluxer/constants/src/ChannelConstants';
import {useLingui} from '@lingui/react/macro';
import {
EyeIcon,
EyeSlashIcon,
FileAudioIcon,
FileCodeIcon,
FileIcon,
FilePdfIcon,
FileTextIcon,
FileZipIcon,
type Icon,
PencilIcon,
TrashIcon,
} from '@phosphor-icons/react';
import {clsx} from 'clsx';
import {observer} from 'mobx-react-lite';
import type React from 'react';
import {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react';
import type {ConnectableElement} from 'react-dnd';
import {useDrag, useDrop} from 'react-dnd';
import {getEmptyImage} from 'react-dnd-html5-backend';
const getFileExtension = (filename: string): string => {
const ext = filename.split('.').pop()?.toLowerCase() || '';
return ext.length > 0 && ext.length <= 4 ? ext : '';
};
const getFileIcon = (file: File): Icon => {
const mimeType = file.type.toLowerCase();
const extension = file.name.split('.').pop()?.toLowerCase() || '';
if (mimeType.startsWith('audio/')) {
return FileAudioIcon;
}
if (mimeType === 'application/pdf') {
return FilePdfIcon;
}
if (mimeType.startsWith('text/') || ['txt', 'md', 'markdown', 'rtf'].includes(extension)) {
return FileTextIcon;
}
if (
[
'application/zip',
'application/x-zip-compressed',
'application/x-rar-compressed',
'application/x-7z-compressed',
].includes(mimeType) ||
['zip', 'rar', '7z', 'tar', 'gz'].includes(extension)
) {
return FileZipIcon;
}
if (
mimeType.startsWith('application/') &&
[
'js',
'ts',
'jsx',
'tsx',
'html',
'css',
'json',
'xml',
'py',
'java',
'cpp',
'c',
'cs',
'php',
'rb',
'go',
'rs',
'swift',
].includes(extension)
) {
return FileCodeIcon;
}
return FileIcon;
};
const isAttachmentMedia = (attachment: CloudAttachment): boolean => {
if (attachment.file.type.startsWith('video/')) {
return attachment.previewURL !== null || attachment.thumbnailURL !== null;
}
if (isEmbeddableImageFile(attachment.file)) {
return attachment.previewURL !== null;
}
return false;
};
const VideoPreviewModal = observer(({file, width, height}: {file: File; width: number; height: number}) => {
const {t} = useLingui();
const [blobUrl, setBlobUrl] = useState(null);
useEffect(() => {
const url = URL.createObjectURL(file);
setBlobUrl(url);
return () => URL.revokeObjectURL(url);
}, [file]);
if (!blobUrl) return null;
return (