import { ChevronRightIcon } from "lucide-react" import { useEffect, useState, type KeyboardEvent as ReactKeyboardEvent } from "react" import { useNavigate } from "@tanstack/react-router" import type { TFunction } from "i18next" import { useTranslation } from "react-i18next" import { toast } from "sonner" import { settingsAdapter } from "@/adapters/settings" import { AppMainCard } from "@/components/common/app-main-card" import { MainCardHeader } from "@/components/common/main-card-header" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { ScrollArea } from "@/components/ui/scroll-area" import { Switch } from "@/components/ui/switch" import { cn } from "@/lib/utils" import { appActions, settingsStoreActions, useAppStore, useSettingsStore, } from "@/store" import { buildShortcutFromKeyboardEvent, } from "./shortcut-key-binding" import { ShortcutEditDialog } from "./shortcut-edit-dialog" import { ShortcutKbdList } from "./shortcut-kbd-list" import { SettingsSidebar } from "./settings-sidebar" import { allSettingsSections, buildSettingsSearchItems, buildSettingsShortcutItems, buildShortcutValue, defaultSettingsConfig, filterSettingsSectionsBySearch, parseShortcutKeys, settingsSectionId, type SettingsShortcutItem, } from "./utils" function resolveErrorMessage(error: unknown, fallback: string) { if (error instanceof Error && error.message.trim().length > 0) { return error.message } return fallback } function resolveSourceName(shortcut: SettingsShortcutItem) { if (shortcut.from.startsWith("picgo-plugin-")) { return shortcut.from.replace("picgo-plugin-", "") } return "PicGo" } function isBuiltinShortcut(shortcut: SettingsShortcutItem) { return shortcut.from === "picgo" } function resolveShortcutLabel( shortcut: SettingsShortcutItem, t: TFunction ) { if (shortcut.from === "picgo") { if (shortcut.name === "upload") { return t("QUICK_UPLOAD") } if (shortcut.name === "open-main-window") { return t("OPEN_MAIN_WINDOW") } } return shortcut.label } export function PicGoSettingsShortcuts() { const navigate = useNavigate() const { t } = useTranslation() const providers = useAppStore.use.providers() const appConfig = useAppStore.use.appConfig() const settingsSearchValue = useSettingsStore.use.searchValue() const settingsConfig = appConfig?.settings ?? defaultSettingsConfig const picBedProxy = appConfig?.picBed.proxy ?? "" const [editingShortcutId, setEditingShortcutId] = useState(null) const [draftShortcutValue, setDraftShortcutValue] = useState("") const [isCaptureActive, setIsCaptureActive] = useState(false) const shortcutItems = buildSettingsShortcutItems(settingsConfig.shortKey) const searchItems = buildSettingsSearchItems( settingsConfig, providers, picBedProxy ) const { matchedSections, matchedItemIds, hasQuery } = filterSettingsSectionsBySearch( allSettingsSections, searchItems, settingsSearchValue ) useEffect(() => { let isDisposed = false async function bootstrap() { try { await appActions.ensureHydrated() await appActions.ensureSettingsHydrated() } catch (error) { if (!isDisposed) { toast.error(resolveErrorMessage(error, "Failed")) } } } bootstrap() return () => { isDisposed = true } }, []) useEffect(() => { settingsAdapter.toggleShortcutModifiedMode(editingShortcutId !== null) return () => { settingsAdapter.toggleShortcutModifiedMode(false) } }, [editingShortcutId]) const openEditDialog = (shortcut: SettingsShortcutItem) => { setEditingShortcutId(shortcut.id) setDraftShortcutValue(shortcut.key) setIsCaptureActive(false) } const closeEditDialog = () => { setEditingShortcutId(null) setDraftShortcutValue("") setIsCaptureActive(false) } const handleActivateCapture = () => { setDraftShortcutValue("") setIsCaptureActive(true) } const handleCaptureKeyDown = ( event: ReactKeyboardEvent ) => { event.preventDefault() const keys = buildShortcutFromKeyboardEvent(event.nativeEvent) setDraftShortcutValue(buildShortcutValue(keys.slice(0, 4))) setIsCaptureActive(false) } const handleSaveShortcut = async () => { if (!editingShortcutId) { return } try { const keys = parseShortcutKeys(draftShortcutValue).slice(0, 4) if (keys.length === 0) { toast.error(t("FAILED")) return } await settingsStoreActions.updateShortcutKeys(editingShortcutId, keys) toast.success(t("TIPS_SHORTCUT_MODIFIED_SUCCEED")) closeEditDialog() } catch (error) { toast.error(resolveErrorMessage(error, t("FAILED"))) } } const handleToggleShortcut = async (shortcutId: string, nextEnabled: boolean) => { try { await settingsStoreActions.setShortcutEnabled(shortcutId, nextEnabled) toast.success(t("TIPS_SHORTCUT_MODIFIED_SUCCEED")) } catch (error) { toast.error(resolveErrorMessage(error, t("FAILED"))) } } return ( <> navigate({ to: "/main/settings/settings", search: (prev) => ({ ...prev, section, }), }) } onNavigateRoute={(to) => navigate({ to })} />
{t("SETTINGS")} {t("SETTINGS_OPEN_SHORTCUTS")} } />
{hasQuery && !matchedItemIds.has("shortcuts-entry") ? (
{t("SETTINGS_NO_RESULTS_TITLE")}

{t("SETTINGS_NO_RESULTS_DESCRIPTION")}

) : (
{shortcutItems.map((shortcut) => { const label = resolveShortcutLabel(shortcut, t) const sourceName = resolveSourceName(shortcut) const isBuiltin = isBuiltinShortcut(shortcut) return (
{label}
{sourceName}
{ await handleToggleShortcut( shortcut.id, checked === true ) }} /> {shortcut.enable ? t("SHORTCUT_ENABLED") : t("SHORTCUT_DISABLED")}
) })}
)}
{ await handleSaveShortcut() }} onCaptureActivate={handleActivateCapture} onCaptureBlur={() => setIsCaptureActive(false)} onCaptureKeyDown={handleCaptureKeyDown} /> ) }