pl-fe: allow uploading files to drive
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -5957,7 +5957,8 @@ class PlApiClient {
|
||||
|
||||
const response = await this.request('/api/iceshrimp/drive', {
|
||||
method: 'POST',
|
||||
body: { file, folderId },
|
||||
body: { file },
|
||||
params: { folderId },
|
||||
contentType: '',
|
||||
});
|
||||
|
||||
|
||||
@ -23,6 +23,8 @@ interface MenuItem {
|
||||
to?: string;
|
||||
type?: 'toggle' | 'radio';
|
||||
items?: Array<Omit<MenuItem, 'items'>>;
|
||||
onSelectFile?: (files: FileList) => void;
|
||||
accept?: string;
|
||||
}
|
||||
|
||||
interface IDropdownMenuItem {
|
||||
@ -37,6 +39,7 @@ const DropdownMenuItem = ({ index, item, onClick, autoFocus, onSetTab }: IDropdo
|
||||
const history = useHistory();
|
||||
|
||||
const itemRef = useRef<HTMLAnchorElement>(null);
|
||||
const fileElement = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleClick: React.EventHandler<React.MouseEvent | React.KeyboardEvent> = (event) => {
|
||||
event.stopPropagation();
|
||||
@ -49,6 +52,11 @@ const DropdownMenuItem = ({ index, item, onClick, autoFocus, onSetTab }: IDropdo
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.onSelectFile) {
|
||||
fileElement.current?.click();
|
||||
return;
|
||||
}
|
||||
|
||||
if (onClick) onClick(!(item.to && userTouching.matches));
|
||||
|
||||
if (item.to) {
|
||||
@ -65,6 +73,7 @@ const DropdownMenuItem = ({ index, item, onClick, autoFocus, onSetTab }: IDropdo
|
||||
|
||||
const handleAuxClick: React.EventHandler<React.MouseEvent> = (event) => {
|
||||
if (!item) return;
|
||||
if (item.onSelectFile) fileElement.current?.click();
|
||||
if (onClick) onClick();
|
||||
|
||||
if (event.button === 1 && item.middleClick) {
|
||||
@ -87,6 +96,14 @@ const DropdownMenuItem = ({ index, item, onClick, autoFocus, onSetTab }: IDropdo
|
||||
if (item.onChange) item.onChange(event.target.checked);
|
||||
};
|
||||
|
||||
const handleSelectFileChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
console.log('handleSelectFileChange');
|
||||
console.log(e.target.files, item);
|
||||
if (e.target.files?.length && item?.onSelectFile) {
|
||||
item.onSelectFile(e.target.files);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const firstItem = index === 0;
|
||||
|
||||
@ -145,6 +162,19 @@ const DropdownMenuItem = ({ index, item, onClick, autoFocus, onSetTab }: IDropdo
|
||||
<Icon src={require('@phosphor-icons/core/regular/caret-right.svg')} containerClassName='ml-auto' className='size-5 flex-none' />
|
||||
)}
|
||||
</a>
|
||||
|
||||
{item.onSelectFile && (
|
||||
<label className='sr-only'>
|
||||
<span>{item.text}</span>
|
||||
<input
|
||||
ref={fileElement}
|
||||
type='file'
|
||||
accept={item.accept}
|
||||
onChange={handleSelectFileChange}
|
||||
className='hidden'
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
@ -734,6 +734,9 @@
|
||||
"drive.file.update_description.error": "Failed to update description.",
|
||||
"drive.file.update_description.placeholder": "New description",
|
||||
"drive.file.update_description.success": "Description updated successfully.",
|
||||
"drive.file.upload": "Upload file",
|
||||
"drive.file.upload.error": "Failed to upload file.",
|
||||
"drive.file.upload.success": "File uploaded successfully.",
|
||||
"drive.file.view": "View file",
|
||||
"drive.folder.delete": "Delete folder",
|
||||
"drive.folder.delete.confirm": "Delete",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import defaultIcon from '@phosphor-icons/core/regular/paperclip.svg';
|
||||
import React, { useMemo } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import DropdownMenu, { Menu } from 'pl-fe/components/dropdown-menu';
|
||||
import { EmptyMessage } from 'pl-fe/components/empty-message';
|
||||
@ -10,10 +11,11 @@ import Icon from 'pl-fe/components/ui/icon';
|
||||
import IconButton from 'pl-fe/components/ui/icon-button';
|
||||
import { MIMETYPE_ICONS } from 'pl-fe/components/upload';
|
||||
import ColumnLoading from 'pl-fe/features/ui/components/column-loading';
|
||||
import { useDeleteDriveFileMutation, useUpdateDriveFileMutation } from 'pl-fe/queries/drive/use-drive-file';
|
||||
import { useCreateDriveFileMutation, useDeleteDriveFileMutation, useUpdateDriveFileMutation } from 'pl-fe/queries/drive/use-drive-file';
|
||||
import { useDeleteDriveFolderMutation, useDriveFolderQuery, useUpdateDriveFolderMutation } from 'pl-fe/queries/drive/use-drive-folder';
|
||||
import { useModalsActions } from 'pl-fe/stores/modals';
|
||||
import toast from 'pl-fe/toast';
|
||||
import { download } from 'pl-fe/utils/download';
|
||||
|
||||
import type { DriveFile, DriveFolder, MediaAttachment } from 'pl-api';
|
||||
|
||||
@ -49,6 +51,9 @@ const messages = defineMessages({
|
||||
fileDelete: { id: 'drive.file.delete', defaultMessage: 'Delete file' },
|
||||
fileDeleteSuccess: { id: 'drive.file.delete.success', defaultMessage: 'File deleted successfully.' },
|
||||
fileDeleteError: { id: 'drive.file.delete.error', defaultMessage: 'Failed to delete file.' },
|
||||
fileUpload: { id: 'drive.file.upload', defaultMessage: 'Upload file' },
|
||||
fileUploadSuccess: { id: 'drive.file.upload.success', defaultMessage: 'File uploaded successfully.' },
|
||||
fileUploadError: { id: 'drive.file.upload.error', defaultMessage: 'Failed to upload file.' },
|
||||
});
|
||||
|
||||
interface IFile {
|
||||
@ -64,25 +69,30 @@ const File: React.FC<IFile> = ({ file }) => {
|
||||
|
||||
const isMedia = file.content_type.match(/image|video|audio/);
|
||||
|
||||
const handleView = () => {
|
||||
if (!isMedia) {
|
||||
download(file.url, file.filename);
|
||||
return;
|
||||
}
|
||||
|
||||
const mediaAttachment = {
|
||||
id: file.id,
|
||||
url: file.url,
|
||||
preview_url: file.thumbnail_url,
|
||||
remote_url: file.url,
|
||||
description: file.description || '',
|
||||
type: file.content_type.split('/')[0] as 'image' | 'video' | 'audio' | 'unknown',
|
||||
mime_type: file.content_type,
|
||||
blurhash: null,
|
||||
} as MediaAttachment;
|
||||
|
||||
openModal('MEDIA', {
|
||||
media: [mediaAttachment],
|
||||
index: 0,
|
||||
});
|
||||
};
|
||||
|
||||
const items = useMemo(() => {
|
||||
const handleView = () => {
|
||||
const mediaAttachment = {
|
||||
id: file.id,
|
||||
url: file.url,
|
||||
preview_url: file.thumbnail_url,
|
||||
remote_url: file.url,
|
||||
description: file.description || '',
|
||||
type: file.content_type.split('/')[0] as 'image' | 'video' | 'audio' | 'unknown',
|
||||
mime_type: file.content_type,
|
||||
blurhash: null,
|
||||
} as MediaAttachment;
|
||||
|
||||
openModal('MEDIA', {
|
||||
media: [mediaAttachment],
|
||||
index: 0,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRename = () => {
|
||||
openModal('TEXT_FIELD', {
|
||||
heading: <FormattedMessage id='drive.file.rename' defaultMessage='Rename file' />,
|
||||
@ -203,7 +213,7 @@ const File: React.FC<IFile> = ({ file }) => {
|
||||
}, [file]);
|
||||
|
||||
return (
|
||||
<div className='group relative flex w-32 flex-col items-center gap-2' tabIndex={0}>
|
||||
<div className='group relative flex w-32 flex-col items-center gap-2' tabIndex={0} onDoubleClick={handleView}>
|
||||
<div className='invisible absolute self-end group-hover:visible group-focus:visible'>
|
||||
<DropdownMenu items={items} placement='right-start'>
|
||||
<IconButton
|
||||
@ -241,12 +251,17 @@ interface IFolder {
|
||||
}
|
||||
|
||||
const Folder: React.FC<IFolder> = ({ folder }) => {
|
||||
const history = useHistory();
|
||||
const intl = useIntl();
|
||||
|
||||
const { openModal } = useModalsActions();
|
||||
const { mutate: deleteFolder } = useDeleteDriveFolderMutation(folder.id!);
|
||||
const { mutate: updateFolder } = useUpdateDriveFolderMutation(folder.id!);
|
||||
|
||||
const handleEnterFolder = () => {
|
||||
history.push(`/drive/${folder.id}`);
|
||||
};
|
||||
|
||||
const items: Menu = useMemo(() => {
|
||||
const handleRename = () => {
|
||||
openModal('TEXT_FIELD', {
|
||||
@ -299,7 +314,7 @@ const Folder: React.FC<IFolder> = ({ folder }) => {
|
||||
}, [folder]);
|
||||
|
||||
return (
|
||||
<div className='group relative flex w-32 flex-col items-center gap-2' tabIndex={0}>
|
||||
<div className='group relative flex w-32 flex-col items-center gap-2' tabIndex={0} onDoubleClick={handleEnterFolder}>
|
||||
<div className='invisible absolute self-end group-hover:visible group-focus:visible'>
|
||||
<DropdownMenu items={items} placement='right-start'>
|
||||
<IconButton
|
||||
@ -334,6 +349,20 @@ const DrivePage: React.FC<IDrivePage> = ({ params }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { data, isPending } = useDriveFolderQuery(params?.folderId);
|
||||
const { mutate: uploadFile } = useCreateDriveFileMutation(params?.folderId);
|
||||
|
||||
const items: Menu = [
|
||||
{
|
||||
text: intl.formatMessage(messages.fileUpload),
|
||||
icon: require('@phosphor-icons/core/regular/upload.svg'),
|
||||
onSelectFile: (files: FileList) => {
|
||||
uploadFile(files[0], {
|
||||
onSuccess: () => toast.success(messages.fileUploadSuccess),
|
||||
onError: (error) => toast.error(messages.fileUploadError),
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (isPending) {
|
||||
return <ColumnLoading />;
|
||||
@ -345,7 +374,7 @@ const DrivePage: React.FC<IDrivePage> = ({ params }) => {
|
||||
<Column
|
||||
label={data?.name || intl.formatMessage(messages.heading)}
|
||||
backHref={data?.id === null ? '/drive' : data?.parent_id ? `/drive/${data.parent_id}` : undefined}
|
||||
// action={<DropdownMenu items={items} src={require('@phosphor-icons/core/regular/dots-three-vertical.svg')} />}
|
||||
action={<DropdownMenu items={items} src={require('@phosphor-icons/core/regular/dots-three-vertical.svg')} />}
|
||||
>
|
||||
{isEmpty ? (
|
||||
<EmptyMessage
|
||||
|
||||
@ -16,14 +16,14 @@ const useDriveFileQuery = (fileId: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
const useCreateDriveFileMutation = () => {
|
||||
const useCreateDriveFileMutation = (folderId?: string) => {
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['drive', 'files'],
|
||||
mutationFn: ({ file, folderId }: { file: File; folderId?: string }) => client.drive.createFile(file, folderId),
|
||||
onSuccess: (file, { folderId }) => {
|
||||
mutationFn: (file: File) => client.drive.createFile(file, folderId),
|
||||
onSuccess: (file) => {
|
||||
queryClient.setQueryData(['drive', 'files', file.id], file);
|
||||
queryClient.invalidateQueries({ queryKey: ['drive', 'folders', folderId], exact: true });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user