дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
fix(toolwindows): let a maximized tool window fill the workbench (#2176)
Maximize Tool Window sized a window to a percentage, which left a strip of the editor and could shrink a window already larger. A maximized tool window now fills the workbench and hides the editor and other anchors, keeping saved sizes and split ratios for the restore. Opening or focusing an editor restores the layout, and closing the window returns focus to the editor. Fixes #2162
Цей коміт міститься в:
1 джерело
6f9279e753
коміт
1a06f6e9cd
10 files changed
+227
-21
No files matched your search
@@ -56,10 +56,7 @@ test("skips binary and unchanged siblings and restores after changing preview fi
|
||||
const toolbar = await openChain(page);
|
||||
await page.evaluate(async () => {
|
||||
const inputPath = "/src/features/vcs/diffInput.ts";
|
||||
const layoutPath = "/src/workbench/toolwindows/store.ts";
|
||||
const { openDiff, withChain } = await import(inputPath);
|
||||
const { useToolWindowLayout } = await import(layoutPath);
|
||||
useToolWindowLayout.setState({ maximized: "left" });
|
||||
const files = [
|
||||
["a.ts", "after\n"],
|
||||
["binary.dat", "\0binary"],
|
||||
@@ -73,7 +70,13 @@ test("skips binary and unchanged siblings and restores after changing preview fi
|
||||
await openDiff(withChain(files, 0), { preview: true });
|
||||
});
|
||||
await expect(toolbar).toContainText("1 difference");
|
||||
await toolbar.getByRole("button", { name: "Maximize Editor", exact: true }).click();
|
||||
await page.evaluate(async () => {
|
||||
const layoutPath = "/src/workbench/toolwindows/store.ts";
|
||||
const { useToolWindowLayout, hideAllToolWindows } = await import(layoutPath);
|
||||
useToolWindowLayout.setState({ maximized: "left" });
|
||||
hideAllToolWindows();
|
||||
});
|
||||
await toolbar.locator("..").locator(".cm-content").last().focus();
|
||||
const selected = page.getByRole("tablist", { name: "Editor tabs" }).locator('[aria-selected="true"]');
|
||||
await page.keyboard.press("F7");
|
||||
await page.keyboard.press("F7");
|
||||
@@ -90,5 +93,6 @@ test("skips binary and unchanged siblings and restores after changing preview fi
|
||||
return (await import(layoutPath)).useToolWindowLayout.getState().maximized;
|
||||
});
|
||||
expect(maximized).toBe("left");
|
||||
await expect(toolbar).toBeHidden();
|
||||
await expect(page.getByRole("tree", { name: "Project", exact: true })).toBeVisible();
|
||||
});
|
||||
@@ -1,5 +1,64 @@
|
||||
import { expect, ipcCalls, stubDesktop, test, visibleToolWindows } from "./fixtures";
|
||||
|
||||
for (const anchor of ["left", "right", "bottom"] as const) {
|
||||
test(`maximized ${anchor} Terminal fills the workbench and restores the layout`, async ({ workbench: page }) => {
|
||||
await page.getByRole("treeitem", { name: "README.md", exact: true }).dblclick();
|
||||
await expect(page.locator("[data-editor-area] .cm-content")).toBeFocused();
|
||||
await page.evaluate(async (anchor) => {
|
||||
const path = "/src/workbench/toolwindows/store.ts";
|
||||
const layout = await import(path);
|
||||
layout.moveToolWindow("terminal", anchor);
|
||||
layout.setToolWindowSize(anchor, 650);
|
||||
layout.showToolWindow("terminal", true);
|
||||
}, anchor);
|
||||
const terminal = page.getByRole("region", { name: "Terminal", exact: true });
|
||||
const editor = page.locator("[data-editor-area]");
|
||||
const input = terminal.getByRole("textbox", { name: "Terminal input" });
|
||||
await expect(input).toBeFocused();
|
||||
const before = (await terminal.boundingBox())!;
|
||||
const editorBefore = (await editor.boundingBox())!;
|
||||
await page.keyboard.press("Meta+Shift+'");
|
||||
await expect(editor).toBeHidden();
|
||||
await expect(input).toBeFocused();
|
||||
const fillsGrid = async () =>
|
||||
terminal.evaluate((el) => {
|
||||
const panel = el.getBoundingClientRect();
|
||||
const grid = el.closest("[data-maximized]")!.getBoundingClientRect();
|
||||
return Math.abs(panel.width - grid.width) + Math.abs(panel.height - grid.height);
|
||||
});
|
||||
await expect.poll(fillsGrid).toBeLessThan(2);
|
||||
await page.setViewportSize({ width: 1100, height: 750 });
|
||||
await expect.poll(fillsGrid).toBeLessThan(2);
|
||||
await page.setViewportSize({ width: 1400, height: 900 });
|
||||
await terminal.getByRole("button", { name: "Options", exact: true }).click();
|
||||
await page.getByRole("menuitem", { name: "Restore Size", exact: true }).click();
|
||||
await expect(editor).toBeVisible();
|
||||
await expect.poll(async () => Math.abs((await terminal.boundingBox())!.width - before.width)).toBeLessThan(2);
|
||||
await expect.poll(async () => Math.abs((await terminal.boundingBox())!.height - before.height)).toBeLessThan(2);
|
||||
await expect.poll(async () => Math.abs((await editor.boundingBox())!.width - editorBefore.width)).toBeLessThan(2);
|
||||
await terminal.getByRole("button", { name: "Options", exact: true }).click();
|
||||
await page.getByRole("menuitem", { name: "Maximize", exact: true }).click();
|
||||
await expect(editor).toBeHidden();
|
||||
const title = terminal.locator("header").first().getByText("Terminal", { exact: true });
|
||||
await title.dblclick();
|
||||
await expect(editor).toBeVisible();
|
||||
await title.dblclick();
|
||||
await expect(editor).toBeHidden();
|
||||
await page.evaluate(async () => {
|
||||
const path = "/src/workbench/contributions.ts";
|
||||
await (await import(path)).editorService().openFile("/dev/acme-api/README.md");
|
||||
});
|
||||
await expect(editor).toBeVisible();
|
||||
await expect(editor.locator(".cm-content")).toBeFocused();
|
||||
await input.focus();
|
||||
await page.keyboard.press("Meta+Shift+'");
|
||||
await expect(editor).toBeHidden();
|
||||
await terminal.getByRole("button", { name: "Hide", exact: true }).click();
|
||||
await expect(editor).toBeVisible();
|
||||
await expect(editor.locator(".cm-content")).toBeFocused();
|
||||
});
|
||||
}
|
||||
|
||||
test("⌘1 focuses, hides and re-shows the Project tool window", async ({ workbench: page }) => {
|
||||
const project = page.getByRole("region", { name: "Project" });
|
||||
await page.keyboard.press("Meta+1");
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
type TextEditorWrapperContribution,
|
||||
} from "@/workbench/contributions";
|
||||
import { useDistractionFree } from "@/workbench/distractionFree";
|
||||
import { restoreMaximizedToolWindow } from "@/workbench/toolwindows/store";
|
||||
import { ask } from "@/workbench/messages";
|
||||
import { t } from "@/platform/i18n";
|
||||
import { call, commands } from "@/platform/ipc";
|
||||
@@ -1071,6 +1072,7 @@ export class EditorController implements SlotHost {
|
||||
try {
|
||||
let s = useTabs.getState();
|
||||
const activate = this.isCurrentOpening(generation);
|
||||
if (activate) restoreMaximizedToolWindow();
|
||||
const preview =
|
||||
!!o.preview &&
|
||||
editorPrefs().tabs.previewTab &&
|
||||
@@ -1180,9 +1182,14 @@ export class EditorController implements SlotHost {
|
||||
}
|
||||
|
||||
activate(paneId: string, tabId: string, focus = true) {
|
||||
const restored = restoreMaximizedToolWindow();
|
||||
++this.activationGeneration;
|
||||
useTabs.setState((s) => activateTab(setActivePane(s, paneId), paneId, tabId), true);
|
||||
if (focus) {
|
||||
if (restored) {
|
||||
requestAnimationFrame(() => this.focus());
|
||||
return;
|
||||
}
|
||||
const slot = this.slots.get(tabId);
|
||||
if (slot?.view) slot.focus();
|
||||
else if (slot) slot.wantsFocus = true;
|
||||
@@ -1594,6 +1601,10 @@ export class EditorController implements SlotHost {
|
||||
// --- service helpers ------------------------------------------------------------------
|
||||
|
||||
focus() {
|
||||
if (restoreMaximizedToolWindow()) {
|
||||
requestAnimationFrame(() => this.focus());
|
||||
return;
|
||||
}
|
||||
const tab = this.activeTabOf();
|
||||
if (!tab) {
|
||||
document.querySelector<HTMLElement>("[data-editor-area]")?.focus({ preventScroll: true });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { setContext } from "@/platform/context";
|
||||
import { EDITOR_AREA_SELECTOR, installFocusTracking } from "./focus";
|
||||
import { EDITOR_AREA_SELECTOR, focusEditor, installFocusTracking } from "./focus";
|
||||
|
||||
describe("focus after popups", () => {
|
||||
afterEach(() => {
|
||||
@@ -14,6 +14,18 @@ describe("focus after popups", () => {
|
||||
return document.querySelector<HTMLElement>(`${EDITOR_AREA_SELECTOR} .cm-content`)!;
|
||||
}
|
||||
|
||||
it("waits for the restored editor to become visible before focusing it", () => {
|
||||
vi.useFakeTimers();
|
||||
const editor = setup();
|
||||
const area = editor.parentElement!;
|
||||
area.hidden = true;
|
||||
focusEditor();
|
||||
expect(document.activeElement).not.toBe(editor);
|
||||
area.hidden = false;
|
||||
vi.runAllTimers();
|
||||
expect(document.activeElement).toBe(editor);
|
||||
});
|
||||
|
||||
it("returns focus to the editor when the last popup closes and focus was left nowhere", () => {
|
||||
vi.useFakeTimers();
|
||||
const editor = setup();
|
||||
|
||||
@@ -6,6 +6,15 @@ export const EDITOR_AREA_SELECTOR = "[data-editor-area]";
|
||||
|
||||
/** Moves focus to the active editor (or the editor area when none is open). */
|
||||
export function focusEditor() {
|
||||
if (typeof document !== "undefined" && document.querySelector(EDITOR_AREA_SELECTOR)?.closest("[hidden]")) {
|
||||
// Restoring the layout commits the editor's visibility on the next render.
|
||||
requestAnimationFrame(focusEditorNow);
|
||||
return;
|
||||
}
|
||||
focusEditorNow();
|
||||
}
|
||||
|
||||
function focusEditorNow() {
|
||||
const service = maybeEditorService();
|
||||
if (service?.focus) {
|
||||
service.focus();
|
||||
|
||||
@@ -73,6 +73,14 @@
|
||||
.editorIsland {
|
||||
grid-area: editor;
|
||||
}
|
||||
.grid[data-maximized] {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: minmax(0, 1fr);
|
||||
grid-template-areas: none;
|
||||
}
|
||||
.grid[data-maximized] > [data-anchor] {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
|
||||
/* A side: primary and split areas, each an island; stacked on the left and right, side by side at the bottom. */
|
||||
.side {
|
||||
@@ -84,7 +92,8 @@
|
||||
.sideRow {
|
||||
flex-direction: row;
|
||||
}
|
||||
.island[hidden] {
|
||||
.island[hidden],
|
||||
.side[hidden] {
|
||||
display: none;
|
||||
}
|
||||
.side > .splitGap {
|
||||
|
||||
@@ -78,14 +78,18 @@ export function useToolWindows(): ToolWindowContribution[] {
|
||||
/** Visible window per area, ignoring ids that aren't registered (yet) or don't dock there. */
|
||||
export function useEffectiveVisible(windows: ToolWindowContribution[]): SlotRecord {
|
||||
const visible = useToolWindowLayout((s) => s.visible);
|
||||
const maximized = useToolWindowLayout((s) => s.maximized);
|
||||
return useMemo(() => {
|
||||
const out = {} as SlotRecord;
|
||||
for (const slot of SLOTS) {
|
||||
const id = visible[slot];
|
||||
out[slot] = id && windows.some((w) => w.id === id && slotOf(w) === slot) ? id : null;
|
||||
out[slot] =
|
||||
id && windows.some((w) => w.id === id && slotOf(w) === slot && (!maximized || w.anchor === maximized))
|
||||
? id
|
||||
: null;
|
||||
}
|
||||
return out;
|
||||
}, [visible, windows]);
|
||||
}, [visible, windows, maximized]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -710,7 +714,10 @@ function HeaderRow({
|
||||
return (
|
||||
<header
|
||||
className={clsx(styles.header, tw.headerContent && styles.headerTabbed)}
|
||||
onDoubleClick={(e) => e.target === e.currentTarget && toggleMaximizeToolWindow(tw.id)}
|
||||
onDoubleClick={(e) => {
|
||||
if (!(e.target as Element).closest('button, a, input, [role="tablist"], [role="separator"]'))
|
||||
toggleMaximizeToolWindow(tw.id);
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
// A middle click on the header hides the window (like the Hide button).
|
||||
// Not a press on the header's tabs or buttons, which close and act
|
||||
@@ -837,7 +844,6 @@ function ToolWindowView({ tw, hidden }: { tw: ToolWindowContribution; hidden: bo
|
||||
}
|
||||
|
||||
const MIN: Record<ToolWindowAnchor, number> = { left: 140, right: 160, bottom: 90 };
|
||||
const MAXIMIZED: Record<ToolWindowAnchor, string> = { left: "82%", right: "82%", bottom: "88%" };
|
||||
/** The editor keeps at least this much room when a tool window is resized (see the grid template). */
|
||||
const EDITOR_MIN = { width: 200, height: 60 };
|
||||
/** Smallest area when a side is split: stacked (left / right) and side by side (bottom). */
|
||||
@@ -960,7 +966,11 @@ function SidePanel({
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div className={clsx(styles.side, anchor === "bottom" && styles.sideRow)} data-anchor={anchor}>
|
||||
<div
|
||||
className={clsx(styles.side, anchor === "bottom" && styles.sideRow)}
|
||||
data-anchor={anchor}
|
||||
hidden={!sideVisible(visible, anchor)}
|
||||
>
|
||||
{area(anchor, split ? ratio : 1)}
|
||||
<SplitHandle anchor={anchor} hidden={!split} />
|
||||
{area(`${anchor}-split`, split ? 1 - ratio : 1)}
|
||||
@@ -980,7 +990,7 @@ function syncTracks(grid: HTMLElement, visible: SlotRecord) {
|
||||
const s = useToolWindowLayout.getState();
|
||||
for (const a of ["left", "right", "bottom"] as const) {
|
||||
const shown = sideVisible(visible, a);
|
||||
const size = !shown ? "0px" : s.maximized === a ? MAXIMIZED[a] : `${s.sizes[a]}px`;
|
||||
const size = !shown ? "0px" : `${s.sizes[a]}px`;
|
||||
grid.style.setProperty(`--tw-${a}`, size);
|
||||
grid.style.setProperty(`--gap-${a}`, shown ? "var(--island-gap)" : "0px");
|
||||
}
|
||||
@@ -1005,6 +1015,7 @@ function GapHandle({
|
||||
sync: () => void;
|
||||
}) {
|
||||
const vertical = anchor !== "bottom";
|
||||
const maximized = useToolWindowLayout((s) => s.maximized);
|
||||
const sign = anchor === "left" ? 1 : -1;
|
||||
const measure = () => {
|
||||
const island = gridRef.current?.querySelector<HTMLElement>(`[data-anchor="${anchor}"]`);
|
||||
@@ -1070,7 +1081,7 @@ function GapHandle({
|
||||
aria-orientation={vertical ? "vertical" : "horizontal"}
|
||||
aria-label={`Resize ${anchor} tool window`}
|
||||
tabIndex={0}
|
||||
hidden={!visibleIds.length}
|
||||
hidden={!visibleIds.length || !!maximized}
|
||||
className={styles.gap}
|
||||
data-gap={anchor}
|
||||
onPointerDown={onPointerDown}
|
||||
@@ -1151,6 +1162,11 @@ export function ToolWindowLayout({ editor }: { editor: ReactNode }) {
|
||||
if (gridRef.current) syncTracks(gridRef.current, visible);
|
||||
};
|
||||
useLayoutEffect(sync, [visible, sizes, maximized]);
|
||||
useLayoutEffect(() => {
|
||||
if (!maximized || !editorRef.current?.contains(document.activeElement)) return;
|
||||
const id = visibleOn(visible, maximized)[0];
|
||||
if (id) gridRef.current?.querySelector<HTMLElement>(`[data-toolwindow="${id}"]`)?.focus({ preventScroll: true });
|
||||
}, [maximized, visible]);
|
||||
|
||||
const gap = (anchor: ToolWindowAnchor) => (
|
||||
<GapHandle
|
||||
@@ -1168,10 +1184,14 @@ export function ToolWindowLayout({ editor }: { editor: ReactNode }) {
|
||||
>
|
||||
{bars && stripe("left")}
|
||||
{bars && stripeHandle("left")}
|
||||
<div ref={gridRef} className={clsx(styles.grid, widescreen && styles.widescreen)}>
|
||||
<div
|
||||
ref={gridRef}
|
||||
className={clsx(styles.grid, widescreen && styles.widescreen)}
|
||||
data-maximized={maximized ?? undefined}
|
||||
>
|
||||
<SidePanel anchor="left" windows={windows} visible={visible} />
|
||||
{gap("left")}
|
||||
<div ref={editorRef} className={clsx(styles.island, styles.editorIsland)}>
|
||||
<div ref={editorRef} className={clsx(styles.island, styles.editorIsland)} hidden={!!maximized}>
|
||||
{editor}
|
||||
</div>
|
||||
{gap("right")}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
||||
import { Tabs } from "@/ui";
|
||||
import { registerToolWindow } from "../contributions";
|
||||
import { isToolWindowVisible, resetToolWindowLayout, showToolWindow } from "./store";
|
||||
import { isToolWindowVisible, resetToolWindowLayout, showToolWindow, useToolWindowLayout } from "./store";
|
||||
import { ToolWindowLayout } from "./ToolWindows";
|
||||
|
||||
const Empty = () => null;
|
||||
@@ -70,3 +70,18 @@ it("middle click on a header content tab closes the tab, not the window", () =>
|
||||
expect(closeContentTab).toHaveBeenCalledWith("file");
|
||||
expect(isToolWindowVisible("problems")).toBe(true);
|
||||
});
|
||||
|
||||
it("header double click hides the editor and restores its previous size", () => {
|
||||
showToolWindow("run");
|
||||
render(<ToolWindowLayout editor={<input aria-label="Editor" />} />);
|
||||
const editor = screen.getByRole("textbox", { name: "Editor" });
|
||||
const sizes = useToolWindowLayout.getState().sizes;
|
||||
editor.focus();
|
||||
fireEvent.doubleClick(header("run"));
|
||||
expect(editor).not.toBeVisible();
|
||||
expect(editor).not.toHaveFocus();
|
||||
expect(useToolWindowLayout.getState().focusRequest?.id).toBe("run");
|
||||
fireEvent.doubleClick(header("run"));
|
||||
expect(editor).toBeVisible();
|
||||
expect(useToolWindowLayout.getState().sizes).toEqual(sizes);
|
||||
});
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
toggleMaximizeToolWindow,
|
||||
sanitizePlacements,
|
||||
resetToolWindowLayout,
|
||||
restoreMaximizedToolWindow,
|
||||
setFocusedToolWindow,
|
||||
setToolWindowSize,
|
||||
showToolWindow,
|
||||
@@ -140,6 +141,55 @@ describe("tool window semantics", () => {
|
||||
});
|
||||
|
||||
describe("tool window persistence", () => {
|
||||
it.each(["left", "right", "bottom"] as const)("preserves the %s layout through maximize and reload", (anchor) => {
|
||||
moveToolWindow("terminal", anchor);
|
||||
moveToolWindow("problems", `${anchor}-split`);
|
||||
showToolWindow("terminal");
|
||||
showToolWindow("problems");
|
||||
setToolWindowSize(anchor, 987);
|
||||
setToolWindowSplit(anchor, 0.63);
|
||||
const before = useToolWindowLayout.getState();
|
||||
toggleMaximizeToolWindow("terminal");
|
||||
setToolWindowSize(anchor, 111);
|
||||
expect(isToolWindowVisible("terminal")).toBe(true);
|
||||
expect(isToolWindowVisible("problems")).toBe(true);
|
||||
if (anchor !== "left") {
|
||||
expect(isToolWindowVisible("project")).toBe(false);
|
||||
expect(getContext()["toolWindow.project.visible"]).toBe(false);
|
||||
}
|
||||
toggleMaximizeToolWindow("terminal");
|
||||
expect(useToolWindowLayout.getState()).toMatchObject({
|
||||
visible: before.visible,
|
||||
sizes: before.sizes,
|
||||
splits: before.splits,
|
||||
});
|
||||
toggleMaximizeToolWindow("terminal");
|
||||
// Saving another layout choice while maximized must retain all the original anchors.
|
||||
setToolWindowSplit(anchor, 0.63);
|
||||
showToolWindow("terminal");
|
||||
loadToolWindowLayout("/work/a");
|
||||
expect(useToolWindowLayout.getState()).toMatchObject({
|
||||
visible: before.visible,
|
||||
sizes: before.sizes,
|
||||
splits: before.splits,
|
||||
maximized: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("restores other anchors on navigation, opening another side, or closing the maximized window", () => {
|
||||
showToolWindow("terminal");
|
||||
toggleMaximizeToolWindow("terminal");
|
||||
restoreMaximizedToolWindow();
|
||||
expect(isToolWindowVisible("project")).toBe(true);
|
||||
toggleMaximizeToolWindow("terminal");
|
||||
showToolWindow("project", true);
|
||||
expect(useToolWindowLayout.getState().maximized).toBeNull();
|
||||
toggleMaximizeToolWindow("terminal");
|
||||
hideToolWindow("terminal");
|
||||
expect(useToolWindowLayout.getState().maximized).toBeNull();
|
||||
expect(isToolWindowVisible("project")).toBe(true);
|
||||
});
|
||||
|
||||
it("persists visible windows and sizes per project root", () => {
|
||||
showToolWindow("structure");
|
||||
showToolWindow("terminal");
|
||||
@@ -273,8 +323,10 @@ describe("split areas", () => {
|
||||
it("keeps the side maximized while its other area shows a window", () => {
|
||||
showToolWindow("bookmarks");
|
||||
toggleMaximizeToolWindow("project");
|
||||
setFocusedToolWindow("bookmarks");
|
||||
hideToolWindow("bookmarks");
|
||||
expect(useToolWindowLayout.getState().maximized).toBe("left");
|
||||
expect(useToolWindowLayout.getState().focusRequest?.id).toBe("project");
|
||||
hideToolWindow("project");
|
||||
expect(useToolWindowLayout.getState().maximized).toBeNull();
|
||||
});
|
||||
|
||||
@@ -365,9 +365,13 @@ export function installToolWindowLayoutSync(): () => void {
|
||||
}
|
||||
|
||||
function publishVisibility() {
|
||||
const { visible, placements } = useToolWindowLayout.getState();
|
||||
const { visible, placements, maximized } = useToolWindowLayout.getState();
|
||||
for (const tw of useContributions.getState().toolWindows.values()) {
|
||||
setContext(`toolWindow.${tw.id}.visible`, visible[slotOf(placed(tw, placements))] === tw.id);
|
||||
const window = placed(tw, placements);
|
||||
setContext(
|
||||
`toolWindow.${tw.id}.visible`,
|
||||
(!maximized || maximized === window.anchor) && visible[slotOf(window)] === tw.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +383,8 @@ function update(patch: Partial<ToolWindowLayoutState>, save = true) {
|
||||
|
||||
export function isToolWindowVisible(id: string): boolean {
|
||||
const slot = slotOfId(id);
|
||||
return !!slot && useToolWindowLayout.getState().visible[slot] === id;
|
||||
const { visible, maximized } = useToolWindowLayout.getState();
|
||||
return !!slot && (!maximized || maximized === slotAnchor(slot)) && visible[slot] === id;
|
||||
}
|
||||
|
||||
/** Last shown or focused tool window, kept after it is hidden (Jump to Last Tool Window). */
|
||||
@@ -426,7 +431,10 @@ export function hideToolWindow(id: string) {
|
||||
maximized: s.maximized === slotAnchor(slot) && sideEmpty ? null : s.maximized,
|
||||
lastActive: s.lastActive === id ? null : s.lastActive,
|
||||
});
|
||||
if (hadFocus) focusEditor();
|
||||
if (hadFocus) {
|
||||
if (s.maximized === slotAnchor(slot) && !sideEmpty) focusToolWindow(s.visible[partnerSlot(slot)]!);
|
||||
else focusEditor();
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether `tw` has a stripe button: the user's choice, else its default. */
|
||||
@@ -552,7 +560,14 @@ export function toggleMaximizeToolWindow(id: string) {
|
||||
if (!a) return;
|
||||
if (!isToolWindowVisible(id)) showToolWindow(id, true);
|
||||
const s = useToolWindowLayout.getState();
|
||||
update({ maximized: s.maximized === a ? null : a }, false);
|
||||
update({ maximized: s.maximized === a ? null : a, focusRequest: { id, seq: ++focusSeq } }, false);
|
||||
}
|
||||
|
||||
/** Reveals the editor when navigation opens or focuses an editor tab. */
|
||||
export function restoreMaximizedToolWindow() {
|
||||
const maximized = !!useToolWindowLayout.getState().maximized;
|
||||
if (maximized) update({ maximized: null }, false);
|
||||
return maximized;
|
||||
}
|
||||
|
||||
export function setFocusedToolWindow(id: string | null) {
|
||||
|
||||
Reference in new issue
Заблокувати користувача