From 871de99ad7ad079bdda27d942141f05f2984ffe6 Mon Sep 17 00:00:00 2001 From: Jake <134332787+justjxke@users.noreply.github.com> Date: Wed, 5 Nov 2025 19:44:15 +0000 Subject: [PATCH] Keybind setting change (#480) --- src/equicordplugins/commandPalette/index.tsx | 148 +++++++++++++++++-- src/equicordplugins/commandPalette/style.css | 67 +++++++++ 2 files changed, 201 insertions(+), 14 deletions(-) diff --git a/src/equicordplugins/commandPalette/index.tsx b/src/equicordplugins/commandPalette/index.tsx index d096db590..ed7dd213a 100644 --- a/src/equicordplugins/commandPalette/index.tsx +++ b/src/equicordplugins/commandPalette/index.tsx @@ -5,23 +5,145 @@ */ import { definePluginSettings } from "@api/Settings"; +import { classNameFactory } from "@api/Styles"; import { EquicordDevs, IS_MAC } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { useState } from "@webpack/common"; import { cleanupCommandPaletteRuntime, registerBuiltInCommands, wrapChatBarChildren } from "./registry"; import { CommandPaletteSettingsPanel } from "./settingsPanel"; import { openCommandPalette } from "./ui"; const DEFAULT_KEYS = IS_MAC ? ["Meta", "Shift", "P"] : ["Control", "Shift", "P"]; -const DEFAULT_HOTKEY = DEFAULT_KEYS.join("+"); +const cl = classNameFactory("vc-cp-"); +let isRecordingGlobal = false; let openScheduled = false; export const settings = definePluginSettings({ hotkey: { - description: "Hotkey used to open the command palette (format: Ctrl+Shift+P)", - type: OptionType.STRING, - default: DEFAULT_HOTKEY + description: "Hotkey used to open the command palette", + type: OptionType.COMPONENT, + default: DEFAULT_KEYS, + component: () => { + const [isRecording, setIsRecording] = useState(false); + const [currentKeys, setCurrentKeys] = useState([]); + + const recordKeybind = (setIsRecording: (value: boolean) => void) => { + const pressedKeys: Set = new Set(); + const keyLists: string[][] = []; + let recordingTimeout: number | null = null; + + setIsRecording(true); + setCurrentKeys([]); + isRecordingGlobal = true; + + const updateKeys = () => { + const currentKeyArray = Array.from(pressedKeys); + setCurrentKeys(currentKeyArray); + keyLists.push(currentKeyArray); + + if (recordingTimeout) { + clearTimeout(recordingTimeout); + } + recordingTimeout = window.setTimeout(() => { + const longestArray = keyLists.reduce((a, b) => a.length > b.length ? a : b); + if (longestArray.length > 0) { + settings.store.hotkey = longestArray.map(key => key.toLowerCase()); + } + setIsRecording(false); + setCurrentKeys([]); + isRecordingGlobal = false; + document.removeEventListener("keydown", keydownListener); + document.removeEventListener("keyup", keyupListener); + }, 500); + }; + + const getKeyName = (key: string) => { + const keyMap: Record = { + "Control": "Control", + "Shift": "Shift", + "Alt": "Alt", + "Meta": "Meta" + }; + return keyMap[key] || key; + }; + + const keydownListener = (e: KeyboardEvent) => { + if (e.ctrlKey) pressedKeys.add("Control"); + else pressedKeys.delete("Control"); + + if (e.shiftKey) pressedKeys.add("Shift"); + else pressedKeys.delete("Shift"); + + if (e.altKey) pressedKeys.add("Alt"); + else pressedKeys.delete("Alt"); + + if (e.metaKey) pressedKeys.add("Meta"); + else pressedKeys.delete("Meta"); + + const keyName = getKeyName(e.key); + pressedKeys.add(keyName); + + updateKeys(); + }; + + const keyupListener = (e: KeyboardEvent) => { + if (e.ctrlKey) pressedKeys.add("Control"); + else pressedKeys.delete("Control"); + + if (e.shiftKey) pressedKeys.add("Shift"); + else pressedKeys.delete("Shift"); + + if (e.altKey) pressedKeys.add("Alt"); + else pressedKeys.delete("Alt"); + + if (e.metaKey) pressedKeys.add("Meta"); + else pressedKeys.delete("Meta"); + + const keyName = getKeyName(e.key); + pressedKeys.delete(keyName); + + updateKeys(); + }; + + document.addEventListener("keydown", keydownListener); + document.addEventListener("keyup", keyupListener); + }; + + const getKeySymbol = (key: string) => { + const normalizedKey = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase(); + const keySymbols: Record = { + "Control": "⌃", + "Shift": "⇧", + "Alt": "⌥", + "Meta": "⌘" + }; + return keySymbols[normalizedKey] || ""; + }; + + const displayKeys = isRecording ? currentKeys : settings.store.hotkey; + const displayText = displayKeys.length > 0 + ? displayKeys.map(word => { + const symbol = getKeySymbol(word); + const displayWord = word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + return symbol ? `${symbol}${displayWord}` : displayWord; + }).join(" + ") + : "No keybind set"; + + return ( + <> +
+
+ {displayText} + +
+
+ + ); + } }, visualStyle: { description: "Palette appearance", @@ -50,16 +172,13 @@ export const settings = definePluginSettings({ function getConfiguredHotkey() { const raw = settings.store.hotkey; - const hotkeyString = typeof raw === "string" ? raw.trim() : ""; - - if (!hotkeyString) return DEFAULT_KEYS; - - const parts = hotkeyString - .split("+") - .map(part => part.trim()) - .filter(Boolean); - - return parts.length > 0 ? parts : DEFAULT_KEYS; + if (Array.isArray(raw) && raw.length > 0) { + return raw; + } + if (typeof raw === "string" && (raw as string).trim()) { + return (raw as string).split("+").map(part => part.trim()).filter(Boolean); + } + return DEFAULT_KEYS; } function matchesHotkey(e: KeyboardEvent) { @@ -142,6 +261,7 @@ export default definePlugin({ }, handleKeydown(e: KeyboardEvent) { + if (isRecordingGlobal) return; if (!matchesHotkey(e)) return; if (shouldIgnoreTarget(e.target) && !hotkeyUsesModifiers()) return; e.preventDefault(); diff --git a/src/equicordplugins/commandPalette/style.css b/src/equicordplugins/commandPalette/style.css index b395f9a30..baa3060a2 100644 --- a/src/equicordplugins/commandPalette/style.css +++ b/src/equicordplugins/commandPalette/style.css @@ -681,6 +681,73 @@ background: rgb(255 255 255 / 12%); } +/* Keybind recorder styles */ +.vc-cp-key-recorder-container { + position: relative; + display: block; + cursor: pointer; + height: 40px; + width: 20rem; +} + +.vc-cp-key-recorder { + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 3px; + background-color: hsl(var(--black-500-hsl) / 10%); + color: var(--header-primary); + line-height: 22px; + font-weight: 600; + padding: 10px; + text-overflow: ellipsis; + overflow: hidden; + border: 1px solid; + border-color: hsl(var(--black-500-hsl) / 30%); + transition: border .15s ease; + user-select: none; + font-family: var(--font-primary); +} + +.vc-cp-key-display { + flex: 1; + text-overflow: ellipsis; + overflow: hidden; + margin-right: 10px; +} + +.vc-cp-recording { + animation: shadowPulse_b16790 1s ease-in infinite; + box-shadow: 0 0 6px hsl(var(--red-400-hsl) / 30%); + border-color: hsl(var(--red-400-hsl) / 30%); + color: var(--status-danger); +} + +.vc-cp-key-recorder:hover { + border-color: hsl(var(--red-400-hsl) / 30%); +} + +.vc-cp-recording-button { + color: var(--status-danger) !important; + background-color: hsl(var(--red-400-hsl) / 10%) !important; + opacity: 1; + transition: opacity .2s ease-in-out, transform .2s ease-in-out; +} + +.vc-cp-key-recorder-button { + flex-shrink: 0; + height: 30px; + width: 128px; + color: var(--white-500); + background-color: var(--button-secondary-background); + border-radius: 10px; + transition: background-color 0.15s ease; +} + +.vc-cp-key-recorder-button:hover { + background-color: var(--button-secondary-background-hover); +} + /* stylelint-enable no-descending-specificity */ /* fuck you css */