mirror of
https://github.com/t8y2/dbx.git
synced 2026-11-01 16:28:46 +00:00
feat(settings): add custom shortcut settings
This commit is contained in:
@@ -1,36 +1,87 @@
|
||||
import {
|
||||
DEFAULT_SHORTCUT_SETTINGS,
|
||||
normalizeShortcutSettings,
|
||||
type ShortcutActionId,
|
||||
type ShortcutSettings,
|
||||
} from "@/lib/shortcutRegistry";
|
||||
|
||||
export interface ShortcutLikeEvent {
|
||||
key: string;
|
||||
metaKey?: boolean;
|
||||
ctrlKey?: boolean;
|
||||
altKey?: boolean;
|
||||
shiftKey?: boolean;
|
||||
isComposing?: boolean;
|
||||
}
|
||||
|
||||
export function isExecuteSqlShortcut(event: ShortcutLikeEvent): boolean {
|
||||
if (event.isComposing) return false;
|
||||
if (event.altKey) return false;
|
||||
if (!event.metaKey && !event.ctrlKey) return false;
|
||||
return event.key === "Enter";
|
||||
function normalizeKey(key: string): string {
|
||||
if (key === " ") return "Space";
|
||||
return key.length === 1 ? key.toLowerCase() : key;
|
||||
}
|
||||
|
||||
export function isCloseTabShortcut(event: ShortcutLikeEvent): boolean {
|
||||
if (event.isComposing) return false;
|
||||
if (!event.metaKey) return false;
|
||||
return event.key.toLowerCase() === "w";
|
||||
function shortcutKeyName(key: string): string | null {
|
||||
if (key === " ") return "Space";
|
||||
if (["Control", "Meta", "Shift", "Alt"].includes(key)) return null;
|
||||
if (key.length === 1) return key.toUpperCase();
|
||||
return key;
|
||||
}
|
||||
|
||||
export function isFocusSearchShortcut(event: ShortcutLikeEvent): boolean {
|
||||
if (event.isComposing) return false;
|
||||
if (event.altKey) return false;
|
||||
if (!event.metaKey && !event.ctrlKey) return false;
|
||||
return event.key.toLowerCase() === "f";
|
||||
export function eventToShortcut(event: ShortcutLikeEvent): string | null {
|
||||
if (event.isComposing) return null;
|
||||
|
||||
const key = shortcutKeyName(event.key);
|
||||
if (!key) return null;
|
||||
|
||||
const hasModifier = !!event.metaKey || !!event.ctrlKey || !!event.altKey || !!event.shiftKey;
|
||||
if (!hasModifier && key.length === 1) return null;
|
||||
|
||||
const parts: string[] = [];
|
||||
if (event.shiftKey) parts.push("Shift");
|
||||
if (event.metaKey || event.ctrlKey) parts.push("Mod");
|
||||
if (event.altKey) parts.push("Alt");
|
||||
parts.push(key);
|
||||
return parts.join("+");
|
||||
}
|
||||
|
||||
export function isSaveShortcut(event: ShortcutLikeEvent): boolean {
|
||||
export function matchesShortcut(event: ShortcutLikeEvent, shortcut: string): boolean {
|
||||
if (event.isComposing) return false;
|
||||
if (event.altKey) return false;
|
||||
if (!event.metaKey && !event.ctrlKey) return false;
|
||||
return event.key.toLowerCase() === "s";
|
||||
const parts = shortcut.split("+");
|
||||
const key = parts[parts.length - 1] ?? "";
|
||||
const modifiers = new Set(parts.slice(0, -1));
|
||||
const usesMod = modifiers.has("Mod");
|
||||
const usesMeta = modifiers.has("Meta");
|
||||
const usesCtrl = modifiers.has("Ctrl");
|
||||
|
||||
if (usesMod) {
|
||||
if (!event.metaKey && !event.ctrlKey) return false;
|
||||
} else {
|
||||
if (!!event.metaKey !== usesMeta) return false;
|
||||
if (!!event.ctrlKey !== usesCtrl) return false;
|
||||
}
|
||||
|
||||
if (!!event.altKey !== modifiers.has("Alt")) return false;
|
||||
if (!!event.shiftKey !== modifiers.has("Shift")) return false;
|
||||
return normalizeKey(event.key) === normalizeKey(key);
|
||||
}
|
||||
|
||||
function actionShortcut(actionId: ShortcutActionId, shortcuts?: Partial<ShortcutSettings>): string {
|
||||
return normalizeShortcutSettings(shortcuts)[actionId] || DEFAULT_SHORTCUT_SETTINGS[actionId];
|
||||
}
|
||||
|
||||
export function isExecuteSqlShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("executeSql", shortcuts));
|
||||
}
|
||||
|
||||
export function isCloseTabShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("closeTab", shortcuts));
|
||||
}
|
||||
|
||||
export function isFocusSearchShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("focusSearch", shortcuts));
|
||||
}
|
||||
|
||||
export function isSaveShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("saveSql", shortcuts));
|
||||
}
|
||||
|
||||
export function isObjectSourceSaveShortcutTarget(
|
||||
@@ -39,7 +90,6 @@ export function isObjectSourceSaveShortcutTarget(
|
||||
return !!target?.closest("[data-object-source-editor], [data-object-source-preview]");
|
||||
}
|
||||
|
||||
export function isCancelSearchShortcut(event: ShortcutLikeEvent): boolean {
|
||||
if (event.isComposing) return false;
|
||||
return event.key === "Escape";
|
||||
export function isCancelSearchShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("cancelSearch", shortcuts));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
export type ShortcutActionId = "executeSql" | "saveSql" | "closeTab" | "focusSearch" | "cancelSearch";
|
||||
|
||||
export type ShortcutScope = "global" | "editor" | "search";
|
||||
|
||||
export interface ShortcutDefinition {
|
||||
id: ShortcutActionId;
|
||||
labelKey: string;
|
||||
scope: ShortcutScope;
|
||||
defaultShortcut: string;
|
||||
}
|
||||
|
||||
export type ShortcutSettings = Record<ShortcutActionId, string>;
|
||||
|
||||
export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
|
||||
{
|
||||
id: "executeSql",
|
||||
labelKey: "settings.shortcutExecuteSql",
|
||||
scope: "editor",
|
||||
defaultShortcut: "Mod+Enter",
|
||||
},
|
||||
{
|
||||
id: "saveSql",
|
||||
labelKey: "settings.shortcutSaveSql",
|
||||
scope: "editor",
|
||||
defaultShortcut: "Mod+S",
|
||||
},
|
||||
{
|
||||
id: "closeTab",
|
||||
labelKey: "settings.shortcutCloseTab",
|
||||
scope: "global",
|
||||
defaultShortcut: "Meta+W",
|
||||
},
|
||||
{
|
||||
id: "focusSearch",
|
||||
labelKey: "settings.shortcutFocusSearch",
|
||||
scope: "global",
|
||||
defaultShortcut: "Mod+F",
|
||||
},
|
||||
{
|
||||
id: "cancelSearch",
|
||||
labelKey: "settings.shortcutCancelSearch",
|
||||
scope: "search",
|
||||
defaultShortcut: "Escape",
|
||||
},
|
||||
];
|
||||
|
||||
export const DEFAULT_SHORTCUT_SETTINGS: ShortcutSettings = Object.fromEntries(
|
||||
SHORTCUT_DEFINITIONS.map((definition) => [definition.id, definition.defaultShortcut]),
|
||||
) as ShortcutSettings;
|
||||
|
||||
export function normalizeShortcutSettings(settings?: Partial<ShortcutSettings>): ShortcutSettings {
|
||||
return Object.fromEntries(
|
||||
SHORTCUT_DEFINITIONS.map((definition) => [
|
||||
definition.id,
|
||||
typeof settings?.[definition.id] === "string" && settings[definition.id]?.trim()
|
||||
? settings[definition.id]
|
||||
: definition.defaultShortcut,
|
||||
]),
|
||||
) as ShortcutSettings;
|
||||
}
|
||||
|
||||
export function shortcutToCodeMirrorKey(shortcut: string): string {
|
||||
return shortcut
|
||||
.split("+")
|
||||
.map((part) => (part.length === 1 ? part.toLowerCase() : part))
|
||||
.join("-");
|
||||
}
|
||||
|
||||
export function formatShortcut(shortcut: string, platform = globalThis.navigator?.platform || ""): string {
|
||||
const isMac = platform.toLowerCase().includes("mac");
|
||||
return shortcut
|
||||
.split("+")
|
||||
.map((part) => {
|
||||
if (part === "Mod") return isMac ? "Cmd" : "Ctrl";
|
||||
if (part === "Meta") return isMac ? "Cmd" : "Meta";
|
||||
return part;
|
||||
})
|
||||
.join("+");
|
||||
}
|
||||
|
||||
export function findShortcutConflict(
|
||||
actionId: ShortcutActionId,
|
||||
shortcut: string,
|
||||
shortcuts: ShortcutSettings,
|
||||
): ShortcutActionId | null {
|
||||
const definition = SHORTCUT_DEFINITIONS.find((item) => item.id === actionId);
|
||||
if (!definition) return null;
|
||||
|
||||
const conflict = SHORTCUT_DEFINITIONS.find(
|
||||
(item) => item.id !== actionId && item.scope === definition.scope && shortcuts[item.id] === shortcut,
|
||||
);
|
||||
return conflict?.id ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user