From 8aa7a040fae27f6dca360ec776591ea193072427 Mon Sep 17 00:00:00 2001 From: yuanyxh <15766118362@139.com> Date: Tue, 7 May 2024 00:17:36 +0800 Subject: [PATCH] refactor: Modify some logic --- src/filehandle/FilePanelFactory.tsx | 21 +-- src/filehandle/components/FileContent.tsx | 105 +++++++----- src/filehandle/components/FileLocation.tsx | 4 +- src/filehandle/components/FilePanel.tsx | 12 +- .../components/FilePanelContainer.tsx | 4 +- .../components/styles/FileContent.module.less | 1 + src/filehandle/hooks/useFileSystem.ts | 111 +++++++----- src/filehandle/index.ts | 7 +- src/filehandle/md_editor/MDHandle.tsx | 2 +- src/filehandle/md_editor/index.tsx | 38 ++--- src/router/index.ts | 1 - src/router/router.ts | 5 - src/utils/index.ts | 13 ++ src/utils/localStorageTools.ts | 4 +- src/viewer/Layout.tsx | 41 +++-- src/viewer/Provider.tsx | 3 + src/viewer/Settings.tsx | 160 +++++++++++------- src/viewer/styles/Layout.module.less | 1 + src/viewer/styles/Provider.module.less | 28 +-- 19 files changed, 325 insertions(+), 236 deletions(-) diff --git a/src/filehandle/FilePanelFactory.tsx b/src/filehandle/FilePanelFactory.tsx index 4e6d11c..7b54545 100644 --- a/src/filehandle/FilePanelFactory.tsx +++ b/src/filehandle/FilePanelFactory.tsx @@ -2,26 +2,12 @@ import { createRoot } from 'react-dom/client'; import { App } from 'antd'; -import FilePanelContainer from './components/FilePanelContainer'; - -const setStyles = (el: HTMLElement) => { - el.style.position = 'relative'; - el.style.zIndex = 'var(--z-gighest)'; - el.style.width = '0'; - el.style.height = '0'; -}; - -const getContainer = () => { - const container = window.document.createElement('div'); - setStyles(container); +import { createElementContainer } from '@/utils'; - window.document.body.appendChild(container); - - return container; -}; +import FilePanelContainer from './components/FilePanelContainer'; class FilePanelFactory { - private container = getContainer(); + private container = createElementContainer(); private root = createRoot(this.container); @@ -85,7 +71,6 @@ class FilePanelFactory { } destroy() { - // window.document.documentElement.removeEventListener('click', onClick, true); this.root.unmount(); Promise.resolve().then(() => this.container.remove()); } diff --git a/src/filehandle/components/FileContent.tsx b/src/filehandle/components/FileContent.tsx index 20bfb7e..62b88ba 100644 --- a/src/filehandle/components/FileContent.tsx +++ b/src/filehandle/components/FileContent.tsx @@ -1,7 +1,8 @@ -import { useContext, useEffect, useMemo, useRef, useState } from 'react'; +import { useContext, useMemo, useRef, useState } from 'react'; import type { InputProps } from 'antd'; -import { App, Input, InputRef, Modal, Typography } from 'antd'; +import type { InputRef } from 'antd'; +import { App, Input, Modal, Typography } from 'antd'; import { ExclamationCircleFilled } from '@ant-design/icons'; import { sleep, validateFileName } from '@/utils'; @@ -10,7 +11,6 @@ import { ContextMenu, Icon } from '@/components'; import { FileSystemContext } from './FilePanel'; import styles from './styles/FileContent.module.less'; -import mdHandler from '../md_editor'; import { isAlwaysExist } from '../utils'; import type { DH, FileInfo } from '../utils/fileManager'; import { @@ -33,6 +33,8 @@ function AddFileModal({ onOk: (name: string, type: FileType) => any; onCancel: () => any; }) { + const { message } = App.useApp(); + const [inputStatus, setInputStatus] = useState<{ name: string; status: InputProps['status']; @@ -47,25 +49,32 @@ function AddFileModal({ useMemo(() => { if (open) { - sleep(100, () => { - inputRef.current?.focus(); - }); + // Delay to make the input box get the focus + sleep(100, () => inputRef.current?.focus()); } }, [open]); const handleInputChange = async (e: React.ChangeEvent) => { const { value } = e.target; - if (value !== '' && !validateFileName(value)) { - setInputStatus({ name: value, status: 'error', message: '无效的文件名' }); - } else if (await isAlwaysExist(current, value)) { - setInputStatus({ - name: value, - status: 'error', - message: '当前目录已包含同名的文件' - }); - } else { - setInputStatus({ name: value, status: '', message: '' }); + try { + if (value !== '' && !validateFileName(value)) { + setInputStatus({ + name: value, + status: 'error', + message: '无效的文件名' + }); + } else if (await isAlwaysExist(current, value)) { + setInputStatus({ + name: value, + status: 'error', + message: '当前目录已包含同名的文件' + }); + } else { + setInputStatus({ name: value, status: '', message: '' }); + } + } catch (err) { + message.error((err as Error).message); } }; @@ -145,7 +154,7 @@ const FileItem: React.FC> = (props) => { }; return ( - + {getIcon(file)} > = (props) => { > {file.name} - + ); }; @@ -163,40 +172,39 @@ interface IFileContentProps { } const FileContent: React.FC = (props) => { - const titleRef = useRef(0); - const sectionRef = useRef(null); - - const [isModalOpen, setModalOpen] = useState(false); - const [selection, setSelection] = useState([]); + const { modal, message } = App.useApp(); const { current, children, fileHandles, - register, enterDirectory, create, remove, forceUpdate } = useContext(FileSystemContext); - const { modal, message } = App.useApp(); - - useEffect(() => { - register(mdHandler); + const titleRef = useRef(0); + const sectionRef = useRef(null); - forceUpdate(); - }, []); + const [isModalOpen, setModalOpen] = useState(false); + const [selection, setSelection] = useState([]); const contextMenu = fileHandles .filter((handle) => handle.contextMenu) .map((handle) => ({ name: handle.contextMenu!.name, icon: handle.contextMenu!.icon, - onClick() { - getHandle(current, selection[0]).then((value) => { - handle.contextMenu!.handler(value); - }); + async onClick() { + try { + let value = current; + if (selection[0]) { + value = (await getHandle(current, selection[0])) as DH; + } + await handle.contextMenu!.handler(value); + } catch (err) { + message.error((err as Error).message); + } } })); @@ -204,6 +212,7 @@ const FileContent: React.FC = (props) => { titleRef.current = FileType.FILE; setModalOpen(true); }; + const handleAddDirectory = () => { titleRef.current = FileType.DIRECTORY; setModalOpen(true); @@ -236,12 +245,16 @@ const FileContent: React.FC = (props) => { }; const open = async (file: FileInfo) => { - const handle = fileHandles.find((handle) => - handle.ext.split(',').some((ext) => ext.trim() === file.ext) - ); + try { + const handle = fileHandles.find((handle) => + handle.ext.split(',').some((ext) => ext.trim() === file.ext) + ); - if (handle) { - handle.open(await current.getFileHandle(file.name)); + if (handle) { + handle.open(await current.getFileHandle(file.name)); + } + } catch (err) { + message.error((err as Error).message); } }; @@ -253,7 +266,11 @@ const FileContent: React.FC = (props) => { icon: , content: '您确认要删除这个文件吗?', async onOk() { - await Promise.all(names.map((name) => remove(name))); + try { + await Promise.all(names.map((name) => remove(name))); + } catch (err) { + message.error((err as Error).message); + } }, okText: '确认', cancelText: '取消' @@ -261,8 +278,12 @@ const FileContent: React.FC = (props) => { }; const handleOk = async (name: string, type: FileType) => { - await create(name, type); - setModalOpen(false); + try { + await create(name, type); + setModalOpen(false); + } catch (err) { + message.error((err as Error).message); + } }; const handleCancel = () => { setModalOpen(false); diff --git a/src/filehandle/components/FileLocation.tsx b/src/filehandle/components/FileLocation.tsx index 399e3ae..cab8f98 100644 --- a/src/filehandle/components/FileLocation.tsx +++ b/src/filehandle/components/FileLocation.tsx @@ -7,9 +7,7 @@ import { Icon } from '@/components'; import { FileSystemContext } from './FilePanel'; import styles from './styles/FileLocation.module.less'; -interface IFileLocationProps {} - -const FileLocation: React.FC> = () => { +const FileLocation: React.FC = () => { const { fileLinked } = useContext(FileSystemContext); return ( diff --git a/src/filehandle/components/FilePanel.tsx b/src/filehandle/components/FilePanel.tsx index 92ff46c..cfc779c 100644 --- a/src/filehandle/components/FilePanel.tsx +++ b/src/filehandle/components/FilePanel.tsx @@ -1,11 +1,13 @@ -import React, { createContext, useRef } from 'react'; +import React, { createContext, useEffect, useRef } from 'react'; -import { Dialog, IDialogExpose, IDialogProps } from '@/components'; +import type { IDialogExpose, IDialogProps } from '@/components'; +import { Dialog } from '@/components'; import FileContent from './FileContent'; import FileLocation from './FileLocation'; import type { FileSystem } from '../hooks/useFileSystem'; import { useFileSystem } from '../hooks/useFileSystem'; +import mdHandler from '../md_editor'; export const FileSystemContext = createContext({} as FileSystem); @@ -15,15 +17,19 @@ const FilePanel: React.FC> = function FilePanel(props) { const dialogRef = useRef(null); const fileSystem = useFileSystem(); + useEffect(() => { + fileSystem.register(mdHandler); + }, []); + return ( { onClose?.(); fileSystem.returnToRoot(); }} + {...rest} > diff --git a/src/filehandle/components/FilePanelContainer.tsx b/src/filehandle/components/FilePanelContainer.tsx index af377e3..e025426 100644 --- a/src/filehandle/components/FilePanelContainer.tsx +++ b/src/filehandle/components/FilePanelContainer.tsx @@ -33,12 +33,12 @@ const FilePanelContainer: React.FC> = ( renderResolveRef.current = resolve; }) ); - }, []); + }, [show, hide]); const onAnimationEnd = useCallback(() => { renderResolveRef.current?.(true); renderResolveRef.current = void 0; - }, []); + }, [show, hide]); const onMinimize = () => { hide(); diff --git a/src/filehandle/components/styles/FileContent.module.less b/src/filehandle/components/styles/FileContent.module.less index 6252271..e9f49f3 100644 --- a/src/filehandle/components/styles/FileContent.module.less +++ b/src/filehandle/components/styles/FileContent.module.less @@ -14,6 +14,7 @@ width: 400px; padding: 4px 14px; font-size: 14px; + cursor: pointer; user-select: none; border-radius: var(--border-radius-base); diff --git a/src/filehandle/hooks/useFileSystem.ts b/src/filehandle/hooks/useFileSystem.ts index a414455..2786604 100644 --- a/src/filehandle/hooks/useFileSystem.ts +++ b/src/filehandle/hooks/useFileSystem.ts @@ -1,7 +1,9 @@ -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { cloneDeep } from 'lodash-es'; +import { AppContext } from '@/App'; + import FileLinkedList from '../FileLinkedList'; import type { DH, FH, FileDataType, FileInfo } from '../utils/fileManager'; import { @@ -31,26 +33,28 @@ export interface FileSystem { fileLinked: FileLinkedList; fileHandles: FileHandle[]; create(name: string, type: FileType, data?: FileDataType): Promise; - remove(name: string): Promise; + remove(name: string): any; move(): void; register(handler: FileHandle): void; returnToRoot(): void; - enterDirectory(name: string): Promise; + enterDirectory(name: string): any; forceUpdate(): any; } export function useFileSystem(): FileSystem { + const { message } = useContext(AppContext); + const root = useRef() as React.MutableRefObject; const fileLinked = useRef() as React.MutableRefObject; + const fileHandlesRef = useRef([]); const [current, setCurrent] = useState() as [ DH, React.Dispatch> ]; const [children, setChildren] = useState([]); - const fileHandleRef = useRef([]); const update = () => { if (!current) return; @@ -66,40 +70,77 @@ export function useFileSystem(): FileSystem { update(); }, [current]); + useMemo(() => { + if (!fileLinked.current) return; + + return fileLinked.current.listener((directory) => { + setCurrent(directory); + }); + }, [fileLinked.current]); + + useEffect(() => { + window.navigator.storage.getDirectory().then((res) => { + setCurrent(res); + root.current = res; + fileLinked.current = new FileLinkedList(res); + }); + }, []); + const fileSystem = useMemo(() => { - function register(handler: FileHandle) { - if (fileHandleRef.current.some((curr) => handler.id === curr.id)) { - return false; - } + function register(handler: FileHandle | FileHandle[]) { + const handlers = handler instanceof Array ? handler : [handler]; + + handlers.forEach((handler) => { + if (fileHandlesRef.current.some((curr) => handler.id === curr.id)) { + return false; + } - fileHandleRef.current.push(cloneDeep(handler)); + fileHandlesRef.current = [ + ...fileHandlesRef.current, + cloneDeep(handler) + ]; + }); + + update(); } function move() {} - async function remove(name: string) { - await removeFile(current, name); - - update(); + function remove(name: string) { + removeFile(current, name) + .then(() => { + update(); + }) + .catch((err) => { + message.error((err as Error).message); + }); } async function create(name: string, type: FileType, data?: FileDataType) { - if (type === FileType.FILE) { - await createFile(current, name, data); - } else { - await createDirectory(current, name); - } + try { + if (type === FileType.FILE) { + await createFile(current, name, data); + } else { + await createDirectory(current, name); + } - update(); + update(); + } catch (err) { + message.error((err as Error).message); + } } - async function enterDirectory(name: string) { - getHandle(current, name).then((res) => { - if (res.kind === 'directory') { - setCurrent(res); - fileLinked.current.inset(res); - } - }); + function enterDirectory(name: string) { + getHandle(current, name) + .then((res) => { + if (res.kind === 'directory') { + setCurrent(res); + fileLinked.current.inset(res); + } + }) + .catch((err) => { + message.error((err as Error).message); + }); } function returnToRoot() { @@ -111,7 +152,7 @@ export function useFileSystem(): FileSystem { current, children, fileLinked: fileLinked.current, - fileHandles: fileHandleRef.current, + fileHandles: fileHandlesRef.current, register, returnToRoot, enterDirectory, @@ -122,21 +163,5 @@ export function useFileSystem(): FileSystem { }; }, [current, children, fileLinked.current]); - useMemo(() => { - if (!fileLinked.current) return; - - return fileLinked.current.listener((directory) => { - setCurrent(directory); - }); - }, [fileLinked.current]); - - useEffect(() => { - window.navigator.storage.getDirectory().then((res) => { - setCurrent(res); - root.current = res; - fileLinked.current = new FileLinkedList(res); - }); - }, []); - return fileSystem; } diff --git a/src/filehandle/index.ts b/src/filehandle/index.ts index da9a41d..9f6900e 100644 --- a/src/filehandle/index.ts +++ b/src/filehandle/index.ts @@ -1,12 +1,7 @@ import { default as FilePanelFactoryInner } from './FilePanelFactory'; -export * from './components/FilePanel'; -export * from './components/FilePanelContainer'; -export * from './hooks/useFileSystem'; -export * from './utils'; +export { getStorageUsage } from './utils'; export * from './utils/checkSupport'; -export * from './utils/error'; -export * from './utils/fileManager'; export type FilePanelFactory = FilePanelFactoryInner; diff --git a/src/filehandle/md_editor/MDHandle.tsx b/src/filehandle/md_editor/MDHandle.tsx index 5bce009..aff2195 100644 --- a/src/filehandle/md_editor/MDHandle.tsx +++ b/src/filehandle/md_editor/MDHandle.tsx @@ -4,7 +4,7 @@ import { Dialog } from '@/components'; import MDEditor from './component/MDEditor'; import styles from './styles/MDHandle.module.less'; -import { DH, FH } from '../utils/fileManager'; +import type { DH, FH } from '../utils/fileManager'; interface IMDHandle { handle: DH | FH; diff --git a/src/filehandle/md_editor/index.tsx b/src/filehandle/md_editor/index.tsx index a7c6f00..aef9858 100644 --- a/src/filehandle/md_editor/index.tsx +++ b/src/filehandle/md_editor/index.tsx @@ -1,28 +1,26 @@ import { createRoot } from 'react-dom/client'; +import { createElementContainer } from '@/utils'; + import { Icon } from '@/components'; import type { FileHandle } from '../hooks/useFileSystem'; import type { DH, FH } from '../utils/fileManager'; -function createMDHandleInstance(handle: DH | FH) { - import('./MDHandle').then(({ default: MDHandle }) => { - const el = window.document.createElement('div'); - - el.style.position = 'relative'; - el.style.zIndex = 'var(--z-gighest)'; - - window.document.body.appendChild(el); - - const root = createRoot(el); - - const destroy = () => { - root.unmount(); - el.remove(); - }; - - root.render(); - }); +async function createMDHandleInstance(handle: DH | FH) { + const { default: MDHandle } = await import('./MDHandle'); + + const el = createElementContainer(); + const root = createRoot(el); + const destroy = () => { + root.unmount(); + el.remove(); + }; + root.render(); + // .catch((err) => { + // // TODO: add global error sender + // window.alert((err as Error).message); + // }); } const mdHandler: FileHandle = { @@ -30,13 +28,13 @@ const mdHandler: FileHandle = { ext: '.md', icon: , open(file) { - createMDHandleInstance(file); + return createMDHandleInstance(file); }, contextMenu: { name: '用 MD 处理器打开', icon: , handler(file) { - createMDHandleInstance(file); + return createMDHandleInstance(file); } } }; diff --git a/src/router/index.ts b/src/router/index.ts index 046f54b..b911a13 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -10,7 +10,6 @@ export { useScrollStore } from './hooks/useScrollStore'; export type { ComponentModule, LazyComponent, - Meta, NavigateOptions, ResolveRouteObject, RouteObject, diff --git a/src/router/router.ts b/src/router/router.ts index 7d9f50b..b4f3f7b 100644 --- a/src/router/router.ts +++ b/src/router/router.ts @@ -16,10 +16,6 @@ interface ComponentModule { type LazyComponent = (props?: any) => Promise; -interface Meta { - title: string; -} - interface ArticleMeta { title: string; date: string; @@ -417,7 +413,6 @@ export default Router; export type { ComponentModule, LazyComponent, - Meta, Module, NavigateOptions, ResolveRouteObject, diff --git a/src/utils/index.ts b/src/utils/index.ts index 3d1baa1..c7c7870 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -33,3 +33,16 @@ export const validateFileName = (name: string) => { return /^[^\\/:*?'`"<>|]{0,255}[^\\/:*?"`<>|.']$/.test(name.trim()); }; + +export const createElementContainer = () => { + const container = window.document.createElement('div'); + + container.style.position = 'relative'; + container.style.zIndex = 'var(--z-gighest)'; + container.style.width = '0'; + container.style.height = '0'; + + window.document.body.appendChild(container); + + return container; +}; diff --git a/src/utils/localStorageTools.ts b/src/utils/localStorageTools.ts index 7372022..26c927a 100644 --- a/src/utils/localStorageTools.ts +++ b/src/utils/localStorageTools.ts @@ -1,3 +1,5 @@ +import { merge } from 'lodash-es'; + export const KEYS = { /** Website configuration key */ APP_KEY: 'app' @@ -40,7 +42,7 @@ export function getStorage(key: TKEYS, fallback?: T): T | undefined { } try { - return window.JSON.parse(value); + return merge({}, fallback, window.JSON.parse(value)); } catch (err) { return fallback; } diff --git a/src/viewer/Layout.tsx b/src/viewer/Layout.tsx index 08e7144..7b486fc 100644 --- a/src/viewer/Layout.tsx +++ b/src/viewer/Layout.tsx @@ -14,6 +14,7 @@ import { import { CommentOutlined } from '@ant-design/icons'; import classnames from 'classnames'; +import classNames from 'classnames'; import { AuthType, createClient } from 'webdav'; import type { IOutletRef } from '@/router'; @@ -42,18 +43,17 @@ import languageData from './data/language.json'; import navbarData from './data/navbar.json'; import styles from './styles/Layout.module.less'; -const active = styles.active; - const Logo = () => { return ( logo - {import.meta.env.VITE_APP_TITLE} +

{import.meta.env.VITE_APP_TITLE}

); }; +// TODO: add serach const SearchWrap = (props: { onFocus: () => void; onBlur: () => void }) => { return ( <> @@ -89,7 +89,7 @@ const TopNavbar = () => {
    {navbarData.map((item) => (
  • - + {item.label}
  • @@ -103,9 +103,11 @@ const TopNavbar = () => { const SideNavbar = (props: { visible: boolean; onClick: () => void }) => { const sideContainerActiveClass = classnames(styles.sideContainer, { - [active]: props.visible + [styles.active]: props.visible + }); + const maskActiveClass = classnames(styles.mask, { + [styles.active]: props.visible }); - const maskActiveClass = classnames(styles.mask, { [active]: props.visible }); return ( <> @@ -125,7 +127,11 @@ const SideNavbar = (props: { visible: boolean; onClick: () => void }) => {
      {navbarData.map((item) => (
    • - + {item.label}
    • @@ -239,7 +245,7 @@ const Actions = () => { = ({ visible, onChange }) => { }; const FileSystemTrigger = () => { + const { message } = useContext(AppContext); + const [support, setSupport] = useState(false); const filePanelRef = useRef(); @@ -424,12 +432,16 @@ const FileSystemTrigger = () => { const handleOpenFilePanel = () => { if (!filePanelRef.current) { - return import('@/filehandle').then(({ default: filePanel }) => { - filePanelRef.current = filePanel; - - // await render mounted - sleep(0, () => filePanelRef.current?.toggle()); - }); + return import('@/filehandle') + .then(({ default: filePanel }) => { + filePanelRef.current = filePanel; + + // await render mounted + sleep(0, () => filePanelRef.current?.toggle()); + }) + .catch((err) => { + message.error((err as Error).message); + }); } filePanelRef.current?.toggle(); @@ -499,7 +511,6 @@ const ViewerLayout = () => {
      - {/* TIPS: main wrapper */}
      ); diff --git a/src/viewer/Provider.tsx b/src/viewer/Provider.tsx index 77d8574..d7f93bd 100644 --- a/src/viewer/Provider.tsx +++ b/src/viewer/Provider.tsx @@ -19,6 +19,7 @@ interface Toc { attributes: Attributes; children: Toc[]; } + interface Attributes { id?: string; } @@ -144,6 +145,8 @@ function calcImageSize({ containerWidth: number; // containerHeight: number; }) { + containerWidth = containerWidth > 648 ? 648 : containerWidth; + if ([width, height, containerWidth].includes(0)) { return { width: 0, height: 0 }; } diff --git a/src/viewer/Settings.tsx b/src/viewer/Settings.tsx index 47fcda0..951d034 100644 --- a/src/viewer/Settings.tsx +++ b/src/viewer/Settings.tsx @@ -12,7 +12,8 @@ import { import { getStorageUsage } from '@/filehandle'; -import { Canvas, CanvasInstance } from '@/components'; +import type { CanvasInstance } from '@/components'; +import { Canvas } from '@/components'; import { AppContext } from '@/App'; @@ -23,13 +24,15 @@ const { Paragraph, Text } = Typography; const isLocal = import.meta.env.PROD === false; function ServiceWorkerCache() { + const { message } = useContext(AppContext); + const { settings: { enableServiceWorkerCache }, setEnableServiceWorkerCache } = useAppStore(); const [spinning, setSpinning] = useState(false); - const { message } = useContext(AppContext); + const serviceWorkerRef = useRef(new ServiceWorkerManager()); const handleSwitchServiceWorkerCache = ( @@ -49,19 +52,27 @@ function ServiceWorkerCache() { : '无法启用 service worker 缓存,您可联系作者反馈。'; message[installed ? 'success' : 'error'](info); + }) + .catch((err) => { + message.error((err as Error).message); }); } - serviceWorkerRef.current.unregisterServiceWorker().then((uninstalled) => { - setSpinning(false); - uninstalled && setEnableServiceWorkerCache(enableServiceWorkerCache); + serviceWorkerRef.current + .unregisterServiceWorker() + .then((uninstalled) => { + setSpinning(false); + uninstalled && setEnableServiceWorkerCache(enableServiceWorkerCache); - const info = uninstalled - ? '已为您禁用 service worker 缓存' - : '无法禁用 service worker 缓存,您可联系作者反馈。'; + const info = uninstalled + ? '已为您禁用 service worker 缓存' + : '无法禁用 service worker 缓存,您可联系作者反馈。'; - message[uninstalled ? 'success' : 'error'](info); - }); + message[uninstalled ? 'success' : 'error'](info); + }) + .catch((err) => { + message.error((err as Error).message); + }); }; return ( @@ -90,18 +101,23 @@ function ServiceWorkerCache() { } function WebNotification() { + const { message } = useContext(AppContext); + const { settings: { enableNotification }, setEnableNotification } = useAppStore(); const [spinning, setSpinning] = useState(false); - const { message } = useContext(AppContext); useEffect(() => { - hasPermission('notifications').then((value) => { - setEnableNotification(value); - }); + hasPermission('notifications') + .then((value) => { + setEnableNotification(value); + }) + .catch((err) => { + message.error((err as Error).message); + }); }, []); const handleSwitchWebNotification = (enableNotification: boolean) => { @@ -122,6 +138,9 @@ function WebNotification() { return true; } }) + .catch((err) => { + message.error((err as Error).message); + }) .finally(() => { setSpinning(false); }); @@ -150,12 +169,12 @@ function WebNotification() { } function PersistentStorage() { - const [enablePersistent, setEnablePersistentStorage] = useState(false); const { message } = useContext(AppContext); + const [enablePersistent, setEnablePersistentStorage] = useState(false); const handleEnablePersistent = (enablePersistent: boolean) => { if (enablePersistent) { - window.navigator.storage + window.navigator?.storage .persist() .then((persistent) => { setEnablePersistentStorage(persistent); @@ -164,8 +183,8 @@ function PersistentStorage() { message.error('暂无法获取持久化存储权限。'); } }) - .catch(() => { - message.error('暂无法获取持久化存储权限。'); + .catch((err) => { + message.error((err as Error).message); setEnablePersistentStorage(false); }); @@ -173,9 +192,14 @@ function PersistentStorage() { }; useEffect(() => { - window.navigator.storage.persisted().then((persistent) => { - setEnablePersistentStorage(persistent); - }); + window.navigator?.storage + .persisted() + .then((persistent) => { + setEnablePersistentStorage(persistent); + }) + .catch((err) => { + message.error((err as Error).message); + }); }, []); return ( @@ -204,7 +228,10 @@ function PersistentStorage() { } function StorageDetail() { + const { message } = useContext(AppContext); + const drawRef = useRef(null); + const [storage, setStorage] = useState<{ quota: number; usage: number; @@ -216,50 +243,55 @@ function StorageDetail() { }); useEffect(() => { - getStorageUsage().then((res) => { - if (!(typeof res.quota === 'number' && typeof res.usage === 'number')) { - return false; - } - - const { quota, usage } = res; - const unUsage = quota - usage; - - setStorage({ - quota: quota / 1000 / 1000, - usage: usage / 1000 / 1000, - unUsage: unUsage / 1000 / 1000 - }); + getStorageUsage() + .then((res) => { + if (!(typeof res.quota === 'number' && typeof res.usage === 'number')) { + return false; + } - const usageAngle = Math.PI * 2 * (res.usage / res.quota); - const unUsageAngle = Math.PI * 2 * ((res.quota - res.usage) / res.quota); - - const drawEl = drawRef.current!; - - const centerX = drawEl.width() / 2; - const centerY = drawEl.height() / 2; - - drawEl.arc(centerX, centerY, centerX - 2, 0, Math.PI * 2); - drawEl.strokeStyle('#f0f0f0'); - drawEl.stroke(); - - drawEl.beginPath(); - drawEl.moveTo(centerX, centerY); - drawEl.arc(centerX, centerY, centerX - 2, 0, usageAngle); - drawEl.fillStyle('#ff9759'); - drawEl.fill(); - - drawEl.beginPath(); - drawEl.moveTo(centerX, centerY); - drawEl.arc( - centerX, - centerY, - centerX - 2, - usageAngle, - usageAngle + unUsageAngle - ); - drawEl.fillStyle('white'); - drawEl.fill(); - }); + const { quota, usage } = res; + const unUsage = quota - usage; + + setStorage({ + quota: quota / 1000 / 1000, + usage: usage / 1000 / 1000, + unUsage: unUsage / 1000 / 1000 + }); + + const usageAngle = Math.PI * 2 * (res.usage / res.quota); + const unUsageAngle = + Math.PI * 2 * ((res.quota - res.usage) / res.quota); + + const drawEl = drawRef.current!; + + const centerX = drawEl.width() / 2; + const centerY = drawEl.height() / 2; + + drawEl.arc(centerX, centerY, centerX - 2, 0, Math.PI * 2); + drawEl.strokeStyle('#f0f0f0'); + drawEl.stroke(); + + drawEl.beginPath(); + drawEl.moveTo(centerX, centerY); + drawEl.arc(centerX, centerY, centerX - 2, 0, usageAngle); + drawEl.fillStyle('#ff9759'); + drawEl.fill(); + + drawEl.beginPath(); + drawEl.moveTo(centerX, centerY); + drawEl.arc( + centerX, + centerY, + centerX - 2, + usageAngle, + usageAngle + unUsageAngle + ); + drawEl.fillStyle('white'); + drawEl.fill(); + }) + .catch((err) => { + message.error((err as Error).message); + }); }, []); return ( diff --git a/src/viewer/styles/Layout.module.less b/src/viewer/styles/Layout.module.less index f867997..78e7178 100644 --- a/src/viewer/styles/Layout.module.less +++ b/src/viewer/styles/Layout.module.less @@ -18,6 +18,7 @@ } > .title { + display: inline; margin-left: 10px; font-size: 1.125rem; font-weight: 500; diff --git a/src/viewer/styles/Provider.module.less b/src/viewer/styles/Provider.module.less index 53f2d61..8e660c6 100644 --- a/src/viewer/styles/Provider.module.less +++ b/src/viewer/styles/Provider.module.less @@ -214,18 +214,6 @@ code { } } - &:hover { - .copy { - visibility: visible; - opacity: 1; - } - } - - &::-webkit-scrollbar { - width: 4px; - height: 4px; - } - + ul { margin-top: 40px; } @@ -235,6 +223,22 @@ code { } } + pre + pre { + margin-top: 30px; + } + + pre:hover { + .copy { + visibility: visible; + opacity: 1; + } + } + + pre::-webkit-scrollbar { + width: 4px; + height: 4px; + } + blockquote { padding: 0.25rem 0 0.25rem 1rem; margin: 25px 0;