fix: shortcuts back working
This commit is contained in:
@@ -44,6 +44,7 @@ defmodule BDS.Desktop.ShellLive do
|
||||
socket
|
||||
|> assign(:page_title, ShellData.title())
|
||||
|> assign(:page_language, ShellData.ui_language())
|
||||
|> assign(:client_shortcuts, Commands.client_shortcuts())
|
||||
|> assign(:offline_mode, true)
|
||||
|> assign(:tab_meta, %{})
|
||||
|> assign(:project_menu_open, false)
|
||||
@@ -940,6 +941,8 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|
||||
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
||||
|
||||
defp encoded_shortcuts(shortcuts), do: Jason.encode!(shortcuts)
|
||||
|
||||
defp panel_tab_label(:tasks), do: translated("Tasks")
|
||||
defp panel_tab_label(:output), do: translated("Output")
|
||||
defp panel_tab_label(:git_log), do: translated("Git Log")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="app" id="bds-shell-app" phx-hook="AppShell" phx-window-keydown="shortcut">
|
||||
<div class="app" id="bds-shell-app" phx-hook="AppShell" data-shortcuts={encoded_shortcuts(@client_shortcuts)}>
|
||||
<div class="window-titlebar" data-region="title-bar">
|
||||
<div class="window-titlebar-menu-bar is-hidden">
|
||||
<button class="window-titlebar-menu-button" type="button">File</button>
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule BDS.UI.Commands do
|
||||
%{id: :select_all, accelerator: "CTRL+A"},
|
||||
%{id: :find, accelerator: "CTRL+F"},
|
||||
%{id: :replace, accelerator: "CTRL+H"},
|
||||
%{id: :edit_preferences, accelerator: "CTRL+,"},
|
||||
%{id: :edit_preferences, accelerator: "CTRL+,", key: ",", primary: true},
|
||||
%{id: :view_posts, accelerator: "CTRL+1", key: "1", primary: true},
|
||||
%{id: :view_media, accelerator: "CTRL+2", key: "2", primary: true},
|
||||
%{id: :toggle_sidebar, accelerator: "CTRL+B", key: "b", primary: true},
|
||||
@@ -37,12 +37,28 @@ defmodule BDS.UI.Commands do
|
||||
Map.get(shortcut, :meta, Map.get(shortcut, "meta", false)) or
|
||||
Map.get(shortcut, :ctrl, Map.get(shortcut, "ctrl", false))
|
||||
|
||||
case Enum.find(@menu_shortcuts, &shortcut_match?(&1, key, primary)) do
|
||||
shift = Map.get(shortcut, :shift, Map.get(shortcut, "shift", false))
|
||||
alt = Map.get(shortcut, :alt, Map.get(shortcut, "alt", false))
|
||||
|
||||
case Enum.find(@menu_shortcuts, &shortcut_match?(&1, key, primary, shift, alt)) do
|
||||
%{id: command_id} -> MenuBar.execute(state, command_id)
|
||||
nil -> state
|
||||
end
|
||||
end
|
||||
|
||||
def client_shortcuts do
|
||||
@menu_shortcuts
|
||||
|> Enum.filter(&Map.has_key?(&1, :key))
|
||||
|> Enum.map(fn shortcut ->
|
||||
%{
|
||||
key: shortcut.key,
|
||||
primary: Map.get(shortcut, :primary, false),
|
||||
shift: Map.get(shortcut, :shift, false),
|
||||
alt: Map.get(shortcut, :alt, false)
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
def accelerator_label(command_id) when is_atom(command_id) do
|
||||
case Enum.find(@menu_shortcuts, &(&1.id == command_id)) do
|
||||
%{accelerator: accelerator} -> accelerator
|
||||
@@ -50,9 +66,12 @@ defmodule BDS.UI.Commands do
|
||||
end
|
||||
end
|
||||
|
||||
defp shortcut_match?(%{key: expected_key, primary: expected_primary}, key, primary) do
|
||||
key == expected_key and primary == expected_primary
|
||||
defp shortcut_match?(%{key: expected_key} = shortcut, key, primary, shift, alt) do
|
||||
key == expected_key and
|
||||
primary == Map.get(shortcut, :primary, false) and
|
||||
shift == Map.get(shortcut, :shift, false) and
|
||||
alt == Map.get(shortcut, :alt, false)
|
||||
end
|
||||
|
||||
defp shortcut_match?(_shortcut, _key, _primary), do: false
|
||||
defp shortcut_match?(_shortcut, _key, _primary, _shift, _alt), do: false
|
||||
end
|
||||
|
||||
@@ -7,6 +7,37 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const ASSISTANT_STORAGE_KEY = "bds-panel-assistant-sidebar";
|
||||
const UI_LANGUAGE_STORAGE_KEY = "bds-ui-language";
|
||||
|
||||
const parseShortcutConfig = (value) => {
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch (_error) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeShortcutKey = (key) => String(key || "").toLowerCase();
|
||||
|
||||
const shortcutTargetIsEditable = (event) => {
|
||||
const tag = event.target?.tagName || null;
|
||||
return event.target?.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(tag);
|
||||
};
|
||||
|
||||
const shortcutMatchesEvent = (shortcut, event) => {
|
||||
const primary = event.metaKey || event.ctrlKey;
|
||||
|
||||
return (
|
||||
normalizeShortcutKey(event.key) === normalizeShortcutKey(shortcut.key) &&
|
||||
primary === Boolean(shortcut.primary) &&
|
||||
event.shiftKey === Boolean(shortcut.shift) &&
|
||||
event.altKey === Boolean(shortcut.alt)
|
||||
);
|
||||
};
|
||||
|
||||
const clamp = (value, min, max) => Math.max(min, Math.min(value, max));
|
||||
|
||||
const readStoredSize = (key, fallback, min, max) => {
|
||||
@@ -93,6 +124,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const Hooks = {
|
||||
AppShell: {
|
||||
mounted() {
|
||||
this.shortcuts = parseShortcutConfig(this.el.dataset.shortcuts);
|
||||
this.syncStoredLayout();
|
||||
this.syncStoredUiLanguage();
|
||||
this.destroyOverlaySync = syncTitlebarOverlayInsets();
|
||||
@@ -159,7 +191,33 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
}
|
||||
};
|
||||
|
||||
this.handleShortcutKeyDown = (event) => {
|
||||
if (shortcutTargetIsEditable(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shortcut = this.shortcuts.find((candidate) => shortcutMatchesEvent(candidate, event));
|
||||
|
||||
if (!shortcut) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.pushEvent("shortcut", {
|
||||
key: normalizeShortcutKey(event.key),
|
||||
meta: event.metaKey,
|
||||
ctrl: event.ctrlKey,
|
||||
alt: event.altKey,
|
||||
shift: event.shiftKey,
|
||||
tag: event.target?.tagName || null,
|
||||
contentEditable: event.target?.isContentEditable || false
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("bds:native-menu-action", this.handleNativeMenuAction);
|
||||
window.addEventListener("keydown", this.handleShortcutKeyDown, true);
|
||||
this.el.addEventListener("change", this.handleChange);
|
||||
},
|
||||
|
||||
@@ -167,6 +225,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
this.el.removeEventListener("mousedown", this.handleMouseDown);
|
||||
this.el.removeEventListener("change", this.handleChange);
|
||||
window.removeEventListener("bds:native-menu-action", this.handleNativeMenuAction);
|
||||
window.removeEventListener("keydown", this.handleShortcutKeyDown, true);
|
||||
if (this.destroyOverlaySync) {
|
||||
this.destroyOverlaySync();
|
||||
}
|
||||
|
||||
@@ -60,6 +60,11 @@ defmodule BDS.Desktop.AutomationTest do
|
||||
snapshot = Automation.snapshot(session)
|
||||
assert snapshot.panel_visible == false
|
||||
|
||||
assert :ok = Automation.press(session, "Meta+,")
|
||||
|
||||
snapshot = Automation.snapshot(session)
|
||||
assert snapshot.editor_title == "Settings"
|
||||
|
||||
assert :ok = Automation.click(session, "[data-testid='toggle-assistant']")
|
||||
|
||||
snapshot = Automation.snapshot(session)
|
||||
|
||||
@@ -79,6 +79,9 @@ defmodule BDS.UI.ShellTest do
|
||||
state = Commands.handle_shortcut(state, %{meta: true, key: "w"})
|
||||
assert state.tabs == []
|
||||
assert state.editor_route == :dashboard
|
||||
|
||||
state = Commands.handle_shortcut(state, %{meta: true, key: ","})
|
||||
assert state.editor_route == :settings
|
||||
end
|
||||
|
||||
test "resizing is clamped to the shell limits and dirty flags only apply to post tabs" do
|
||||
@@ -142,6 +145,11 @@ defmodule BDS.UI.ShellTest do
|
||||
assert live_js =~ "windowControlsOverlay"
|
||||
assert live_js =~ "geometrychange"
|
||||
assert live_js =~ "--bds-titlebar-overlay-left"
|
||||
assert live_js =~ "dataset.shortcuts"
|
||||
assert live_js =~ "addEventListener(\"keydown\", this.handleShortcutKeyDown, true)"
|
||||
assert live_js =~ "event.preventDefault()"
|
||||
assert live_js =~ "this.pushEvent(\"shortcut\""
|
||||
assert template =~ "data-shortcuts={encoded_shortcuts(@client_shortcuts)}"
|
||||
assert template =~ "tab-actions"
|
||||
assert template =~ "tab-dirty-indicator"
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user