import { useState } from "react" import { useTranslation } from "react-i18next" import { AlertCircleIcon, CheckCircle2Icon, ChevronDownIcon, FolderSearch2Icon, LoaderCircleIcon, SearchIcon, WrenchIcon, } from "lucide-react" import { AppMainCard } from "@/components/common/app-main-card" import { toolboxPageAdapter } from "@/adapters/toolbox-page" import { useIPCOn } from "@/hooks/useIPC" import { UtilityWindowLayout } from "@/components/independent-window/utility-window-layout" import { resolveIndependentWindowErrorMessage } from "@/components/independent-window/utils" import { Button } from "@/components/ui/button" import { ScrollArea } from "@/components/ui/scroll-area" import { cn } from "@/lib/utils" import { IRPCActionType, IToolboxItemCheckStatus, IToolboxItemType } from "~/universal/types/enum" type ToolboxHandlerTextKey = "SETTINGS_OPEN_CONFIG_FILE" | "OPEN_FILE_PATH" type ToolboxTitleKey = | "TOOLBOX_CHECK_CONFIG_FILE_BROKEN" | "TOOLBOX_CHECK_ALBUM_FILE_BROKEN" | "TOOLBOX_CHECK_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD" | "TOOLBOX_CHECK_PROBLEM_WITH_PROXY" interface ToolboxItemState { type: IToolboxItemType titleKey: ToolboxTitleKey status: IToolboxItemCheckStatus messageKey: string messageVariables?: Record value: string hasNoFixMethod?: boolean handlerTextKey?: ToolboxHandlerTextKey } function ToolboxStatusIcon({ status, }: { status: ToolboxItemState["status"] }) { if (status === IToolboxItemCheckStatus.LOADING) { return } if (status === IToolboxItemCheckStatus.SUCCESS) { return } if (status === IToolboxItemCheckStatus.ERROR) { return } return } function applyToolboxResult( currentItems: ToolboxItemState[], result: IToolboxCheckRes ): ToolboxItemState[] { return currentItems.map((item) => { if (item.type !== result.type) { return item } return { ...item, status: result.status, messageKey: result.msg ?? "", value: typeof result.value === "string" ? result.value : "", } }) } export function PicGoToolboxPage() { const { t } = useTranslation() const [items, setItems] = useState([ { type: IToolboxItemType.IS_CONFIG_FILE_BROKEN, titleKey: "TOOLBOX_CHECK_CONFIG_FILE_BROKEN", status: IToolboxItemCheckStatus.INIT, messageKey: "", value: "", handlerTextKey: "SETTINGS_OPEN_CONFIG_FILE", }, { type: IToolboxItemType.IS_ALBUM_FILE_BROKEN, titleKey: "TOOLBOX_CHECK_ALBUM_FILE_BROKEN", status: IToolboxItemCheckStatus.INIT, messageKey: "", value: "", }, { type: IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD, titleKey: "TOOLBOX_CHECK_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD", status: IToolboxItemCheckStatus.INIT, messageKey: "", value: "", handlerTextKey: "OPEN_FILE_PATH", }, { type: IToolboxItemType.HAS_PROBLEM_WITH_PROXY, titleKey: "TOOLBOX_CHECK_PROBLEM_WITH_PROXY", status: IToolboxItemCheckStatus.INIT, messageKey: "", value: "", hasNoFixMethod: true, } ]) const [activeType, setActiveType] = useState(null) const [isChecking, setIsChecking] = useState(false) const [isFixing, setIsFixing] = useState(false) useIPCOn(IRPCActionType.TOOLBOX_CHECK_RES, (result: IToolboxCheckRes) => { applyResult(result) if (result.status === IToolboxItemCheckStatus.ERROR) { setActiveType(result.type) } }) const progress = items.length === 0 ? 0 : Math.round( (items.filter( (item) => item.status !== IToolboxItemCheckStatus.INIT && item.status !== IToolboxItemCheckStatus.LOADING ).length / items.length) * 100 ) const isAllSuccess = items.length > 0 && items.every((item) => item.status === IToolboxItemCheckStatus.SUCCESS) const canFixItems = items.filter( (item) => item.status === IToolboxItemCheckStatus.ERROR && item.hasNoFixMethod !== true ) const canFixLength = canFixItems.length const applyResult = (result: IToolboxCheckRes) => { setItems((currentItems) => applyToolboxResult(currentItems, result)) } const handleCheck = async () => { if (items.length === 0 || isChecking || isFixing) { return } setActiveType(null) setIsChecking(true) setItems((currentItems) => currentItems.map((item) => ({ ...item, status: IToolboxItemCheckStatus.LOADING, messageKey: "", messageVariables: undefined, value: "", })) ) try { toolboxPageAdapter.runCheck() console.info("[toolbox-page] toolbox scan completed") } catch (error) { console.error( `[toolbox-page] toolbox scan failed: ${resolveIndependentWindowErrorMessage(error, t("FAILED"))}` ) } finally { setIsChecking(false) } } const handleFix = async () => { if (canFixLength === 0 || isFixing || isChecking) { return } setIsFixing(true) const fixTypes = canFixItems.map((item) => item.type) try { for (const type of fixTypes) { const result = await toolboxPageAdapter.fixItem(type) applyResult(result) } console.info("[toolbox-page] toolbox fix completed") const shouldRestartNow = window.confirm(t("TOOLBOX_FIX_DONE_NEED_RELOAD")) if (shouldRestartNow) { toolboxPageAdapter.reloadApp() console.info("[toolbox-page] restart confirmed") } else { console.info("[toolbox-page] restart skipped") } } catch (error) { console.error( `[toolbox-page] toolbox fix failed: ${resolveIndependentWindowErrorMessage(error, t("FAILED"))}` ) } finally { setIsFixing(false) } } const handleOpenPath = async (path: string) => { try { toolboxPageAdapter.openFile(path) console.info(`[toolbox-page] opened path: ${path}`) } catch (error) { console.error( `[toolbox-page] open path failed: ${resolveIndependentWindowErrorMessage(error, t("FAILED"))}` ) } } const renderPrimaryAction = () => { if (progress !== 100) { return ( ) } if (isAllSuccess) { return (

{t("TOOLBOX_SUCCESS_TIPS")}

) } if (canFixLength !== 0) { return ( ) } return (
{t("TOOLBOX_CANT_AUTO_FIX")}
) } return (
PicGo Toolbox

{t("TOOLBOX_TITLE")}

{t("TOOLBOX_SUB_TITLE")}

{renderPrimaryAction()}
{items.map((item) => { const expanded = activeType === item.type return (
{expanded ? (

{item.messageKey ? t(item.messageKey as never, item.messageVariables) : ""}

{item.handlerTextKey && item.value ? ( ) : null}
) : null}
) })}
) }