mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-05-06 18:22:44 +08:00
feat: add create file functionality with modal and context menu integration
This commit is contained in:
@@ -97,6 +97,7 @@
|
||||
"Home": "Home",
|
||||
"File Manager": "File Manager",
|
||||
"New Folder": "New Folder",
|
||||
"New File": "New File",
|
||||
"Upload": "Upload",
|
||||
"Name": "Name",
|
||||
"Size": "Size",
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
"Home": "主页",
|
||||
"File Manager": "文件管理",
|
||||
"New Folder": "新建目录",
|
||||
"New File": "新建文件",
|
||||
"Upload": "上传",
|
||||
"Name": "名称",
|
||||
"Size": "大小",
|
||||
|
||||
@@ -17,6 +17,7 @@ import { EmptyState } from './components/EmptyState';
|
||||
import { ContextMenu } from './components/ContextMenu';
|
||||
import { DropzoneOverlay } from './components/DropzoneOverlay';
|
||||
import { CreateDirModal } from './components/Modals/CreateDirModal';
|
||||
import { CreateFileModal } from './components/Modals/CreateFileModal';
|
||||
import { RenameModal } from './components/Modals/RenameModal';
|
||||
import { ProcessorModal } from './components/Modals/ProcessorModal';
|
||||
import UploadModal from './components/Modals/UploadModal';
|
||||
@@ -49,6 +50,7 @@ const FileExplorerPage = memo(function FileExplorerPage() {
|
||||
|
||||
// --- State for Modals ---
|
||||
const [creatingDir, setCreatingDir] = useState(false);
|
||||
const [creatingFile, setCreatingFile] = useState(false);
|
||||
const [renaming, setRenaming] = useState<VfsEntry | null>(null);
|
||||
const [sharingEntries, setSharingEntries] = useState<VfsEntry[]>([]);
|
||||
const [detailEntry, setDetailEntry] = useState<VfsEntry | null>(null);
|
||||
@@ -138,7 +140,7 @@ const FileExplorerPage = memo(function FileExplorerPage() {
|
||||
clearSearchSelection();
|
||||
}, [clearSearchSelection, clearSelection]);
|
||||
|
||||
const { doCreateDir: doCreateDirInCurrentDir } = useFileActions({
|
||||
const { doCreateDir: doCreateDirInCurrentDir, doCreateFile: doCreateFileInCurrentDir } = useFileActions({
|
||||
path,
|
||||
refresh,
|
||||
clearSelection,
|
||||
@@ -343,6 +345,7 @@ const FileExplorerPage = memo(function FileExplorerPage() {
|
||||
|
||||
{/* --- Modals & Context Menus --- */}
|
||||
<CreateDirModal open={creatingDir} onOk={(name) => { doCreateDirInCurrentDir(name); setCreatingDir(false); }} onCancel={() => setCreatingDir(false)} />
|
||||
<CreateFileModal open={creatingFile} onOk={(name) => { doCreateFileInCurrentDir(name); setCreatingFile(false); }} onCancel={() => setCreatingFile(false)} />
|
||||
<RenameModal entry={renaming} onOk={(entry, newName) => { doRename(entry, newName); setRenaming(null); }} onCancel={() => setRenaming(null)} />
|
||||
<FileDetailModal entry={detailEntry} loading={detailLoading} data={detailData} onClose={() => setDetailEntry(null)} />
|
||||
<MoveCopyModal
|
||||
@@ -422,6 +425,7 @@ const FileExplorerPage = memo(function FileExplorerPage() {
|
||||
}}
|
||||
onUploadFile={openFilePicker}
|
||||
onUploadDirectory={openDirectoryPicker}
|
||||
onCreateFile={() => setCreatingFile(true)}
|
||||
onCreateDir={() => setCreatingDir(true)}
|
||||
onShare={doShare}
|
||||
onGetDirectLink={doGetDirectLink}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useI18n } from '../../../i18n';
|
||||
import {
|
||||
FolderFilled, AppstoreOutlined, AppstoreAddOutlined, DownloadOutlined,
|
||||
EditOutlined, DeleteOutlined, InfoCircleOutlined, UploadOutlined, PlusOutlined,
|
||||
ShareAltOutlined, LinkOutlined, CopyOutlined, SwapOutlined
|
||||
ShareAltOutlined, LinkOutlined, CopyOutlined, SwapOutlined, FileAddOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
interface ContextMenuProps {
|
||||
@@ -28,6 +28,7 @@ interface ContextMenuProps {
|
||||
onProcess: (entry: VfsEntry, processorType: string) => void;
|
||||
onUploadFile: () => void;
|
||||
onUploadDirectory: () => void;
|
||||
onCreateFile: () => void;
|
||||
onCreateDir: () => void;
|
||||
onShare: (entries: VfsEntry[]) => void;
|
||||
onGetDirectLink: (entry: VfsEntry) => void;
|
||||
@@ -70,6 +71,7 @@ export const ContextMenu: React.FC<ContextMenuProps> = (props) => {
|
||||
{ key: 'upload-folder', label: t('Upload Folder'), onClick: actions.onUploadDirectory },
|
||||
],
|
||||
},
|
||||
{ key: 'new-file', label: t('New File'), icon: <FileAddOutlined />, onClick: actions.onCreateFile },
|
||||
{ key: 'mkdir', label: t('New Folder'), icon: <PlusOutlined />, onClick: actions.onCreateDir },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Input, Modal } from 'antd';
|
||||
import { useI18n } from '../../../../i18n';
|
||||
|
||||
interface CreateFileModalProps {
|
||||
open: boolean;
|
||||
onOk: (name: string) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export const CreateFileModal: React.FC<CreateFileModalProps> = ({ open, onOk, onCancel }) => {
|
||||
const [name, setName] = useState('');
|
||||
const { t } = useI18n();
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setName('');
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleOk = () => {
|
||||
onOk(name);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('New File')}
|
||||
open={open}
|
||||
onOk={handleOk}
|
||||
onCancel={onCancel}
|
||||
okButtonProps={{ disabled: !name.trim() }}
|
||||
destroyOnHidden
|
||||
>
|
||||
<Input
|
||||
placeholder={t('Filename')}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
onPressEnter={handleOk}
|
||||
autoFocus
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -37,6 +37,20 @@ export function useFileActions({ path, refresh, clearSelection, onShare, onGetDi
|
||||
}
|
||||
}, [path, refresh, t]);
|
||||
|
||||
const doCreateFile = useCallback(async (name: string) => {
|
||||
if (!name.trim()) {
|
||||
message.warning(t('Please input name'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const fullPath = (path === '/' ? '' : path) + '/' + name.trim();
|
||||
await vfsApi.uploadFile(fullPath, new Blob([]));
|
||||
refresh();
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
}
|
||||
}, [path, refresh, t]);
|
||||
|
||||
const doDelete = useCallback(async (entries: VfsEntry[]) => {
|
||||
Modal.confirm({
|
||||
title: t('Confirm delete {name}?', { name: entries.length > 1 ? `${entries.length} ${t('items')}` : entries[0].name }),
|
||||
@@ -193,6 +207,7 @@ export function useFileActions({ path, refresh, clearSelection, onShare, onGetDi
|
||||
|
||||
return {
|
||||
doCreateDir,
|
||||
doCreateFile,
|
||||
doDelete,
|
||||
doRename,
|
||||
doDownload,
|
||||
|
||||
Reference in New Issue
Block a user