From 8c4115cf29967d084ba8367d3d5737a499d11ec0 Mon Sep 17 00:00:00 2001 From: Grant Eastwood <308940434+eastwoodgrant@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:23:43 -0400 Subject: [PATCH] feat: add "Unbind All" button to keybind settings Rebinding on a fresh install/version meant clearing every key one by one first. Add an "Unbind All" button to the keybind settings tab that clears all bindings at once. UserSettings.unbindAllKeybinds() sets each default action to the "Null" sentinel (the same one a single per-key Unbind uses), so keybinds() drops them all. The modal button calls it, reloads state, and re-renders. Resolves #4309 Co-Authored-By: Claude Opus 4.8 --- resources/lang/en.json | 1 + src/client/UserSettingModal.ts | 13 +++++++++++++ src/core/game/UserSettings.ts | 12 ++++++++++++ tests/UserSettings.test.ts | 24 ++++++++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/resources/lang/en.json b/resources/lang/en.json index 6c89617f8b..effc4e7202 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -1667,6 +1667,7 @@ "toggle_view_desc": "Alternate view (terrain/countries)", "toggle_visibility": "Toggle Visibility", "unbind": "Unbind", + "unbind_all": "Unbind All", "view_options": "View Options", "zoom_controls": "Zoom Controls", "zoom_in": "Zoom In", diff --git a/src/client/UserSettingModal.ts b/src/client/UserSettingModal.ts index 428afd95dd..0a4a9a2dd4 100644 --- a/src/client/UserSettingModal.ts +++ b/src/client/UserSettingModal.ts @@ -73,6 +73,12 @@ export class UserSettingModal extends BaseModal { this.userKeybinds = validated; } + private unbindAllKeybinds() { + this.userSettings.unbindAllKeybinds(Platform.isMac); + this.loadKeybindsFromStorage(); + this.requestUpdate(); + } + private handleKeybindChange( e: CustomEvent<{ action: string; @@ -366,6 +372,13 @@ export class UserSettingModal extends BaseModal { ${translateText("user_setting.keybinds_hint")} + +

diff --git a/src/core/game/UserSettings.ts b/src/core/game/UserSettings.ts index 7be154af9e..b1b8e6c0b3 100644 --- a/src/core/game/UserSettings.ts +++ b/src/core/game/UserSettings.ts @@ -551,6 +551,18 @@ export class UserSettings { } } + /** + * Unbind every keybind at once: set each default action to "Null" (the same + * sentinel a single per-key Unbind uses), so keybinds() drops them all. + */ + unbindAllKeybinds(isMac: boolean): void { + const unbound: Record = {}; + for (const action of Object.keys(getDefaultKeybinds(isMac))) { + unbound[action] = "Null"; + } + this.setKeybinds(unbound); + } + soundEffectsVolume(): number { return this.getFloat("settings.soundEffectsVolume", 0); } diff --git a/tests/UserSettings.test.ts b/tests/UserSettings.test.ts index b526425858..077fa994e5 100644 --- a/tests/UserSettings.test.ts +++ b/tests/UserSettings.test.ts @@ -1,5 +1,6 @@ import { EFFECTS_KEY, + getDefaultKeybinds, PLAYER_STATS_COLUMNS_KEY, TEAM_STATS_COLUMNS_KEY, UserSettings, @@ -135,3 +136,26 @@ describe("UserSettings stats columns", () => { expect(localStorage.getItem(TEAM_STATS_COLUMNS_KEY)).toBe('["warships"]'); }); }); + +describe("UserSettings unbindAllKeybinds (#4309)", () => { + beforeEach(() => { + localStorage.clear(); + ( + UserSettings as unknown as { cache: Map } + ).cache.clear(); + }); + + it("sets every default keybind action to Null so nothing stays bound", () => { + const s = new UserSettings(); + s.unbindAllKeybinds(false); + + const parsed = s.parsedUserKeybinds(); + const defaults = getDefaultKeybinds(false); + expect(Object.keys(defaults).length).toBeGreaterThan(0); + for (const action of Object.keys(defaults)) { + expect(parsed[action]).toBe("Null"); + } + // keybinds() drops "Null" entries, so no action resolves to a key. + expect(Object.keys(s.keybinds(false))).toHaveLength(0); + }); +});