Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 13 additions & 0 deletions src/client/UserSettingModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -366,6 +372,13 @@ export class UserSettingModal extends BaseModal {
${translateText("user_setting.keybinds_hint")}
</div>

<button
class="text-xs font-bold uppercase tracking-wider bg-white/5 hover:bg-red-500/20 border border-white/10 hover:border-red-500/50 px-4 py-2 rounded text-white/70 hover:text-red-200 transition-colors mt-3"
@click=${this.unbindAllKeybinds}
>
${translateText("user_setting.unbind_all")}
</button>

<h2
class="text-blue-200 text-xl font-bold mt-4 mb-3 border-b border-white/10 pb-2"
>
Expand Down
12 changes: 12 additions & 0 deletions src/core/game/UserSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> = {};
for (const action of Object.keys(getDefaultKeybinds(isMac))) {
unbound[action] = "Null";
}
this.setKeybinds(unbound);
Comment on lines +559 to +563

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg '(^|/)UserSettings\.ts$|(^|/)UserSettingModal\.(ts|tsx)$|setting-keybind|keybind' | sed -n '1,200p'

echo
echo "== UserSettings outline =="
file=$(git ls-files | rg '(^|/)UserSettings\.ts$' | head -n1 || true)
if [ -n "${file:-}" ]; then
  echo "FILE=$file"
  wc -l "$file"
  ast-grep outline "$file" --view compact | sed -n '1,220p'
  echo
  echo "== relevant UserSettings lines 520-600 =="
  sed -n '520,600p' "$file" | nl -ba -v520
  echo
  echo "== getDefaultKeybinds/setKeybinds/keybinds occurrences =="
  rg -n "getDefaultKeybinds|setKeybinds|keybinds\(|Null|keybind" "$file" | sed -n '1,200p'
fi

echo
echo "== search keybind modal/loading code =="
rg -n "UserSettingModal|display=|value=" -g '*.{ts,tsx,js,jsx}' . | sed -n '1,240p'

Repository: openfrontio/OpenFrontIO

Length of output: 489


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== UserSettings core code =="
sed -n '500,585p' src/core/game/UserSettings.ts | nl -ba -v500

echo
echo "== UserSettings keybind usages =="
rg -n "getDefaultKeybinds|setKeybinds|keybinds\(|Null|unbound" src/core/game/UserSettings.ts

echo
echo "== UserSettingModal relevant code =="
wc -l src/client/UserSettingModal.ts
rg -n "SettingKey|keybind|getDisplay|get.*Key|display|value" src/client/UserSettingModal.ts
sed -n '1,260p' src/client/UserSettingModal.ts | nl -ba -v1

echo
echo "== check tests for unbind =="
rg -n "Null|unbind|keybind|getDefaultKeybinds|settings-keybind|setting-keybind" -p tests src | nl -ba -v1 | sed -n '1,260p'

Repository: openfrontio/OpenFrontIO

Length of output: 230


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== UserSettings core code =="
awk '{printf "%6d\t%s\n", NR, $0}' src/core/game/UserSettings.ts | sed -n '500,585p'

echo
echo "== UserSettings keybind usages =="
rg -n "getDefaultKeybinds|setKeybinds|keybinds\(|Null|unbound" src/core/game/UserSettings.ts

echo
echo "== UserSettingModal relevant code =="
wc -l src/client/UserSettingModal.ts
rg -n "SettingKey|keybind|getDisplay|get.*Key|display|value" src/client/UserSettingModal.ts
awk '{printf "%6d\t%s\n", NR, $0}' src/client/UserSettingModal.ts | sed -n '1,260p'

echo
echo "== check tests/usages for unbind =="
rg -n "Null|unbind|keybind|getDefaultKeybinds|settings-keybind|setting-keybind" tests src | sed -n '1,260p'

Repository: openfrontio/OpenFrontIO

Length of output: 47215


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== getKeyValue/getKeyChar loadKeybind path =="
awk '{printf "%6d\t%s\n", NR, $0}' src/client/UserSettingModal.ts | sed -n '40,85p'
awk '{printf "%6d\t%s\n", NR, $0}' src/client/UserSettingModal.ts | sed -n '177,190p'

echo
echo "== SettingKeybind component =="
rg -n "class SettingKeybind|display|value|requestUpdate|render|formatKeyForDisplay" -g '*ts' src/client -A80 | sed -n '1,260p'

echo
echo "== precise SettingKeybind file(s) =="
git ls-files | rg 'SettingKeybind|setting/.*Keybind'
for f in $(git ls-files | rg 'SettingKeybind|setting/.*Keybind'); do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,180p' "$f" | awk '{printf "%6d\t%s\n", NR, $0}'
done

echo
echo "== JS-like behavior probe for loadKeybinds validation of string "Null" entry =="
node - <<'JS'
function loadKeybindsFromStorage(parsed) {
  const validated = {};
  for (const [action, entry] of Object.entries(parsed)) {
    if (typeof entry === "string") {
      validated[action] = { value: entry, key: entry };
    } else if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
      const rawValue = entry.value ?? "Null";
      const value = Array.isArray(rawValue)
        ? rawValue.find(v => typeof v === "string")
        : rawValue;
      const rawKey = entry.key ?? value;
      const key = Array.isArray(rawKey)
        ? rawKey.find(v => typeof v === "string")
        : rawKey;
      if (typeof value === "string" && typeof key === "string") {
        validated[action] = { value, key };
      }
    }
  }
  return validated;
}
const storage = { unbindAll: { value: "Null", key: "Null" } };
const userKeybinds = loadKeybindsFromStorage(storage);
console.log(JSON.stringify({
  storage,
  validated: userKeybinds,
  value: userKeybinds.unbindAll.value,
  display: userKeybinds.unbindAll.key,
  getKeyValue: userKeybinds.unbindAll.value === "Null" ? "" : userKeybinds.unbindAll.value || undefined,
  getKeyChar: userKeybinds.unbindAll.key || "",
}));
JS

Repository: openfrontio/OpenFrontIO

Length of output: 27349


Normalize "Null" to an empty key after bulk unbind.

loadKeybindsFromStorage() already maps string entries to value and key, while getKeyValue() normalizes the value. Use the same normalization for the key, such as normalizing "Null" to an empty key, so the keybind rows do not receive display="Null" on reload.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/game/UserSettings.ts` around lines 559 - 563, Update
loadKeybindsFromStorage() to normalize the loaded key through the same logic
used by getKeyValue(), converting the bulk-unbind sentinel "Null" to an empty
key before assigning it to keybind rows. Keep normal key values unchanged and
preserve the existing value normalization.

}

soundEffectsVolume(): number {
return this.getFloat("settings.soundEffectsVolume", 0);
}
Expand Down
24 changes: 24 additions & 0 deletions tests/UserSettings.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
EFFECTS_KEY,
getDefaultKeybinds,
PLAYER_STATS_COLUMNS_KEY,
TEAM_STATS_COLUMNS_KEY,
UserSettings,
Expand Down Expand Up @@ -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<string, string | null> }
).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);
});
});
Loading