Refactor hotkey setting UI (#506)

* refactor hotkeys setting

* update UI

* refactor

* fix toast
This commit is contained in:
an-lee
2024-04-10 11:54:28 +08:00
committed by GitHub
parent 5b87d218ac
commit 7dfd47bb6d
7 changed files with 163 additions and 140 deletions

View File

@@ -523,7 +523,8 @@
"AiTranslate": "AI translate",
"cambridgeDictionary": "Cambridge dictionary",
"customizeShortcuts": "Customize shortcuts",
"customizeShortcutsTip":"Press any sequence of keys to set a shortcut",
"customizeShortcutsTip":"Click to change",
"customizeShortcutsRecordingTip":"Recording new shortcut",
"customizeShortcutsInvalidToast": "Your shortcut should only have one modifier (Ctrl, Alt, Shift, or Meta) and one key, like 'Ctrl+C'.",
"customizeShortcutsConflictToast": "{{input}} conflicts with the existing {{otherHotkeyName}} shortcut.",
"customizeShortcutsUpdated": "Changes saved",

View File

@@ -522,9 +522,10 @@
"AiTranslate": "智能翻译",
"cambridgeDictionary": "剑桥词典",
"customizeShortcuts": "自定义快捷键",
"customizeShortcutsTip":"按任意键序列设置快捷键",
"customizeShortcutsInvalidToast":"快捷键应最多含一个修饰键Ctrl、Alt、Shift 或 Meta和一个键如 'Ctrl+C'",
"customizeShortcutsConflictToast": "{{input}}和已有{{otherHotkeyName}}的键位冲突了",
"customizeShortcutsTip":"点击重新录制",
"customizeShortcutsRecordingTip":"正在录制快捷键",
"customizeShortcutsInvalidToast":"快捷键应最多含一个修饰键Ctrl, Alt, Shift 或 Meta和一个键如 'Ctrl+C'",
"customizeShortcutsConflictToast": "{{input}} 和已有 {{otherHotkeyName}} 的键位冲突了",
"customizeShortcutsUpdated": "设置成功",
"following": "关注中",
"followers": "被关注",

View File

@@ -1,103 +0,0 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@renderer/components/ui";
import { toast } from "@renderer/components/ui";
import { useContext, useMemo } from "react";
import { HotKeysSettingsProviderContext } from "../context";
import { CheckIcon, KeyboardIcon, XIcon } from "lucide-react";
import { t } from "i18next";
export const ChangeHotkeyDialog = ({
open,
name,
keyName,
onOpenChange,
}: {
open: boolean;
name: string;
keyName: string;
onOpenChange: (open: boolean) => void;
}) => {
const {
changeHotkey,
currentHotkeys,
recordingHotkeys,
resetRecordingHotkeys,
} = useContext(HotKeysSettingsProviderContext);
const joinedKeys = useMemo(() => [...recordingHotkeys].join("+"), [
recordingHotkeys,
]);
const changeKeyMap = async () => {
const ret = ((await changeHotkey(
keyName,
recordingHotkeys
)) as unknown) as {
error: "conflict" | "invalid";
data: string | string[];
input: string;
};
const { error, data, input } = ret ?? {};
if (error === "conflict") {
toast.error(
t("customizeShortcutsConflictToast", {
input,
otherHotkeyName: (data as string[]).join(","),
})
);
} else if (error === "invalid") {
toast.error(t("customizeShortcutsInvalidToast"));
} else {
toast.success(t("customizeShortcutsUpdated"));
}
};
const clear = () => {
resetRecordingHotkeys();
};
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t("customizeShortcuts")}</AlertDialogTitle>
</AlertDialogHeader>
<div>
<p className="pb-4">{name}</p>
<div className="flex items-center">
<p className="inline-block gap-2 border-2 border-black rounded p-[2px] mr-2">
{currentHotkeys[keyName]}
</p>
{joinedKeys.length > 0 ? (
<div className="flex items-center gap-1">
<p className="border-2 border-black rounded p-[2px]">
{joinedKeys}
</p>
<div className="cursor-pointer" onClick={changeKeyMap}>
<CheckIcon className="text-green-500 w-5 h-5" />
</div>
<div className="cursor-pointer" onClick={clear}>
<XIcon className="text-red-500 w-5 h-5" />
</div>
</div>
) : (
<div className="inline-block gap-2 border-2 border-black rounded p-[2px]">
<span className="text-sm">{t("customizeShortcutsTip")}</span>
<KeyboardIcon className="inline ml-1 w-6 h-6 text-muted-foreground" />
</div>
)}
</div>
</div>
<AlertDialogFooter>
<AlertDialogAction>Close</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -0,0 +1,129 @@
import { t } from "i18next";
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
Button,
toast,
} from "@renderer/components/ui";
import { HotKeysSettingsProviderContext } from "@renderer/context";
import { useContext, useMemo, useEffect } from "react";
export const HotkeysSettings = ({
open,
name,
keyName,
onOpenChange,
}: {
open: boolean;
name: string;
keyName: string;
onOpenChange: (open: boolean) => void;
}) => {
const {
changeHotkey,
currentHotkeys,
recordingHotkeys,
resetRecordingHotkeys,
startRecordingHotkeys,
stopRecordingHotkeys,
isRecording,
} = useContext(HotKeysSettingsProviderContext);
const joinedKeys = useMemo(
() => [...recordingHotkeys].join("+"),
[recordingHotkeys]
);
const changeKeyMap = async () => {
const ret = (await changeHotkey(keyName, recordingHotkeys)) as unknown as {
error: "conflict" | "invalid";
data: string | string[];
input: string;
};
stopRecordingHotkeys();
const { error, data, input } = ret ?? {};
if (error === "conflict") {
toast.error(
t("customizeShortcutsConflictToast", {
input,
otherHotkeyName: (data as string[])
.map((str) => t(str.charAt(0).toLowerCase() + str.slice(1)))
.join(","),
})
);
} else if (error === "invalid") {
toast.error(t("customizeShortcutsInvalidToast"));
} else {
toast.success(t("customizeShortcutsUpdated"));
onOpenChange(false);
}
};
const reset = () => {
stopRecordingHotkeys();
resetRecordingHotkeys();
};
// ensure recording disabled when dialog close
useEffect(() => {
return () => {
stopRecordingHotkeys();
};
}, [open]);
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{name}</AlertDialogTitle>
</AlertDialogHeader>
<div>
{isRecording ? (
<div className="">
<div className="flex justify-center mb-4">
<Button variant="secondary">
{joinedKeys.length > 0 ? (
<span className="text-sm">{joinedKeys}</span>
) : (
<span className="font-mono">-</span>
)}
</Button>
</div>
<div className="py-2 text-center text-sm text-muted-foreground">
{t("customizeShortcutsRecordingTip")}
</div>
</div>
) : (
<div className="">
<div className="flex justify-center mb-4">
<Button
variant="outline"
className="font-mono"
onClick={() => {
startRecordingHotkeys();
}}
>
{currentHotkeys[keyName]}
</Button>
</div>
<div className="py-2 text-center text-sm text-muted-foreground">
{t("customizeShortcutsTip")}
</div>
</div>
)}
</div>
<AlertDialogFooter>
<Button disabled={!isRecording || !joinedKeys} onClick={changeKeyMap}>
{t("save")}
</Button>
<AlertDialogCancel onClick={reset}>{t("cancel")}</AlertDialogCancel>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -1,8 +1,10 @@
import { t } from "i18next";
import { Separator } from "@renderer/components/ui";
import { HotKeysSettingsProviderContext, Hotkey } from "@/renderer/context";
import {
Separator,
} from "@renderer/components/ui";
import { HotKeysSettingsProviderContext, Hotkey } from "@renderer/context";
import { HotkeysSettings } from "@renderer/components";
import { useContext, useState } from "react";
import { ChangeHotkeyDialog } from "../change-hotkey-dialog";
export const Hotkeys = () => {
const [open, setOpen] = useState(false);
@@ -10,27 +12,15 @@ export const Hotkeys = () => {
name: string;
keyName: string;
} | null>(null);
const {
currentHotkeys,
startRecordingHotkeys,
stopRecordingHotkeys,
} = useContext(HotKeysSettingsProviderContext);
const { currentHotkeys } = useContext(HotKeysSettingsProviderContext);
const commandOrCtrl = navigator.platform.includes("Mac") ? "Cmd" : "Ctrl";
const handleItemSelected = (item: { name: string; keyName: Hotkey }) => {
setOpen(true);
startRecordingHotkeys();
setSelectedItem(item);
};
const handleOpenChange = (open: boolean) => {
setOpen(open);
if (!open) {
stopRecordingHotkeys();
}
};
return (
<>
<div className="font-semibold mb-4 capitilized">{t("hotkeys")}</div>
@@ -39,8 +29,8 @@ export const Hotkeys = () => {
<div className="flex items-center justify-between py-4">
<div className="flex items-center space-x-2">{t("quitApp")}</div>
<kbd className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-not-allowed">
{commandOrCtrl} + Q
<kbd className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-not-allowed capitalize">
{commandOrCtrl}+Q
</kbd>
</div>
@@ -53,11 +43,11 @@ export const Hotkeys = () => {
<kbd
onClick={() =>
handleItemSelected({
name: "Open preferences",
name: t("openPreferences"),
keyName: "OpenPreferences",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer capitalize"
>
{currentHotkeys.OpenPreferences}
</kbd>
@@ -77,7 +67,7 @@ export const Hotkeys = () => {
keyName: "PlayOrPause",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer capitalize"
>
{currentHotkeys.PlayOrPause}
</kbd>
@@ -96,7 +86,7 @@ export const Hotkeys = () => {
keyName: "StartOrStopRecording",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer capitalize"
>
{currentHotkeys.StartOrStopRecording}
</kbd>
@@ -115,7 +105,7 @@ export const Hotkeys = () => {
keyName: "PlayOrPauseRecording",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer capitalize"
>
{currentHotkeys.PlayOrPauseRecording}
</kbd>
@@ -153,7 +143,7 @@ export const Hotkeys = () => {
keyName: "PlayNextSegment",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer capitalize"
>
{currentHotkeys.PlayNextSegment}
</kbd>
@@ -172,7 +162,7 @@ export const Hotkeys = () => {
keyName: "Compare",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer capitalize"
>
{currentHotkeys.Compare}
</kbd>
@@ -181,12 +171,12 @@ export const Hotkeys = () => {
<Separator />
</div>
<ChangeHotkeyDialog
<HotkeysSettings
open={open}
keyName={selectedItem?.keyName}
name={selectedItem?.name}
onOpenChange={handleOpenChange}
onOpenChange={setOpen}
/>
</>
);
};
};

View File

@@ -1,6 +1,8 @@
export * from "./preferences";
export * from "./about";
export * from "./hotkeys";
export * from "./hotkeys-settings";
export * from "./default-engine-settings";
export * from "./openai-settings";

View File

@@ -47,11 +47,11 @@ const defaultKeyMap = {
OpenPreferences: `${ControlOrCommand}+Comma`,
// player
PlayOrPause: "Space",
StartOrStopRecording: "r",
PlayOrPauseRecording: `${ControlOrCommand}+r`,
PlayPreviousSegment: "p",
PlayNextSegment: "n",
Compare: "c",
StartOrStopRecording: "R",
PlayOrPauseRecording: `${ControlOrCommand}+R`,
PlayPreviousSegment: "P",
PlayNextSegment: "N",
Compare: "C",
// dev tools
OpenDevTools: `${ControlOrCommand}+Shift+I`,
};
@@ -76,6 +76,7 @@ type HotkeysSettingsProviderState = {
currentHotkeys: Record<string, string>;
recordingHotkeys?: any;
enabled: boolean;
isRecording: boolean;
startRecordingHotkeys?: () => void;
stopRecordingHotkeys?: () => void;
resetRecordingHotkeys?: () => void;
@@ -85,6 +86,7 @@ type HotkeysSettingsProviderState = {
const initialState: HotkeysSettingsProviderState = {
currentHotkeys: {},
enabled: true,
isRecording: false,
};
export const HotKeysSettingsProviderContext = createContext<
@@ -215,6 +217,7 @@ export const HotKeysSettingsProvider = ({
currentHotkeys,
recordingHotkeys: keys,
enabled: !isRecording,
isRecording,
startRecordingHotkeys,
stopRecordingHotkeys,
resetRecordingHotkeys: resetKeys,