From 941999c9c9073833d945f50523196a3591ec0265 Mon Sep 17 00:00:00 2001 From: yuanyxh <15766118362@139.com> Date: Tue, 7 May 2024 23:53:05 +0800 Subject: [PATCH] feat: add webdav create modal --- src/App.tsx | 6 +- src/assets/svgs/mdi--web.svg | 1 + src/filehandle/FilePanelFactory.tsx | 17 ++- src/filehandle/components/FileContent.tsx | 122 +++++++++++++++++- src/filehandle/hooks/useFileSystem.ts | 8 +- .../component/styles/MDSidebar.module.less | 2 +- src/filehandle/md_editor/index.tsx | 7 +- src/filehandle/utils/fileManager.ts | 4 + src/store/index.ts | 2 + src/store/useAppStore.ts | 18 +-- src/store/useUserStore.ts | 35 +++++ src/utils/localStorageTools.ts | 3 +- 12 files changed, 200 insertions(+), 25 deletions(-) create mode 100644 src/assets/svgs/mdi--web.svg create mode 100644 src/store/useUserStore.ts diff --git a/src/App.tsx b/src/App.tsx index 06cf0e0..a21e893 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,7 +12,7 @@ import type { MessageInstance } from 'antd/es/message/interface'; import type { HookAPI } from 'antd/es/modal/useModal'; import type { NotificationInstance } from 'antd/es/notification/interface'; -import type { State } from '@/store'; +import type { AppState } from '@/store'; import { useAppStore } from '@/store'; import { @@ -80,9 +80,9 @@ const App: React.FC = (props) => { const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)'); if (hasLocalStorage('app')) { - setLanguage(getStorage('app')?.settings?.language || 'zh-CN'); + setLanguage(getStorage('app')?.settings?.language || 'zh-CN'); setColorScheme( - getStorage('app')?.settings?.colorScheme || 'light' + getStorage('app')?.settings?.colorScheme || 'light' ); } else { setLanguage(language); diff --git a/src/assets/svgs/mdi--web.svg b/src/assets/svgs/mdi--web.svg new file mode 100644 index 0000000..241f71e --- /dev/null +++ b/src/assets/svgs/mdi--web.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/filehandle/FilePanelFactory.tsx b/src/filehandle/FilePanelFactory.tsx index 7b54545..be4c9f2 100644 --- a/src/filehandle/FilePanelFactory.tsx +++ b/src/filehandle/FilePanelFactory.tsx @@ -1,3 +1,4 @@ +import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { App } from 'antd'; @@ -20,15 +21,15 @@ class FilePanelFactory { constructor() { const show = (cb?: AsyncFunction) => { - if (!this._show && cb) { - return (this._show = cb); + if (cb) { + this._show = cb; } this.show(); }; const hide = (cb?: AsyncFunction) => { - if (!this._hide && cb) { - return (this._hide = cb); + if (cb) { + this._hide = cb; } this.hide(); @@ -36,9 +37,11 @@ class FilePanelFactory { this.root.render( // antd App provider Modal, Message, Notification - - - + + + + + ); } diff --git a/src/filehandle/components/FileContent.tsx b/src/filehandle/components/FileContent.tsx index 62b88ba..7553db4 100644 --- a/src/filehandle/components/FileContent.tsx +++ b/src/filehandle/components/FileContent.tsx @@ -2,9 +2,12 @@ import { useContext, useMemo, useRef, useState } from 'react'; import type { InputProps } from 'antd'; import type { InputRef } from 'antd'; -import { App, Input, Modal, Typography } from 'antd'; +import { App, Form, Input, Modal, Typography } from 'antd'; import { ExclamationCircleFilled } from '@ant-design/icons'; +import type { WebdavInfo } from '@/store'; +import { useUserStore } from '@/store'; + import { sleep, validateFileName } from '@/utils'; import { ContextMenu, Icon } from '@/components'; @@ -123,6 +126,101 @@ function AddFileModal({ ); } +function MountWebdavModal(props: { open: boolean; close(): void }) { + const { open, close } = props; + + const { modal, message } = App.useApp(); + + const [form] = Form.useForm(); + + const { webdavs, addWebdav } = useUserStore(); + + const handleOk = () => { + form + .validateFields() + .then(() => { + const webdav: WebdavInfo = form.getFieldsValue(); + + if (webdavs.some((w) => w.name === webdav.name.trim())) { + modal.error({ + title: '提示', + content: `已包含名称为 "${webdav.name.trim()}" 的挂载目录!` + }); + + return void 0; + } + + addWebdav({ ...webdav, name: webdav.name.trim() }); + + message.success('已添加 webdav 目录。'); + + sleep(35, close); + }) + .catch(() => { + /* empty */ + }); + }; + + const handleCancel = () => { + form.resetFields(); + close(); + }; + + return ( + +
+ + label="webdav 路径" + name="url" + rules={[{ required: true, message: 'Please input your webdav url!' }]} + > + + + + + label="目录名称" + name="name" + rules={[ + { required: true, message: 'Please input your webdav locale name!' } + ]} + > + + + + + label="用户名" + name="username" + rules={[{ required: true, message: 'Please input your username!' }]} + > + + + + + label="密码" + name="password" + rules={[{ required: true, message: 'Please input your password!' }]} + > + + + +
+ ); +} + interface IFileItemProps extends React.AnchorHTMLAttributes { file: FileInfo; } @@ -178,16 +276,21 @@ const FileContent: React.FC = (props) => { current, children, fileHandles, + fileLinked, enterDirectory, create, remove, forceUpdate } = useContext(FileSystemContext); + const root = fileLinked?.root?.value; + const titleRef = useRef(0); const sectionRef = useRef(null); const [isModalOpen, setModalOpen] = useState(false); + const [isMountModalOpen, setMountModalOpen] = useState(false); + const [selection, setSelection] = useState([]); const contextMenu = fileHandles @@ -218,6 +321,10 @@ const FileContent: React.FC = (props) => { setModalOpen(true); }; + const handleMountWebdav = () => { + setMountModalOpen(true); + }; + const handleImportFile = () => { importFile(current) .then((value) => { @@ -313,6 +420,14 @@ const FileContent: React.FC = (props) => { onHide={() => selection.length && setSelection([])} menu={[ ...contextMenu, + { + name: '挂载 webdav 目录', + icon: , + style: { + display: current === root ? void 0 : 'none' + }, + onClick: handleMountWebdav + }, { name: '新建文件', icon: , @@ -366,6 +481,11 @@ const FileContent: React.FC = (props) => { onOk={handleOk} onCancel={handleCancel} /> + + setMountModalOpen(false)} + /> ); }; diff --git a/src/filehandle/hooks/useFileSystem.ts b/src/filehandle/hooks/useFileSystem.ts index 2786604..226dacf 100644 --- a/src/filehandle/hooks/useFileSystem.ts +++ b/src/filehandle/hooks/useFileSystem.ts @@ -2,6 +2,8 @@ import { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { cloneDeep } from 'lodash-es'; +import { useUserStore } from '@/store'; + import { AppContext } from '@/App'; import FileLinkedList from '../FileLinkedList'; @@ -44,6 +46,8 @@ export interface FileSystem { export function useFileSystem(): FileSystem { const { message } = useContext(AppContext); + const { webdavs } = useUserStore(); + const root = useRef() as React.MutableRefObject; const fileLinked = @@ -60,7 +64,7 @@ export function useFileSystem(): FileSystem { if (!current) return; getChildren(current).then((_children) => { - setChildren(_children); + setChildren([..._children]); }); }; @@ -68,7 +72,7 @@ export function useFileSystem(): FileSystem { if (!current) return void 0; update(); - }, [current]); + }, [current, webdavs]); useMemo(() => { if (!fileLinked.current) return; diff --git a/src/filehandle/md_editor/component/styles/MDSidebar.module.less b/src/filehandle/md_editor/component/styles/MDSidebar.module.less index 3130c52..a41b1f2 100644 --- a/src/filehandle/md_editor/component/styles/MDSidebar.module.less +++ b/src/filehandle/md_editor/component/styles/MDSidebar.module.less @@ -61,8 +61,8 @@ } &.active.changed > span::after { - content: '*'; color: var(--color-primary); + content: '*'; } } } diff --git a/src/filehandle/md_editor/index.tsx b/src/filehandle/md_editor/index.tsx index 9977545..845640c 100644 --- a/src/filehandle/md_editor/index.tsx +++ b/src/filehandle/md_editor/index.tsx @@ -1,3 +1,4 @@ +import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { createElementContainer } from '@/utils'; @@ -16,7 +17,11 @@ async function createMDHandleInstance(handle: DH | FH) { root.unmount(); el.remove(); }; - root.render(); + root.render( + + + + ); } const mdHandler: FileHandle = { diff --git a/src/filehandle/utils/fileManager.ts b/src/filehandle/utils/fileManager.ts index 397b5d5..08216ab 100644 --- a/src/filehandle/utils/fileManager.ts +++ b/src/filehandle/utils/fileManager.ts @@ -15,6 +15,10 @@ export interface FileInfo { type: FileType; icon: React.ReactNode; handle: DH | FH; + /** this is webdav file? */ + remote?: boolean; + /** webdav file full path */ + fullPath?: string; ext?: string; } diff --git a/src/store/index.ts b/src/store/index.ts index 07a032c..d438108 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,2 +1,4 @@ export { default as useAppStore } from './useAppStore'; export * from './useAppStore'; +export { default as useUserStore } from './useUserStore'; +export * from './useUserStore'; diff --git a/src/store/useAppStore.ts b/src/store/useAppStore.ts index fa5b427..07bfa17 100644 --- a/src/store/useAppStore.ts +++ b/src/store/useAppStore.ts @@ -2,7 +2,7 @@ import { getStorage, setStorage } from '@/utils'; import { create } from 'zustand'; -export interface State { +export interface AppState { settings: { language: string; colorScheme: 'light' | 'dark'; @@ -14,23 +14,23 @@ export interface State { }; } -export interface Actions { +export interface AppActions { setLanguage(language: string): void; - setColorScheme(colorScheme: State['settings']['colorScheme']): void; + setColorScheme(colorScheme: AppState['settings']['colorScheme']): void; setColorSchemeNonPersistent( - colorScheme: State['settings']['colorScheme'] + colorScheme: AppState['settings']['colorScheme'] ): void; setEnableServiceWorkerCache( - enableServiceWorkerCache: State['settings']['enableServiceWorkerCache'] + enableServiceWorkerCache: AppState['settings']['enableServiceWorkerCache'] ): void; setEnableNotification( - enableNotification: State['settings']['enableNotification'] + enableNotification: AppState['settings']['enableNotification'] ): void; - setFrontDesk(frontDesk: State['status']['frontDesk']): void; + setFrontDesk(frontDesk: AppState['status']['frontDesk']): void; } -const useAppStore = create((set) => ({ - ...getStorage('app', { +const useAppStore = create((set) => ({ + ...getStorage('app', { settings: { language: 'zh-CN', colorScheme: 'light', diff --git a/src/store/useUserStore.ts b/src/store/useUserStore.ts new file mode 100644 index 0000000..2896fff --- /dev/null +++ b/src/store/useUserStore.ts @@ -0,0 +1,35 @@ +import { getStorage, setStorage } from '@/utils'; + +import { create } from 'zustand'; + +export interface WebdavInfo { + url: string; + name: string; + username: string; + password: string; +} + +export interface UserState { + webdavs: WebdavInfo[]; +} + +export interface UserActions { + addWebdav(webdav: WebdavInfo): void; +} + +const useAppStore = create((set) => ({ + ...getStorage('user', { + webdavs: [] + }), + addWebdav(webdav) { + set((prev) => { + const webdavs = { webdavs: [...prev.webdavs, webdav] }; + + setStorage('user', webdavs); + + return webdavs; + }); + } +})); + +export default useAppStore; diff --git a/src/utils/localStorageTools.ts b/src/utils/localStorageTools.ts index 26c927a..e9fbe96 100644 --- a/src/utils/localStorageTools.ts +++ b/src/utils/localStorageTools.ts @@ -2,7 +2,8 @@ import { merge } from 'lodash-es'; export const KEYS = { /** Website configuration key */ - APP_KEY: 'app' + APP_KEY: 'app', + USER_KEY: 'user' } as const; type TKEYS = (typeof KEYS)[keyof typeof KEYS];