Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ea463f6cf | ||
|
|
7c27a897ac |
@@ -47,6 +47,7 @@ import {
|
|||||||
OPEN_SPAWN_EVENT,
|
OPEN_SPAWN_EVENT,
|
||||||
SESSION_SELECT_EVENT,
|
SESSION_SELECT_EVENT,
|
||||||
SPAWN_SIMILAR_EVENT,
|
SPAWN_SIMILAR_EVENT,
|
||||||
|
requestCloseDrawer,
|
||||||
requestOpenSpawn,
|
requestOpenSpawn,
|
||||||
requestToggleDrawer,
|
requestToggleDrawer,
|
||||||
} from "./uiEvents.js";
|
} from "./uiEvents.js";
|
||||||
@@ -182,6 +183,7 @@ function Shell() {
|
|||||||
(session: Session, opts?: { newTab?: boolean }) => {
|
(session: Session, opts?: { newTab?: boolean }) => {
|
||||||
layoutStore.getState().openSessionBeside(session.id, opts);
|
layoutStore.getState().openSessionBeside(session.id, opts);
|
||||||
navigate({ kind: "attach", sessionId: session.id });
|
navigate({ kind: "attach", sessionId: session.id });
|
||||||
|
requestCloseDrawer();
|
||||||
},
|
},
|
||||||
[layoutStore, navigate],
|
[layoutStore, navigate],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { act, cleanup, fireEvent, render } from "@testing-library/react";
|
import { act, cleanup, fireEvent, render } from "@testing-library/react";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import { OPEN_SPAWN_EVENT, SPAWN_SIMILAR_EVENT } from "../../uiEvents.js";
|
import { CLOSE_DRAWER_EVENT, OPEN_SPAWN_EVENT, SPAWN_SIMILAR_EVENT } from "../../uiEvents.js";
|
||||||
import { TOGGLE_DRAWER_EVENT } from "../../uiEvents.js";
|
import { TOGGLE_DRAWER_EVENT } from "../../uiEvents.js";
|
||||||
import { Drawer } from "./Drawer.js";
|
import { Drawer } from "./Drawer.js";
|
||||||
|
|
||||||
@@ -103,6 +103,19 @@ describe("Drawer (#35)", () => {
|
|||||||
expect(shell().classList.contains("drawer-open")).toBe(false);
|
expect(shell().classList.contains("drawer-open")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("closes on the close-drawer event (after spawn lands)", () => {
|
||||||
|
render(
|
||||||
|
<Drawer sidebar={<div className="tree-leaf">row</div>}>
|
||||||
|
<div>content</div>
|
||||||
|
</Drawer>,
|
||||||
|
);
|
||||||
|
openViaToggle();
|
||||||
|
act(() => {
|
||||||
|
document.dispatchEvent(new CustomEvent(CLOSE_DRAWER_EVENT));
|
||||||
|
});
|
||||||
|
expect(shell().classList.contains("drawer-open")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("closes on a backdrop click", () => {
|
it("closes on a backdrop click", () => {
|
||||||
render(
|
render(
|
||||||
<Drawer sidebar={<div className="tree-leaf">row</div>}>
|
<Drawer sidebar={<div className="tree-leaf">row</div>}>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { OPEN_SPAWN_EVENT, SPAWN_SIMILAR_EVENT, TOGGLE_DRAWER_EVENT } from "../../uiEvents.js";
|
import { CLOSE_DRAWER_EVENT, OPEN_SPAWN_EVENT, SPAWN_SIMILAR_EVENT, TOGGLE_DRAWER_EVENT } from "../../uiEvents.js";
|
||||||
|
|
||||||
export interface DrawerProps {
|
export interface DrawerProps {
|
||||||
sidebar: ReactNode;
|
sidebar: ReactNode;
|
||||||
@@ -28,10 +28,12 @@ export function Drawer({ sidebar, children, className }: DrawerProps) {
|
|||||||
document.addEventListener(TOGGLE_DRAWER_EVENT, onToggle);
|
document.addEventListener(TOGGLE_DRAWER_EVENT, onToggle);
|
||||||
document.addEventListener(OPEN_SPAWN_EVENT, onClose);
|
document.addEventListener(OPEN_SPAWN_EVENT, onClose);
|
||||||
document.addEventListener(SPAWN_SIMILAR_EVENT, onClose);
|
document.addEventListener(SPAWN_SIMILAR_EVENT, onClose);
|
||||||
|
document.addEventListener(CLOSE_DRAWER_EVENT, onClose);
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener(TOGGLE_DRAWER_EVENT, onToggle);
|
document.removeEventListener(TOGGLE_DRAWER_EVENT, onToggle);
|
||||||
document.removeEventListener(OPEN_SPAWN_EVENT, onClose);
|
document.removeEventListener(OPEN_SPAWN_EVENT, onClose);
|
||||||
document.removeEventListener(SPAWN_SIMILAR_EVENT, onClose);
|
document.removeEventListener(SPAWN_SIMILAR_EVENT, onClose);
|
||||||
|
document.removeEventListener(CLOSE_DRAWER_EVENT, onClose);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -333,10 +333,25 @@ export function Sidebar(props: SidebarProps) {
|
|||||||
onCancel={() => setKill(null)}
|
onCancel={() => setKill(null)}
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
const { session: s, force } = kill;
|
const { session: s, force } = kill;
|
||||||
|
// Close the dialog immediately and optimistically remove the
|
||||||
|
// session from the sidebar so the operator isn't staring at a
|
||||||
|
// spinner while the daemon tears down the tmux session.
|
||||||
|
setKill(null);
|
||||||
|
const store = props.store;
|
||||||
|
if (store) {
|
||||||
|
const next = new Map(store.getState().sessions);
|
||||||
|
next.delete(s.id);
|
||||||
|
store.setState({ sessions: next });
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await killSession(s.id, force);
|
await killSession(s.id, force);
|
||||||
setKill(null);
|
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
// Restore the session so it reappears in the sidebar.
|
||||||
|
if (store) {
|
||||||
|
const restored = new Map(store.getState().sessions);
|
||||||
|
restored.set(s.id, s);
|
||||||
|
store.setState({ sessions: restored });
|
||||||
|
}
|
||||||
// The daemon refuses an unforced kill over a worktree holding
|
// The daemon refuses an unforced kill over a worktree holding
|
||||||
// unsaved work; that refusal needs its own prompt
|
// unsaved work; that refusal needs its own prompt
|
||||||
// (docs/WORKTREES.md).
|
// (docs/WORKTREES.md).
|
||||||
@@ -349,7 +364,8 @@ export function Sidebar(props: SidebarProps) {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw e;
|
// Nothing else we can usefully show — the session is back in
|
||||||
|
// the sidebar and the operator can retry.
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -57,6 +57,15 @@ export function requestToggleDrawer(): void {
|
|||||||
document.dispatchEvent(new CustomEvent(TOGGLE_DRAWER_EVENT));
|
document.dispatchEvent(new CustomEvent(TOGGLE_DRAWER_EVENT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Close the mobile drawer unconditionally — used after a spawn lands a new
|
||||||
|
* session so the content pane is visible. Unlike TOGGLE, this is idempotent:
|
||||||
|
* firing it when the drawer is already closed is a no-op. */
|
||||||
|
export const CLOSE_DRAWER_EVENT = "maestro-close-drawer";
|
||||||
|
|
||||||
|
export function requestCloseDrawer(): void {
|
||||||
|
document.dispatchEvent(new CustomEvent(CLOSE_DRAWER_EVENT));
|
||||||
|
}
|
||||||
|
|
||||||
/** Toggle the help overlay from a button click (the `?` key is handled by
|
/** Toggle the help overlay from a button click (the `?` key is handled by
|
||||||
* HelpOverlay's own command set). */
|
* HelpOverlay's own command set). */
|
||||||
export const TOGGLE_HELP_EVENT = "maestro-toggle-help";
|
export const TOGGLE_HELP_EVENT = "maestro-toggle-help";
|
||||||
|
|||||||
Reference in New Issue
Block a user