Skip to content

Commit 52066aa

Browse files
committed
Fix layout close restore behavior
1 parent 37e8409 commit 52066aa

3 files changed

Lines changed: 197 additions & 21 deletions

File tree

anycode/components/layout/Layout.css

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,9 @@
7070
box-shadow: none !important;
7171
}
7272

73-
.dv-scrollable {
74-
border-radius: 8px !important;
75-
}
7673

7774
.layout.dockview-theme-dark .dv-groupview > .dv-tabs-and-actions-container {
7875
background-color: var(--background-color, #242424) !important;
79-
/*border: 1px solid var(--theme-border, #4a4a4a) !important;*/
80-
border-bottom: none !important;
81-
border-top-left-radius: 8px !important;
82-
border-top-right-radius: 8px !important;
8376
margin: 0 !important;
8477
user-select: none;
8578
-webkit-user-select: none;
@@ -140,15 +133,10 @@
140133
width: 14px;
141134
}
142135

143-
.layout .dv-groupview > .dv-content-container {
144-
border-bottom-left-radius: 8px !important;
145-
border-bottom-right-radius: 8px !important;
146-
}
147136

148137
.layout.dockview-theme-dark .dv-tab.dv-active-tab {
149138
background-color: #2a2a2a00 !important;
150139
color: var(--theme-tab-active-foreground, #f0f0f0) !important;
151-
border-radius: 8px !important;
152140
}
153141

154142
.layout.dockview-theme-dark .dv-tab.dv-inactive-tab {
@@ -215,7 +203,6 @@
215203

216204
.dv-groupview {
217205
border: none !important;
218-
border-radius: 8px !important;
219206
}
220207

221208
.layout-dock-panel {
@@ -225,9 +212,6 @@
225212
min-height: 0;
226213
display: flex;
227214
flex-direction: column;
228-
/*border: 1px solid var(--theme-border, #4a4a4a);*/
229-
border-top: none;
230-
border-radius: 0 0 8px 8px;
231215
box-sizing: border-box;
232216
overflow: hidden;
233217
background-color: var(--background-color, #242424);
@@ -252,13 +236,11 @@
252236
.layout .dv-tabs-and-actions-container .dv-right-actions-container:has(.layout-header-actions:hover),
253237
.layout .dv-tabs-and-actions-container .dv-right-actions-container:has(.layout-header-actions:focus-within) {
254238
background-color: var(--background-color, #242424) !important;
255-
border-radius: 8px;
256239
}
257240

258241
.layout-header-action-btn {
259242
background: transparent;
260243
color: #9a9a9a;
261-
border-radius: 6px;
262244
line-height: 1;
263245
width: 22px;
264246
height: 22px;
@@ -396,7 +378,6 @@
396378
border: 1px solid var(--theme-border, #4a4a4a);
397379
background: var(--theme-tab-background, var(--theme-background, var(--background-color, #242424)));
398380
color: var(--theme-tab-foreground, #b2b2b2);
399-
border-radius: 8px;
400381
padding: 8px 10px;
401382
text-align: center;
402383
cursor: pointer;

anycode/components/layout/Layout.tsx

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ type PaneParams = {
5555
};
5656

5757
type SplitDirection = 'row' | 'column';
58+
type DockviewGridNode = DockviewLayout['grid']['root'];
59+
type DockviewLeafNode = {
60+
type: 'leaf';
61+
data: {
62+
id: string;
63+
views: string[];
64+
activeView?: string;
65+
};
66+
};
67+
type DockviewBranchNode = {
68+
type: 'branch';
69+
data: DockviewGridNode[];
70+
};
71+
72+
type LayoutSiblingNode = {
73+
groupIds: string[];
74+
};
75+
76+
type LayoutSiblingInfo = {
77+
direction: 'horizontal' | 'vertical';
78+
siblings: LayoutSiblingNode[];
79+
targetIndex: number;
80+
};
5881

5982
type SplitProps = {
6083
direction: SplitDirection;
@@ -76,6 +99,62 @@ const splitOrientation = (direction: SplitDirection): Orientation => (
7699
direction === 'column' ? Orientation.VERTICAL : Orientation.HORIZONTAL
77100
);
78101

102+
const getNextLayoutDirection = (direction: 'horizontal' | 'vertical'): 'horizontal' | 'vertical' => (
103+
direction === 'horizontal' ? 'vertical' : 'horizontal'
104+
);
105+
106+
const getLayoutDirection = (orientation: Orientation): 'horizontal' | 'vertical' => (
107+
orientation === Orientation.HORIZONTAL ? 'horizontal' : 'vertical'
108+
);
109+
110+
const isDockviewLeafNode = (node: DockviewGridNode): node is DockviewLeafNode => (
111+
node.type === 'leaf'
112+
);
113+
114+
const getLeafGroupIds = (node: DockviewGridNode): string[] => {
115+
if (isDockviewLeafNode(node)) {
116+
return [node.data.id];
117+
}
118+
119+
const branch = node as unknown as DockviewBranchNode;
120+
return branch.data.flatMap(getLeafGroupIds);
121+
};
122+
123+
const findLayoutSiblings = (
124+
node: DockviewGridNode,
125+
targetGroupId: string,
126+
direction: 'horizontal' | 'vertical',
127+
): LayoutSiblingInfo | null => {
128+
if (isDockviewLeafNode(node)) {
129+
return null;
130+
}
131+
132+
const branch = node as unknown as DockviewBranchNode;
133+
const directTargetIndex = branch.data.findIndex((child) => (
134+
isDockviewLeafNode(child) && child.data.id === targetGroupId
135+
));
136+
137+
if (directTargetIndex !== -1) {
138+
return {
139+
direction,
140+
siblings: branch.data.map((child) => ({
141+
groupIds: getLeafGroupIds(child),
142+
})),
143+
targetIndex: directTargetIndex,
144+
};
145+
}
146+
147+
const nextDirection = getNextLayoutDirection(direction);
148+
for (const child of branch.data) {
149+
const nested = findLayoutSiblings(child, targetGroupId, nextDirection);
150+
if (nested) {
151+
return nested;
152+
}
153+
}
154+
155+
return null;
156+
};
157+
79158
export const Split: React.FC<SplitProps> = ({ direction, panes, className }) => {
80159
const apiRef = useRef<SplitviewApi | null>(null);
81160
const containerClassName = useMemo(() => (
@@ -483,7 +562,7 @@ const shouldUseSavedLayout = (layoutState: ReturnType<typeof loadLayoutState>):
483562

484563
return Object.keys(layout.panels).some((panelKey) => {
485564
const layoutPanelId = getLayoutPanelId(panelKey);
486-
return layoutPanelId !== null && layoutPanelId !== 'toolbar';
565+
return layoutPanelId !== null && layoutPanelId !== 'toolbar' && layoutPanelId !== 'picker';
487566
});
488567
};
489568

@@ -741,6 +820,7 @@ export const Layout: React.FC<LayoutProps> = ({
741820
const apiRef = useRef<DockviewApi | null>(null);
742821
const [visibility, setVisibility] = useState<PanelVisibility>(loadPanelVisibility);
743822
const listenersRef = useRef<Array<{ dispose: () => void }>>([]);
823+
const parentGroupMap = useRef<Map<string, string>>(new Map(Object.entries(loadItem<Record<string, string>>('layoutParentGroups') ?? {})));
744824
const layoutSaveTimerRef = useRef<number | null>(null);
745825
const emptyPaneRestoreTimerRef = useRef<number | null>(null);
746826
const isRestoringLayoutRef = useRef<boolean>(false);
@@ -940,6 +1020,20 @@ export const Layout: React.FC<LayoutProps> = ({
9401020
}
9411021
layoutSaveTimerRef.current = window.setTimeout(() => {
9421022
layoutSaveTimerRef.current = null;
1023+
1024+
// Keep parentGroupMap clean by only keeping entries for groups that still exist
1025+
const currentGroupIds = new Set(api.groups.map(g => g.id));
1026+
let mapChanged = false;
1027+
for (const key of parentGroupMap.current.keys()) {
1028+
if (!currentGroupIds.has(key)) {
1029+
parentGroupMap.current.delete(key);
1030+
mapChanged = true;
1031+
}
1032+
}
1033+
if (mapChanged) {
1034+
saveItem('layoutParentGroups', Object.fromEntries(parentGroupMap.current.entries()));
1035+
}
1036+
9431037
const snapshot = getLayoutSnapshot(api);
9441038
if (snapshot === lastLayoutSnapshotRef.current) {
9451039
return;
@@ -1114,6 +1208,11 @@ export const Layout: React.FC<LayoutProps> = ({
11141208
});
11151209
pickerPanel.group.api.setConstraints(PANEL_CONSTRAINTS);
11161210

1211+
if (direction === 'right' || direction === 'below') {
1212+
parentGroupMap.current.set(pickerPanel.group.id, referencePanel.group.id);
1213+
saveItem('layoutParentGroups', Object.fromEntries(parentGroupMap.current.entries()));
1214+
}
1215+
11171216
if (splitSize.width !== undefined || splitSize.height !== undefined) {
11181217
window.requestAnimationFrame(() => {
11191218
referencePanel.group.api.setSize(splitSize);
@@ -1157,7 +1256,96 @@ export const Layout: React.FC<LayoutProps> = ({
11571256
addPickerPanel(api, panel.id, 'within');
11581257
}
11591258

1259+
const group = panel.group;
1260+
const shouldRestoreProportions = group && group.panels.length === 1;
1261+
1262+
interface SiblingGroupSize {
1263+
id: string;
1264+
width: number;
1265+
height: number;
1266+
}
1267+
1268+
let siblingSizes: SiblingGroupSize[] = [];
1269+
let layoutSiblingInfo: LayoutSiblingInfo | null = null;
1270+
let absorberGroupIds: string[] = [];
1271+
let closedGroupSize = 0;
1272+
1273+
if (shouldRestoreProportions) {
1274+
const layout = api.toJSON();
1275+
layoutSiblingInfo = findLayoutSiblings(
1276+
layout.grid.root,
1277+
group.id,
1278+
getLayoutDirection(layout.grid.orientation),
1279+
);
1280+
1281+
if (layoutSiblingInfo) {
1282+
closedGroupSize = layoutSiblingInfo.direction === 'horizontal'
1283+
? group.api.width
1284+
: group.api.height;
1285+
1286+
const siblingGroupIds = layoutSiblingInfo.siblings
1287+
.flatMap((sibling) => sibling.groupIds)
1288+
.filter((id) => id !== group.id);
1289+
1290+
siblingSizes = siblingGroupIds
1291+
.map((id) => {
1292+
const siblingGroup = api.groups.find((g) => g.id === id);
1293+
return siblingGroup
1294+
? {
1295+
id,
1296+
width: siblingGroup.api.width,
1297+
height: siblingGroup.api.height,
1298+
}
1299+
: null;
1300+
})
1301+
.filter((size): size is SiblingGroupSize => size !== null);
1302+
}
1303+
}
1304+
1305+
const parentGroupId = group ? parentGroupMap.current.get(group.id) : undefined;
1306+
1307+
if (layoutSiblingInfo) {
1308+
const parentSibling = parentGroupId
1309+
? layoutSiblingInfo.siblings.find((sibling) => sibling.groupIds.includes(parentGroupId))
1310+
: undefined;
1311+
const adjacentSibling = layoutSiblingInfo.siblings[
1312+
layoutSiblingInfo.targetIndex > 0
1313+
? layoutSiblingInfo.targetIndex - 1
1314+
: layoutSiblingInfo.targetIndex + 1
1315+
];
1316+
absorberGroupIds = (parentSibling ?? adjacentSibling)?.groupIds ?? [];
1317+
}
1318+
1319+
if (group) {
1320+
parentGroupMap.current.delete(group.id);
1321+
saveItem('layoutParentGroups', Object.fromEntries(parentGroupMap.current.entries()));
1322+
}
1323+
11601324
panel.api.close();
1325+
1326+
if (layoutSiblingInfo && siblingSizes.length > 0 && absorberGroupIds.length > 0) {
1327+
window.requestAnimationFrame(() => {
1328+
siblingSizes.forEach((sibling) => {
1329+
const remainingGroup = api.groups.find((g) => g.id === sibling.id);
1330+
if (!remainingGroup) return;
1331+
1332+
const isAbsorber = absorberGroupIds.includes(sibling.id);
1333+
const targetSize: { width?: number; height?: number } = {};
1334+
1335+
if (layoutSiblingInfo.direction === 'horizontal') {
1336+
targetSize.width = isAbsorber
1337+
? sibling.width + closedGroupSize
1338+
: sibling.width;
1339+
} else {
1340+
targetSize.height = isAbsorber
1341+
? sibling.height + closedGroupSize
1342+
: sibling.height;
1343+
}
1344+
1345+
remainingGroup.api.setSize(targetSize);
1346+
});
1347+
});
1348+
}
11611349
}, [addPickerPanel]);
11621350

11631351
useEffect(() => {
@@ -1296,6 +1484,10 @@ export const Layout: React.FC<LayoutProps> = ({
12961484
isRestoringLayoutRef.current = false;
12971485
}
12981486

1487+
if (api.totalPanels === 0) {
1488+
addRootPickerPanel(api, handleSelectPanelFromPicker).api.setActive();
1489+
}
1490+
12991491
if (!restoredSavedLayout) {
13001492
api.getPanel('files')?.api.setActive();
13011493
api.getPanel('editor')?.api.setActive();

anycode/components/layout/layoutState.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type LayoutNode = LayoutGroup | LayoutContainer;
1717

1818
export type LayoutGroup = {
1919
type: 'group';
20+
id?: string;
2021
panels: LayoutPanel[];
2122
activePanelKey?: string;
2223
};
@@ -39,6 +40,7 @@ type DockviewGridNode = DockviewLayout['grid']['root'];
3940
type DockviewLeafNode = {
4041
type: 'leaf';
4142
data: {
43+
id: string;
4244
views: string[];
4345
activeView?: string;
4446
};
@@ -83,6 +85,7 @@ const createLayoutNode = (
8385
const group = (node as unknown as DockviewLeafNode).data;
8486
return {
8587
type: 'group',
88+
id: group.id,
8689
panels: group.views
8790
.map((key: string) => {
8891
const id = getPanelId(key);
@@ -147,7 +150,7 @@ const createDockviewNode = (
147150
): DockviewGridNode => {
148151
if (node.type === 'group') {
149152
const views = node.panels.map((panel) => panel.key);
150-
const groupId = nextGroupId();
153+
const groupId = node.id || nextGroupId();
151154

152155
node.panels.forEach((panel) => {
153156
panels[panel.key] = {

0 commit comments

Comments
 (0)