Feat/custom hotkeys (#496)

* feat: 2024-04-07 15:27:52 - custom hotkeys

* feat: 2024-04-07 15:52:13 - add custome compare shortcuts

* feat: 2024-04-09 10:17:23 - Modify the code according to the code review suggestions and optimize the experience.

---------

Co-authored-by: more.tai <more.tai@huolala.cn>
This commit is contained in:
slarkvan
2024-04-09 14:52:51 +08:00
committed by GitHub
parent 0a6be17f04
commit 5b87d218ac
13 changed files with 521 additions and 64 deletions

View File

@@ -300,7 +300,7 @@
"deleteRecording": "delete recording",
"deleteRecordingConfirmation": "Are you sure to delete this recording?",
"myRecordings": "recordings",
"noRecordingForThisSegmentYet": "No recordings for this segment yet. Press <kbd>R</kbd> to start recording.",
"noRecordingForThisSegmentYet": "No recordings for this segment yet. Press <kbd>{{key}}</kbd> to start recording.",
"lastYear": "last year",
"less": "less",
"more": "more",
@@ -522,6 +522,11 @@
"AiDictionary": "AI dictionary",
"AiTranslate": "AI translate",
"cambridgeDictionary": "Cambridge dictionary",
"customizeShortcuts": "Customize shortcuts",
"customizeShortcutsTip":"Press any sequence of keys to set a 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",
"following": "following",
"followers": "followers",
"allUsers": "all users",

View File

@@ -521,6 +521,11 @@
"AiDictionary": "智能词典",
"AiTranslate": "智能翻译",
"cambridgeDictionary": "剑桥词典",
"customizeShortcuts": "自定义快捷键",
"customizeShortcutsTip":"按任意键序列设置快捷键",
"customizeShortcutsInvalidToast":"快捷键应最多含一个修饰键Ctrl、Alt、Shift 或 Meta和一个键如 'Ctrl+C'",
"customizeShortcutsConflictToast": "{{input}}和已有{{otherHotkeyName}}的键位冲突了",
"customizeShortcutsUpdated": "设置成功",
"following": "关注中",
"followers": "被关注",
"allUsers": "所有用户",

View File

@@ -34,7 +34,7 @@ const libraryPath = () => {
settings.setSync(
"library",
process.env.LIBRARY_PATH ||
path.join(app.getPath("documents"), LIBRARY_PATH_SUFFIX)
path.join(app.getPath("documents"), LIBRARY_PATH_SUFFIX)
);
} else if (path.parse(_library).base !== LIBRARY_PATH_SUFFIX) {
settings.setSync("library", path.join(_library, LIBRARY_PATH_SUFFIX));
@@ -92,6 +92,7 @@ const userDataPath = () => {
return userData;
};
export default {
registerIpcHandlers: () => {
ipcMain.handle("settings-get-library", (_event) => {
@@ -152,6 +153,14 @@ export default {
ipcMain.handle("settings-set-default-engine", (_event, engine) => {
return settings.setSync("defaultEngine", engine);
});
ipcMain.handle("settings-get-default-hotkeys", (_event) => {
return settings.getSync("defaultHotkeys");
});
ipcMain.handle("settings-set-default-hotkeys", (_event, records) => {
return settings.setSync("defaultHotkeys", records);
});
},
cachePath,
libraryPath,

View File

@@ -172,6 +172,12 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", {
switchLanguage: (language: string) => {
return ipcRenderer.invoke("settings-switch-language", language);
},
getDefaultHotkeys: () => {
return ipcRenderer.invoke("settings-get-default-hotkeys");
},
setDefaultHotkeys: (records: Record<string, string>) => {
return ipcRenderer.invoke("settings-set-default-hotkeys", records);
},
},
path: {
join: (...paths: string[]) => {

View File

@@ -3,12 +3,12 @@ import {
AISettingsProvider,
AppSettingsProvider,
DbProvider,
HotKeysSettingsProvider,
} from "@renderer/context";
import router from "./router";
import { RouterProvider } from "react-router-dom";
import { Toaster, toast } from "@renderer/components/ui";
import { Tooltip } from "react-tooltip";
import { useHotkeys } from "react-hotkeys-hook";
function App() {
window.__ENJOY_APP__.onNotification((_event, notification) => {
@@ -31,32 +31,18 @@ function App() {
}
});
const ControlOrCommand = navigator.platform.includes("Mac")
? "Meta"
: "Control";
useHotkeys(`${ControlOrCommand}+Comma`, () => {
document.getElementById("preferences-button")?.click();
});
useHotkeys(`${ControlOrCommand}+Q`, () => {
window.__ENJOY_APP__.app.quit();
});
useHotkeys(`${ControlOrCommand}+Shift+I`, () => {
window.__ENJOY_APP__.app.openDevTools();
});
return (
<ThemeProvider defaultTheme="light" storageKey="vite-ui-theme">
<AppSettingsProvider>
<AISettingsProvider>
<DbProvider>
<RouterProvider router={router} />
<Toaster richColors position="top-center" />
<Tooltip id="global-tooltip" />
</DbProvider>
</AISettingsProvider>
<HotKeysSettingsProvider>
<AISettingsProvider>
<DbProvider>
<RouterProvider router={router} />
<Toaster richColors position="top-center" />
<Tooltip id="global-tooltip" />
</DbProvider>
</AISettingsProvider>
</HotKeysSettingsProvider>
</AppSettingsProvider>
</ThemeProvider>
);

View File

@@ -0,0 +1,103 @@
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

@@ -1,6 +1,7 @@
import { useEffect, useContext, useRef, useState } from "react";
import {
AppSettingsProviderContext,
HotKeysSettingsProviderContext,
MediaPlayerProviderContext,
} from "@renderer/context";
import { MediaRecorder, RecordingDetail } from "@renderer/components";
@@ -60,6 +61,9 @@ export const MediaCurrentRecording = () => {
currentTime: mediaCurrentTime,
} = useContext(MediaPlayerProviderContext);
const { webApi, EnjoyApp } = useContext(AppSettingsProviderContext);
const { enabled, currentHotkeys } = useContext(
HotKeysSettingsProviderContext
);
const [player, setPlayer] = useState(null);
const [regions, setRegions] = useState<Regions | null>(null);
const [currentTime, setCurrentTime] = useState(0);
@@ -328,7 +332,7 @@ export const MediaCurrentRecording = () => {
}
const subscriptions = [
regions.on("region-created", () => { }),
regions.on("region-created", () => {}),
regions.on("region-clicked", (region, e) => {
e.stopPropagation();
@@ -399,7 +403,7 @@ export const MediaCurrentRecording = () => {
}, [currentRecording, isRecording, layout?.width]);
useHotkeys(
["Ctrl+R", "Meta+R"],
currentHotkeys.PlayOrPauseRecording,
(keyboardEvent, hotkeyEvent) => {
if (!player) return;
keyboardEvent.preventDefault();
@@ -411,6 +415,7 @@ export const MediaCurrentRecording = () => {
document.getElementById("recording-play-or-pause-button").click();
}
},
{ enabled },
[player]
);
@@ -422,7 +427,9 @@ export const MediaCurrentRecording = () => {
<div
className="m-auto"
dangerouslySetInnerHTML={{
__html: t("noRecordingForThisSegmentYet"),
__html: t("noRecordingForThisSegmentYet", {
key: currentHotkeys.StartOrStopRecording?.toUpperCase(),
}),
}}
></div>
</div>
@@ -551,16 +558,17 @@ export const MediaCurrentRecording = () => {
>
<GaugeCircleIcon
className={`w-4 h-4 mr-4
${currentRecording.pronunciationAssessment
? currentRecording.pronunciationAssessment
.pronunciationScore >= 80
? "text-green-500"
: currentRecording.pronunciationAssessment
.pronunciationScore >= 60
? "text-yellow-600"
: "text-red-500"
: ""
}
${
currentRecording.pronunciationAssessment
? currentRecording.pronunciationAssessment
.pronunciationScore >= 80
? "text-green-500"
: currentRecording.pronunciationAssessment
.pronunciationScore >= 60
? "text-yellow-600"
: "text-red-500"
: ""
}
`}
/>
<span>{t("pronunciationAssessment")}</span>

View File

@@ -13,6 +13,7 @@ import {
import {
MediaPlayerProviderContext,
AppSettingsProviderContext,
HotKeysSettingsProviderContext,
} from "@renderer/context";
import {
ScissorsIcon,
@@ -56,6 +57,7 @@ export const MediaPlayerControls = () => {
setTranscriptionDraft,
} = useContext(MediaPlayerProviderContext);
const { EnjoyApp } = useContext(AppSettingsProviderContext);
const { currentHotkeys, enabled } = useContext(HotKeysSettingsProviderContext)
const [playMode, setPlayMode] = useState<"loop" | "single" | "all">("single");
const [playbackRate, setPlaybackRate] = useState<number>(1);
const [grouping, setGrouping] = useState(false);
@@ -372,30 +374,32 @@ export const MediaPlayerControls = () => {
}, [wavesurfer, decoded, playMode, activeRegion, currentTime]);
useHotkeys(
["Space", "p", "n", "r", "c"],
[currentHotkeys.PlayOrPause, currentHotkeys.PlayPreviousSegment, currentHotkeys.PlayNextSegment, currentHotkeys.StartOrStopRecording, currentHotkeys.Compare],
(keyboardEvent, hotkeyEvent) => {
if (!wavesurfer) return;
keyboardEvent.preventDefault();
switch (hotkeyEvent.keys.join("")) {
case "space":
case currentHotkeys.PlayOrPause.toLowerCase():
document.getElementById("media-play-or-pause-button").click();
break;
case "p":
case currentHotkeys.PlayPreviousSegment.toLowerCase():
document.getElementById("media-play-previous-button").click();
break;
case "n":
case currentHotkeys.PlayNextSegment.toLowerCase():
document.getElementById("media-play-next-button").click();
break;
case "r":
case currentHotkeys.StartOrStopRecording.toLowerCase():
document.getElementById("media-record-button").click();
break;
case "c":
case currentHotkeys.Compare.toLowerCase():
document.getElementById("media-compare-button").click();
break;
}
},{
enabled
},
[wavesurfer]
[wavesurfer, currentHotkeys]
);
/*

View File

@@ -17,6 +17,7 @@ import {
} from "@renderer/components/ui";
import {
AppSettingsProviderContext,
HotKeysSettingsProviderContext,
MediaPlayerProviderContext,
} from "@renderer/context";
import {
@@ -29,6 +30,7 @@ import { t } from "i18next";
import { formatDateTime, formatDuration } from "@renderer/lib/utils";
export const MediaRecordings = () => {
const { currentHotkeys } = useContext(HotKeysSettingsProviderContext);
const containerRef = useRef<HTMLDivElement>();
const {
recordings = [],
@@ -59,7 +61,9 @@ export const MediaRecordings = () => {
<div
className="text-center px-6 py-8 text-sm text-muted-foreground"
dangerouslySetInnerHTML={{
__html: t("noRecordingForThisSegmentYet"),
__html: t("noRecordingForThisSegmentYet", {
key: currentHotkeys.StartOrStopRecording?.toUpperCase(),
}),
}}
></div>
)}

View File

@@ -1,19 +1,45 @@
import { t } from "i18next";
import { Separator } from "@renderer/components/ui";
import { HotKeysSettingsProviderContext, Hotkey } from "@/renderer/context";
import { useContext, useState } from "react";
import { ChangeHotkeyDialog } from "../change-hotkey-dialog";
export const Hotkeys = () => {
const [open, setOpen] = useState(false);
const [selectedItem, setSelectedItem] = useState<{
name: string;
keyName: string;
} | null>(null);
const {
currentHotkeys,
startRecordingHotkeys,
stopRecordingHotkeys,
} = 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>
<div className="mb-6">
<div className="text-sm text-muted-foreground">{t("system")}</div>
<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">
<kbd className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-not-allowed">
{commandOrCtrl} + Q
</kbd>
</div>
@@ -24,8 +50,16 @@ export const Hotkeys = () => {
<div className="flex items-center space-x-2">
{t("openPreferences")}
</div>
<kbd className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground">
{commandOrCtrl} + ,
<kbd
onClick={() =>
handleItemSelected({
name: "Open preferences",
keyName: "OpenPreferences",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
>
{currentHotkeys.OpenPreferences}
</kbd>
</div>
<Separator />
@@ -36,8 +70,16 @@ export const Hotkeys = () => {
<div className="flex items-center justify-between py-4">
<div className="flex items-center space-x-2">{t("playOrPause")}</div>
<kbd className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground">
Space
<kbd
onClick={() =>
handleItemSelected({
name: t("playOrPause"),
keyName: "PlayOrPause",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
>
{currentHotkeys.PlayOrPause}
</kbd>
</div>
@@ -47,8 +89,16 @@ export const Hotkeys = () => {
<div className="flex items-center space-x-2 capitalize">
{t("startOrStopRecording")}
</div>
<kbd className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground">
r
<kbd
onClick={() =>
handleItemSelected({
name: t("startOrStopRecording"),
keyName: "StartOrStopRecording",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
>
{currentHotkeys.StartOrStopRecording}
</kbd>
</div>
@@ -58,8 +108,16 @@ export const Hotkeys = () => {
<div className="flex items-center space-x-2">
{t("playOrPauseRecording")}
</div>
<kbd className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground">
{commandOrCtrl} + r
<kbd
onClick={() =>
handleItemSelected({
name: t("playOrPauseRecording"),
keyName: "PlayOrPauseRecording",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
>
{currentHotkeys.PlayOrPauseRecording}
</kbd>
</div>
@@ -69,8 +127,16 @@ export const Hotkeys = () => {
<div className="flex items-center space-x-2 capitalize">
{t("playPreviousSegment")}
</div>
<kbd className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground">
p
<kbd
onClick={() =>
handleItemSelected({
name: t("playPreviousSegment"),
keyName: "PlayPreviousSegment",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
>
{currentHotkeys.PlayPreviousSegment}
</kbd>
</div>
@@ -80,8 +146,16 @@ export const Hotkeys = () => {
<div className="flex items-center space-x-2 capitalize">
{t("playNextSegment")}
</div>
<kbd className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground">
n
<kbd
onClick={() =>
handleItemSelected({
name: t("playNextSegment"),
keyName: "PlayNextSegment",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
>
{currentHotkeys.PlayNextSegment}
</kbd>
</div>
@@ -91,14 +165,28 @@ export const Hotkeys = () => {
<div className="flex items-center space-x-2 capitalize">
{t("compare")}
</div>
<kbd className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground">
c
<kbd
onClick={() =>
handleItemSelected({
name: t("compare"),
keyName: "Compare",
})
}
className="bg-muted px-2 py-1 rounded-md text-sm text-muted-foreground cursor-pointer"
>
{currentHotkeys.Compare}
</kbd>
</div>
<Separator />
</div>
<ChangeHotkeyDialog
open={open}
keyName={selectedItem?.keyName}
name={selectedItem?.name}
onOpenChange={handleOpenChange}
/>
</>
);
};

View File

@@ -0,0 +1,236 @@
import {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import { useHotkeys, useRecordHotkeys } from "react-hotkeys-hook";
import { AppSettingsProviderContext } from "./app-settings-provider";
import _ from "lodash";
function isShortcutValid(shortcut: string) {
const modifiers = ["ctrl", "alt", "shift", "meta"];
const keys = shortcut.toLowerCase().split("+");
const modifierCount = keys.filter((key) => modifiers.includes(key)).length;
const normalKeyCount = keys.length - modifierCount;
// Validation rule: At most one modifier key, and at most one regular key
return modifierCount <= 1 && normalKeyCount === 1;
}
function mergeWithPreference(
a: Record<string, string>, // electron settings's cached value
b: Record<string, string> // current version's default value
): Record<string, string> {
const c: Record<string, string> = {};
for (const key in b) {
c[key] = b[key];
}
for (const key in a) {
if (key in b) {
c[key] = a[key];
}
}
return c;
}
const ControlOrCommand = navigator.platform.includes("Mac")
? "Meta"
: "Control";
const defaultKeyMap = {
// system
QuitApp: `${ControlOrCommand}+Q`,
OpenPreferences: `${ControlOrCommand}+Comma`,
// player
PlayOrPause: "Space",
StartOrStopRecording: "r",
PlayOrPauseRecording: `${ControlOrCommand}+r`,
PlayPreviousSegment: "p",
PlayNextSegment: "n",
Compare: "c",
// dev tools
OpenDevTools: `${ControlOrCommand}+Shift+I`,
};
export type Hotkey = keyof typeof defaultKeyMap;
function checkKeyAndValue(
key: Hotkey,
value: string,
shortcuts: typeof defaultKeyMap
) {
const inputValue = value.toLowerCase();
const conflictKeys = Object.keys(shortcuts).filter(
(k: Hotkey) => shortcuts[k].toLowerCase() === inputValue && k !== key
);
return conflictKeys;
}
type HotkeysSettingsProviderState = {
currentHotkeys: Record<string, string>;
recordingHotkeys?: any;
enabled: boolean;
startRecordingHotkeys?: () => void;
stopRecordingHotkeys?: () => void;
resetRecordingHotkeys?: () => void;
changeHotkey?: (key: string, recordedHotkeys: Set<string>) => void;
};
const initialState: HotkeysSettingsProviderState = {
currentHotkeys: {},
enabled: true,
};
export const HotKeysSettingsProviderContext = createContext<
HotkeysSettingsProviderState
>(initialState);
const HotKeysSettingsSystemSettings = ({
currentHotkeys,
enabled,
children,
}: {
currentHotkeys: Record<string, string>;
enabled: boolean;
children: React.ReactNode;
}) => {
useHotkeys(
currentHotkeys.OpenPreferences,
() => {
document.getElementById("preferences-button")?.click();
},
{
enabled,
}
);
useHotkeys(
currentHotkeys.QuitApp,
() => {
window.__ENJOY_APP__.app.quit();
},
{
enabled,
}
);
useHotkeys(
currentHotkeys.OpenDevTools,
() => {
window.__ENJOY_APP__.app.openDevTools();
},
{
enabled,
}
);
return children;
};
export const HotKeysSettingsProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const [currentHotkeys, setCurrentHotkeys] = useState<any>(
initialState.currentHotkeys
);
const [keys, { start, stop, resetKeys, isRecording }] = useRecordHotkeys();
const { EnjoyApp } = useContext(AppSettingsProviderContext);
useEffect(() => {
fetchSettings();
}, []);
const fetchSettings = async () => {
const _hotkeys = await EnjoyApp.settings.getDefaultHotkeys();
// During version iterations, there may be added or removed keys.
const merged = mergeWithPreference(_hotkeys ?? {}, defaultKeyMap);
await EnjoyApp.settings.setDefaultHotkeys(merged).then(() => {
setCurrentHotkeys(merged);
});
};
const changeHotkey = useCallback(
async (
keyName: Hotkey,
recordedHotkeys: Set<string>
): Promise<{
error: "conflict" | "invalid";
data: string | string[];
input: string;
} | void> => {
const str = [...recordedHotkeys].join("+");
const newMap = {
...currentHotkeys,
[keyName]: str,
};
// validate
const conflictKeys = checkKeyAndValue(keyName, str, currentHotkeys);
if (conflictKeys.length > 0) {
resetKeys();
return {
error: "conflict",
data: conflictKeys,
input: str,
};
}
const isValid = isShortcutValid(str);
if (!isValid) {
resetKeys();
return {
error: "invalid",
data: str,
input: str,
};
}
await EnjoyApp.settings.setDefaultHotkeys(newMap).then(() => {
setCurrentHotkeys(newMap);
});
resetKeys();
},
[currentHotkeys]
);
const startRecordingHotkeys = () => {
start();
};
const stopRecordingHotkeys = () => {
stop();
resetKeys();
};
return (
<HotKeysSettingsProviderContext.Provider
value={{
currentHotkeys,
recordingHotkeys: keys,
enabled: !isRecording,
startRecordingHotkeys,
stopRecordingHotkeys,
resetRecordingHotkeys: resetKeys,
changeHotkey,
}}
>
{_.isEmpty(currentHotkeys) ? null : (
<HotKeysSettingsSystemSettings
{...{
currentHotkeys,
enabled: !isRecording,
}}
>
{children}
</HotKeysSettingsSystemSettings>
)}
</HotKeysSettingsProviderContext.Provider>
);
};

View File

@@ -4,3 +4,4 @@ export * from "./db-provider";
export * from "./theme-provider";
export * from "./wavesurfer-provider";
export * from "./media-player-provider";
export * from './hotkeys-settings-provider'

View File

@@ -104,6 +104,8 @@ type EnjoyAppType = {
) => Promise<void>;
getLanguage: () => Promise<string>;
switchLanguage: (language: string) => Promise<void>;
getDefaultHotkeys: () => Promise<Record<string, string> | undefined>;
setDefaultHotkeys: (records: Record<string, string>) => Promise<void>;
};
fs: {
ensureDir: (path: string) => Promise<boolean>;