дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
fix(workbench): Keep tool window resizing smooth with a long agent conversation (#133)
Dragging a tool window edge set an inherited custom property on the tool window grid, so every pointer move restyled the whole workbench below it. With a long agent conversation open each move cost 150-400 ms. The grid track variables are now registered as non-inherited, and the side splitter sets flex-grow on its two areas instead of inherited variables on the side.
Цей коміт міститься в:
1 джерело
ecb3134460
коміт
08fd134bdb
3 files changed
+61
-23
No files matched your search
@@ -279,8 +279,13 @@ describe("tool window split areas", () => {
|
||||
loadToolWindowLayout("/split");
|
||||
const { container, unmount } = render(<ToolWindowLayout editor={<div>editor</div>} />);
|
||||
const splitter = container.querySelector('[data-split-handle="left"]')!;
|
||||
const grow = (slot: string) => container.querySelector<HTMLElement>(`[data-slot="${slot}"]`)!.style.flexGrow;
|
||||
expect(splitter).toHaveAttribute("hidden");
|
||||
act(() => showToolWindow("project"));
|
||||
// Alone, the area fills its side whatever the stored ratio.
|
||||
expect(grow("left")).toBe("1");
|
||||
act(() => showToolWindow("structure"));
|
||||
expect(Number(grow("left")) + Number(grow("left-split"))).toBeCloseTo(1);
|
||||
expect(screen.getByText("tree")).toBeVisible();
|
||||
expect(screen.getByText("members")).toBeInTheDocument();
|
||||
expect(splitter).not.toHaveAttribute("hidden");
|
||||
|
||||
@@ -12,6 +12,35 @@
|
||||
.frameNoLeft {
|
||||
padding-left: var(--island-gap);
|
||||
}
|
||||
/*
|
||||
* Only the grid reads its track variables. Not inherited, so dragging a tool window
|
||||
* edge restyles the grid alone instead of every node in the workbench below it.
|
||||
*/
|
||||
@property --tw-left {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-right {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-bottom {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --gap-left {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --gap-right {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --gap-bottom {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
|
||||
.grid {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
@@ -56,13 +85,6 @@
|
||||
.sideRow {
|
||||
flex-direction: row;
|
||||
}
|
||||
/* --split-a / --split-b: the stored ratio (SidePanel), moved directly while the splitter is dragged. */
|
||||
.areaFirst {
|
||||
flex: var(--split-a, 1) 1 0px;
|
||||
}
|
||||
.areaSecond {
|
||||
flex: var(--split-b, 1) 1 0px;
|
||||
}
|
||||
.island[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -696,24 +696,28 @@ const KEY_STEP = 10;
|
||||
/**
|
||||
* Splitter between the primary and split areas of a side: horizontal on the
|
||||
* left and right (areas stacked), vertical at the bottom (side by side).
|
||||
* Moves CSS variables while dragging; the ratio is stored on release.
|
||||
* Moves the areas' flex-grow while dragging; the ratio is stored on release.
|
||||
*/
|
||||
function SplitHandle({ anchor, hidden }: { anchor: ToolWindowAnchor; hidden: boolean }) {
|
||||
const stacked = anchor !== "bottom";
|
||||
const measure = (el: HTMLElement) => {
|
||||
const side = el.parentElement;
|
||||
const first = el.previousElementSibling as HTMLElement | null;
|
||||
const second = el.nextElementSibling as HTMLElement | null;
|
||||
if (!side || !first || !second) return null;
|
||||
if (!first || !second) return null;
|
||||
const size = (e: HTMLElement) => (stacked ? e.offsetHeight : e.offsetWidth);
|
||||
const total = size(first) + size(second);
|
||||
if (total <= 0) return null;
|
||||
const min = Math.min(stacked ? SPLIT_MIN.stacked : SPLIT_MIN.sideBySide, total / 2);
|
||||
return { side, first: size(first), total, clamp: (px: number) => Math.min(total - min, Math.max(min, px)) };
|
||||
return {
|
||||
areas: [first, second] as const,
|
||||
first: size(first),
|
||||
total,
|
||||
clamp: (px: number) => Math.min(total - min, Math.max(min, px)),
|
||||
};
|
||||
};
|
||||
const apply = (side: HTMLElement, ratio: number) => {
|
||||
side.style.setProperty("--split-a", String(ratio));
|
||||
side.style.setProperty("--split-b", String(1 - ratio));
|
||||
const apply = ([first, second]: readonly [HTMLElement, HTMLElement], ratio: number) => {
|
||||
first.style.flexGrow = String(ratio);
|
||||
second.style.flexGrow = String(1 - ratio);
|
||||
};
|
||||
const ratioOf = (px: number, total: number) => Math.min(MAX_SPLIT, Math.max(MIN_SPLIT, px / total));
|
||||
|
||||
@@ -729,7 +733,7 @@ function SplitHandle({ anchor, hidden }: { anchor: ToolWindowAnchor; hidden: boo
|
||||
let ratio: number | null = null;
|
||||
const move = (ev: globalThis.PointerEvent) => {
|
||||
ratio = ratioOf(m.clamp(m.first + (stacked ? ev.clientY : ev.clientX) - origin), m.total);
|
||||
apply(m.side, ratio);
|
||||
apply(m.areas, ratio);
|
||||
};
|
||||
const end = () => {
|
||||
el.removeEventListener("pointermove", move);
|
||||
@@ -738,7 +742,7 @@ function SplitHandle({ anchor, hidden }: { anchor: ToolWindowAnchor; hidden: boo
|
||||
document.body.style.cursor = cursor;
|
||||
if (ratio !== null) setToolWindowSplit(anchor, ratio);
|
||||
// Back to the store's value (unchanged when the ratio was clamped to it).
|
||||
apply(m.side, useToolWindowLayout.getState().splits[anchor]);
|
||||
apply(m.areas, useToolWindowLayout.getState().splits[anchor]);
|
||||
};
|
||||
el.addEventListener("pointermove", move);
|
||||
el.addEventListener("pointerup", end);
|
||||
@@ -782,25 +786,32 @@ function SidePanel({
|
||||
visible: SlotRecord;
|
||||
}) {
|
||||
const mounted = useToolWindowLayout((s) => s.mounted);
|
||||
const split = !!visible[anchor] && !!visible[`${anchor}-split`];
|
||||
// A lone area takes the whole side: flex-grow below 1 would leave the rest empty.
|
||||
const ratio = useToolWindowLayout((s) => s.splits[anchor]);
|
||||
const area = (slot: ToolWindowSlot, className: string | undefined) => {
|
||||
const area = (slot: ToolWindowSlot, grow: number) => {
|
||||
const visibleId = visible[slot];
|
||||
const list = windows.filter((w) => slotOf(w) === slot && (mounted.has(w.id) || w.id === visibleId));
|
||||
if (!list.length) return null;
|
||||
return (
|
||||
<div className={clsx(styles.island, styles.anchor, className)} data-slot={slot} hidden={!visibleId}>
|
||||
<div
|
||||
className={clsx(styles.island, styles.anchor)}
|
||||
// The stored ratio; SplitHandle moves it directly while dragging.
|
||||
style={{ flexGrow: grow }}
|
||||
data-slot={slot}
|
||||
hidden={!visibleId}
|
||||
>
|
||||
{list.map((w) => (
|
||||
<ToolWindowView key={w.id} tw={w} hidden={w.id !== visibleId} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const style = { "--split-a": ratio, "--split-b": 1 - ratio } as CSSProperties;
|
||||
return (
|
||||
<div className={clsx(styles.side, anchor === "bottom" && styles.sideRow)} data-anchor={anchor} style={style}>
|
||||
{area(anchor, styles.areaFirst)}
|
||||
<SplitHandle anchor={anchor} hidden={!visible[anchor] || !visible[`${anchor}-split`]} />
|
||||
{area(`${anchor}-split`, styles.areaSecond)}
|
||||
<div className={clsx(styles.side, anchor === "bottom" && styles.sideRow)} data-anchor={anchor}>
|
||||
{area(anchor, split ? ratio : 1)}
|
||||
<SplitHandle anchor={anchor} hidden={!split} />
|
||||
{area(`${anchor}-split`, split ? 1 - ratio : 1)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Заблокувати користувача