@@ -24,6 +24,10 @@ defmodule BDS.Desktop.Automation do
|
|||||||
GenServer.call(session, {:click, selector}, @request_timeout)
|
GenServer.call(session, {:click, selector}, @request_timeout)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def press(session, shortcut) when is_binary(shortcut) do
|
||||||
|
GenServer.call(session, {:press, shortcut}, @request_timeout)
|
||||||
|
end
|
||||||
|
|
||||||
def capture_screenshot(session, destination) when is_binary(destination) do
|
def capture_screenshot(session, destination) when is_binary(destination) do
|
||||||
GenServer.call(session, {:capture_screenshot, destination}, @request_timeout)
|
GenServer.call(session, {:capture_screenshot, destination}, @request_timeout)
|
||||||
end
|
end
|
||||||
@@ -78,6 +82,11 @@ defmodule BDS.Desktop.Automation do
|
|||||||
{:reply, normalize_simple_reply(reply), state}
|
{:reply, normalize_simple_reply(reply), state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_call({:press, shortcut}, _from, state) do
|
||||||
|
{reply, state} = driver_request(state, %{"command" => "press", "shortcut" => shortcut})
|
||||||
|
{:reply, normalize_simple_reply(reply), state}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_call({:capture_screenshot, destination}, _from, state) do
|
def handle_call({:capture_screenshot, destination}, _from, state) do
|
||||||
File.mkdir_p!(Path.dirname(destination))
|
File.mkdir_p!(Path.dirname(destination))
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
import Phoenix.HTML
|
import Phoenix.HTML
|
||||||
|
|
||||||
alias BDS.Desktop.ShellData
|
alias BDS.Desktop.ShellData
|
||||||
|
alias BDS.UI.Commands
|
||||||
alias BDS.UI.Registry
|
alias BDS.UI.Registry
|
||||||
alias BDS.UI.Workbench
|
alias BDS.UI.Workbench
|
||||||
|
|
||||||
@@ -65,6 +66,14 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
{:noreply, open_sidebar_item(socket, params, :pin)}
|
{:noreply, open_sidebar_item(socket, params, :pin)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_event("shortcut", params, socket) do
|
||||||
|
if ignore_shortcut?(params) do
|
||||||
|
{:noreply, socket}
|
||||||
|
else
|
||||||
|
{:noreply, reload_shell(socket, Commands.handle_shortcut(socket.assigns.workbench, params))}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def handle_event("select_tab", %{"type" => type, "id" => id}, socket) do
|
def handle_event("select_tab", %{"type" => type, "id" => id}, socket) do
|
||||||
workbench =
|
workbench =
|
||||||
Workbench.open_tab(socket.assigns.workbench, String.to_existing_atom(type), id, :preview)
|
Workbench.open_tab(socket.assigns.workbench, String.to_existing_atom(type), id, :preview)
|
||||||
@@ -430,6 +439,14 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
Enum.find(tabs, &(&1.type == type and &1.id == id))
|
Enum.find(tabs, &(&1.type == type and &1.id == id))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp ignore_shortcut?(params) do
|
||||||
|
Map.get(params, "alt", false) or
|
||||||
|
Map.get(params, "contentEditable", false) or
|
||||||
|
Map.get(params, "content_editable", false) or
|
||||||
|
Map.get(params, "tag") in ["INPUT", "TEXTAREA", "SELECT"] or
|
||||||
|
Map.get(params, :tag) in ["INPUT", "TEXTAREA", "SELECT"]
|
||||||
|
end
|
||||||
|
|
||||||
defp open_sidebar_item(socket, params, intent) do
|
defp open_sidebar_item(socket, params, intent) do
|
||||||
route_atom = sidebar_route_atom(Map.fetch!(params, "route"))
|
route_atom = sidebar_route_atom(Map.fetch!(params, "route"))
|
||||||
tab_id = tab_id_for_route(route_atom, Map.fetch!(params, "id"))
|
tab_id = tab_id_for_route(route_atom, Map.fetch!(params, "id"))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="app" id="bds-shell-app">
|
<div class="app" id="bds-shell-app" phx-window-keydown="shortcut">
|
||||||
<div class="window-titlebar" data-region="title-bar">
|
<div class="window-titlebar" data-region="title-bar">
|
||||||
<div class="window-titlebar-menu-bar is-hidden">
|
<div class="window-titlebar-menu-bar is-hidden">
|
||||||
<button class="window-titlebar-menu-button" type="button">File</button>
|
<button class="window-titlebar-menu-button" type="button">File</button>
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ defmodule BDS.UI.Commands do
|
|||||||
|
|
||||||
def handle_shortcut(state, shortcut) when is_map(shortcut) do
|
def handle_shortcut(state, shortcut) when is_map(shortcut) do
|
||||||
key = shortcut |> Map.get(:key, Map.get(shortcut, "key", "")) |> String.downcase()
|
key = shortcut |> Map.get(:key, Map.get(shortcut, "key", "")) |> String.downcase()
|
||||||
primary = Map.get(shortcut, :meta, false) or Map.get(shortcut, :ctrl, false)
|
primary =
|
||||||
|
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
|
case Enum.find(@menu_shortcuts, &shortcut_match?(&1, key, primary)) do
|
||||||
%{id: command_id} -> MenuBar.execute(state, command_id)
|
%{id: command_id} -> MenuBar.execute(state, command_id)
|
||||||
|
|||||||
@@ -32,7 +32,18 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
const liveSocket = new LiveView.LiveSocket("/live", Phoenix.Socket, {
|
const liveSocket = new LiveView.LiveSocket("/live", Phoenix.Socket, {
|
||||||
params: { _csrf_token: csrfToken },
|
params: { _csrf_token: csrfToken },
|
||||||
hooks: Hooks
|
hooks: Hooks,
|
||||||
|
metadata: {
|
||||||
|
keydown: (event) => ({
|
||||||
|
key: event.key,
|
||||||
|
meta: event.metaKey,
|
||||||
|
ctrl: event.ctrlKey,
|
||||||
|
alt: event.altKey,
|
||||||
|
shift: event.shiftKey,
|
||||||
|
tag: event.target?.tagName || null,
|
||||||
|
contentEditable: event.target?.isContentEditable || false
|
||||||
|
})
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
liveSocket.connect();
|
liveSocket.connect();
|
||||||
|
|||||||
@@ -63,6 +63,13 @@ for await (const line of rl) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.command === "press") {
|
||||||
|
await page.keyboard.press(message.shortcut);
|
||||||
|
await page.waitForTimeout(50);
|
||||||
|
console.log(JSON.stringify({ ref, status: "ok", result: "ok" }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (message.command === "screenshot") {
|
if (message.command === "screenshot") {
|
||||||
await page.screenshot({ path: message.path, fullPage: false });
|
await page.screenshot({ path: message.path, fullPage: false });
|
||||||
console.log(JSON.stringify({ ref, status: "ok", result: message.path }));
|
console.log(JSON.stringify({ ref, status: "ok", result: message.path }));
|
||||||
|
|||||||
@@ -45,6 +45,21 @@ defmodule BDS.Desktop.AutomationTest do
|
|||||||
snapshot = Automation.snapshot(session)
|
snapshot = Automation.snapshot(session)
|
||||||
assert snapshot.sidebar_visible == false
|
assert snapshot.sidebar_visible == false
|
||||||
|
|
||||||
|
assert :ok = Automation.press(session, "Meta+B")
|
||||||
|
|
||||||
|
snapshot = Automation.snapshot(session)
|
||||||
|
assert snapshot.sidebar_visible == true
|
||||||
|
|
||||||
|
assert :ok = Automation.press(session, "Meta+J")
|
||||||
|
|
||||||
|
snapshot = Automation.snapshot(session)
|
||||||
|
assert snapshot.panel_visible == true
|
||||||
|
|
||||||
|
assert :ok = Automation.press(session, "Meta+J")
|
||||||
|
|
||||||
|
snapshot = Automation.snapshot(session)
|
||||||
|
assert snapshot.panel_visible == false
|
||||||
|
|
||||||
assert :ok = Automation.click(session, "[data-testid='toggle-assistant']")
|
assert :ok = Automation.click(session, "[data-testid='toggle-assistant']")
|
||||||
|
|
||||||
snapshot = Automation.snapshot(session)
|
snapshot = Automation.snapshot(session)
|
||||||
|
|||||||
@@ -125,4 +125,33 @@ defmodule BDS.Desktop.ShellLiveTest do
|
|||||||
assert String.contains?(html, ">hero.png<")
|
assert String.contains?(html, ">hero.png<")
|
||||||
assert String.contains?(html, ">cover.png<")
|
assert String.contains?(html, ">cover.png<")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "global shortcuts route through the shared command model" do
|
||||||
|
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
|
|
||||||
|
assert html =~ ~s(data-testid="sidebar-shell")
|
||||||
|
assert html =~ ~s(class="panel-shell is-hidden")
|
||||||
|
|
||||||
|
html = render_keydown(view, "shortcut", %{key: "b", meta: true})
|
||||||
|
assert html =~ ~s(class="sidebar-shell is-hidden")
|
||||||
|
|
||||||
|
html = render_keydown(view, "shortcut", %{key: "j", meta: true})
|
||||||
|
refute html =~ ~s(class="panel-shell is-hidden")
|
||||||
|
|
||||||
|
html = render_keydown(view, "shortcut", %{key: "2", meta: true})
|
||||||
|
assert html =~ ~s(data-view="media")
|
||||||
|
|
||||||
|
html =
|
||||||
|
render_click(view, "pin_sidebar_item", %{
|
||||||
|
"route" => "media",
|
||||||
|
"id" => "media-1",
|
||||||
|
"title" => "hero.png",
|
||||||
|
"subtitle" => "12 KB"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ ~s(data-tab-id="media-1")
|
||||||
|
|
||||||
|
html = render_keydown(view, "shortcut", %{key: "w", meta: true})
|
||||||
|
refute html =~ ~s(data-tab-id="media-1")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user