fix: drag-resize working now
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -24,10 +24,18 @@ defmodule BDS.Desktop.Automation do
|
||||
GenServer.call(session, {:click, selector}, @request_timeout)
|
||||
end
|
||||
|
||||
def drag(session, selector, delta_x) when is_binary(selector) and is_integer(delta_x) do
|
||||
GenServer.call(session, {:drag, selector, delta_x}, @request_timeout)
|
||||
end
|
||||
|
||||
def press(session, shortcut) when is_binary(shortcut) do
|
||||
GenServer.call(session, {:press, shortcut}, @request_timeout)
|
||||
end
|
||||
|
||||
def reload(session) do
|
||||
GenServer.call(session, :reload, @request_timeout)
|
||||
end
|
||||
|
||||
def capture_screenshot(session, destination) when is_binary(destination) do
|
||||
GenServer.call(session, {:capture_screenshot, destination}, @request_timeout)
|
||||
end
|
||||
@@ -82,11 +90,23 @@ defmodule BDS.Desktop.Automation do
|
||||
{:reply, normalize_simple_reply(reply), state}
|
||||
end
|
||||
|
||||
def handle_call({:drag, selector, delta_x}, _from, state) do
|
||||
{reply, state} =
|
||||
driver_request(state, %{"command" => "drag", "selector" => selector, "deltaX" => delta_x})
|
||||
|
||||
{:reply, normalize_simple_reply(reply), state}
|
||||
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(:reload, _from, state) do
|
||||
{reply, state} = driver_request(state, %{"command" => "reload"})
|
||||
{:reply, normalize_simple_reply(reply), state}
|
||||
end
|
||||
|
||||
def handle_call({:capture_screenshot, destination}, _from, state) do
|
||||
File.mkdir_p!(Path.dirname(destination))
|
||||
|
||||
|
||||
@@ -66,6 +66,14 @@ defmodule BDS.Desktop.ShellLive do
|
||||
{:noreply, open_sidebar_item(socket, params, :pin)}
|
||||
end
|
||||
|
||||
def handle_event("sync_layout", params, socket) do
|
||||
{:noreply, reload_shell(socket, sync_layout(socket.assigns.workbench, params))}
|
||||
end
|
||||
|
||||
def handle_event("resize_panel", %{"target" => target, "width" => width}, socket) do
|
||||
{:noreply, reload_shell(socket, resize_panel(socket.assigns.workbench, target, width))}
|
||||
end
|
||||
|
||||
def handle_event("shortcut", params, socket) do
|
||||
if ignore_shortcut?(params) do
|
||||
{:noreply, socket}
|
||||
@@ -439,6 +447,44 @@ defmodule BDS.Desktop.ShellLive do
|
||||
Enum.find(tabs, &(&1.type == type and &1.id == id))
|
||||
end
|
||||
|
||||
defp sync_layout(workbench, params) do
|
||||
workbench
|
||||
|> maybe_set_sidebar_width(Map.get(params, "sidebar_width"))
|
||||
|> maybe_set_assistant_width(Map.get(params, "assistant_sidebar_width"))
|
||||
end
|
||||
|
||||
defp resize_panel(workbench, "sidebar", width) do
|
||||
workbench
|
||||
|> Workbench.set_sidebar_width(parse_width(width))
|
||||
|> Map.put(:sidebar_visible, true)
|
||||
end
|
||||
|
||||
defp resize_panel(workbench, "assistant", width) do
|
||||
workbench
|
||||
|> Workbench.set_assistant_sidebar_width(parse_width(width))
|
||||
|> Map.put(:assistant_sidebar_visible, true)
|
||||
end
|
||||
|
||||
defp resize_panel(workbench, _target, _width), do: workbench
|
||||
|
||||
defp maybe_set_sidebar_width(workbench, nil), do: workbench
|
||||
defp maybe_set_sidebar_width(workbench, width), do: Workbench.set_sidebar_width(workbench, parse_width(width))
|
||||
|
||||
defp maybe_set_assistant_width(workbench, nil), do: workbench
|
||||
|
||||
defp maybe_set_assistant_width(workbench, width) do
|
||||
Workbench.set_assistant_sidebar_width(workbench, parse_width(width))
|
||||
end
|
||||
|
||||
defp parse_width(width) when is_integer(width), do: width
|
||||
|
||||
defp parse_width(width) when is_binary(width) do
|
||||
case Integer.parse(width) do
|
||||
{parsed, _rest} -> parsed
|
||||
:error -> 0
|
||||
end
|
||||
end
|
||||
|
||||
defp ignore_shortcut?(params) do
|
||||
Map.get(params, "alt", false) or
|
||||
Map.get(params, "contentEditable", false) or
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="app" id="bds-shell-app" phx-window-keydown="shortcut">
|
||||
<div class="app" id="bds-shell-app" phx-hook="AppShell" phx-window-keydown="shortcut">
|
||||
<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>
|
||||
|
||||
109
priv/ui/live.js
109
priv/ui/live.js
@@ -3,7 +3,116 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
.querySelector("meta[name='csrf-token']")
|
||||
.getAttribute("content");
|
||||
|
||||
const SIDEBAR_STORAGE_KEY = "bds-panel-sidebar";
|
||||
const ASSISTANT_STORAGE_KEY = "bds-panel-assistant-sidebar";
|
||||
|
||||
const clamp = (value, min, max) => Math.max(min, Math.min(value, max));
|
||||
|
||||
const readStoredSize = (key, fallback, min, max) => {
|
||||
const raw = window.localStorage.getItem(key);
|
||||
|
||||
if (!raw) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const parsed = Number.parseInt(raw, 10);
|
||||
|
||||
if (Number.isNaN(parsed)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return clamp(parsed, min, max);
|
||||
};
|
||||
|
||||
const shellWidth = (selector) => {
|
||||
const shell = document.querySelector(selector);
|
||||
|
||||
if (!shell) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const width = Number.parseInt(shell.style.width || "0", 10);
|
||||
return Number.isNaN(width) ? Math.round(shell.getBoundingClientRect().width) : width;
|
||||
};
|
||||
|
||||
const setShellWidth = (selector, width) => {
|
||||
const shell = document.querySelector(selector);
|
||||
|
||||
if (shell) {
|
||||
shell.style.width = `${width}px`;
|
||||
shell.classList.remove("is-hidden");
|
||||
}
|
||||
};
|
||||
|
||||
const persistWidth = (target, width) => {
|
||||
const key = target === "assistant" ? ASSISTANT_STORAGE_KEY : SIDEBAR_STORAGE_KEY;
|
||||
window.localStorage.setItem(key, String(width));
|
||||
};
|
||||
|
||||
const Hooks = {
|
||||
AppShell: {
|
||||
mounted() {
|
||||
this.syncStoredLayout();
|
||||
|
||||
this.handleMouseDown = (event) => {
|
||||
const handle = event.target.closest("[data-role='resize-handle']");
|
||||
|
||||
if (!handle || !this.el.contains(handle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const target = handle.dataset.resize;
|
||||
const startX = event.clientX;
|
||||
const startWidth =
|
||||
target === "assistant"
|
||||
? shellWidth("[data-testid='assistant-shell']")
|
||||
: shellWidth("[data-testid='sidebar-shell']");
|
||||
|
||||
const min = target === "assistant" ? 280 : 200;
|
||||
const max = target === "assistant" ? 640 : 500;
|
||||
const invert = target === "assistant";
|
||||
|
||||
const onMouseMove = (moveEvent) => {
|
||||
const delta = invert ? startX - moveEvent.clientX : moveEvent.clientX - startX;
|
||||
const width = clamp(startWidth + delta, min, max);
|
||||
const selector = target === "assistant" ? "[data-testid='assistant-shell']" : "[data-testid='sidebar-shell']";
|
||||
|
||||
setShellWidth(selector, width);
|
||||
persistWidth(target, width);
|
||||
};
|
||||
|
||||
const onMouseUp = (upEvent) => {
|
||||
const delta = invert ? startX - upEvent.clientX : upEvent.clientX - startX;
|
||||
const width = clamp(startWidth + delta, min, max);
|
||||
|
||||
persistWidth(target, width);
|
||||
this.pushEvent("resize_panel", { target, width });
|
||||
|
||||
window.removeEventListener("mousemove", onMouseMove);
|
||||
window.removeEventListener("mouseup", onMouseUp);
|
||||
};
|
||||
|
||||
window.addEventListener("mousemove", onMouseMove);
|
||||
window.addEventListener("mouseup", onMouseUp);
|
||||
};
|
||||
|
||||
this.el.addEventListener("mousedown", this.handleMouseDown);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
this.el.removeEventListener("mousedown", this.handleMouseDown);
|
||||
},
|
||||
|
||||
syncStoredLayout() {
|
||||
this.pushEvent("sync_layout", {
|
||||
sidebar_width: readStoredSize(SIDEBAR_STORAGE_KEY, shellWidth("[data-testid='sidebar-shell']"), 200, 500),
|
||||
assistant_sidebar_width: readStoredSize(ASSISTANT_STORAGE_KEY, 360, 280, 640)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
SidebarInteractions: {
|
||||
mounted() {
|
||||
this.handleDblClick = (event) => {
|
||||
|
||||
@@ -43,7 +43,9 @@ for await (const line of rl) {
|
||||
window_title: text("[data-testid='window-title']"),
|
||||
active_view: document.querySelector("[data-testid='activity-button'][data-active='true']")?.dataset.view ?? null,
|
||||
sidebar_visible: !hasClass("[data-testid='sidebar-shell']", "is-hidden"),
|
||||
sidebar_width: document.querySelector("[data-testid='sidebar-shell']")?.getBoundingClientRect().width ?? 0,
|
||||
assistant_visible: !hasClass("[data-testid='assistant-shell']", "is-hidden"),
|
||||
assistant_width: document.querySelector("[data-testid='assistant-shell']")?.getBoundingClientRect().width ?? 0,
|
||||
panel_visible: !hasClass(".panel-shell", "is-hidden"),
|
||||
editor_title: text("[data-testid='editor-title']"),
|
||||
activity_labels: texts("[data-testid='activity-button']", (node) => node.getAttribute("aria-label")),
|
||||
@@ -70,6 +72,33 @@ for await (const line of rl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message.command === "drag") {
|
||||
const locator = page.locator(message.selector);
|
||||
const box = await locator.boundingBox();
|
||||
|
||||
if (!box) {
|
||||
throw new Error(`unable to drag missing element: ${message.selector}`);
|
||||
}
|
||||
|
||||
const startX = box.x + box.width / 2;
|
||||
const startY = box.y + box.height / 2;
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(startX + message.deltaX, startY, { steps: 10 });
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(50);
|
||||
console.log(JSON.stringify({ ref, status: "ok", result: "ok" }));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message.command === "reload") {
|
||||
await page.reload({ waitUntil: "networkidle" });
|
||||
await page.locator("#bds-shell-app").waitFor({ state: "visible" });
|
||||
await page.waitForTimeout(100);
|
||||
console.log(JSON.stringify({ ref, status: "ok", result: "ok" }));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message.command === "screenshot") {
|
||||
await page.screenshot({ path: message.path, fullPage: false });
|
||||
console.log(JSON.stringify({ ref, status: "ok", result: message.path }));
|
||||
|
||||
@@ -71,6 +71,34 @@ defmodule BDS.Desktop.AutomationTest do
|
||||
assert File.exists?(screenshot_path)
|
||||
end
|
||||
|
||||
@tag timeout: 120_000
|
||||
test "automation drags sidebar resize handle and restores width after reload" do
|
||||
{:ok, session} = Automation.start_session()
|
||||
|
||||
on_exit(fn ->
|
||||
Automation.stop_session(session)
|
||||
end)
|
||||
|
||||
snapshot = Automation.snapshot(session)
|
||||
assert snapshot.sidebar_width >= 279
|
||||
assert snapshot.sidebar_width <= 281
|
||||
|
||||
assert :ok = Automation.drag(session, "[data-resize='sidebar']", 90)
|
||||
|
||||
snapshot = Automation.snapshot(session)
|
||||
assert snapshot.sidebar_width >= 360
|
||||
assert snapshot.sidebar_width <= 380
|
||||
|
||||
resized_width = snapshot.sidebar_width
|
||||
|
||||
assert :ok = Automation.reload(session)
|
||||
|
||||
snapshot = Automation.snapshot(session)
|
||||
assert snapshot.sidebar_visible == true
|
||||
assert snapshot.sidebar_width >= resized_width - 2
|
||||
assert snapshot.sidebar_width <= resized_width + 2
|
||||
end
|
||||
|
||||
@tag timeout: 120_000
|
||||
test "automation stop_session shuts down the app and browser child processes it started" do
|
||||
baseline = automation_process_counts()
|
||||
|
||||
@@ -169,4 +169,28 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
assert html =~ ~s(class="sidebar-shell is-hidden")
|
||||
assert html =~ ~s(style="width: 0px;")
|
||||
end
|
||||
|
||||
test "layout hooks sync persisted widths and apply drag resizing" do
|
||||
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
assert html =~ ~s(style="width: 280px;")
|
||||
|
||||
html = render_hook(view, "sync_layout", %{"sidebar_width" => 420, "assistant_sidebar_width" => 480})
|
||||
|
||||
assert html =~ ~s(data-testid="sidebar-shell")
|
||||
assert html =~ ~s(style="width: 420px;")
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("[data-testid='toggle-assistant']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ ~s(data-testid="assistant-shell")
|
||||
assert html =~ ~s(style="width: 480px;")
|
||||
|
||||
html = render_hook(view, "resize_panel", %{"target" => "sidebar", "width" => 460})
|
||||
|
||||
assert html =~ ~s(data-testid="sidebar-shell")
|
||||
assert html =~ ~s(style="width: 460px;")
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user