Skip to content

Commit e3cda20

Browse files
feat: experimental work - reuseExistingPanels
Original experimental work for persistent unused panels (always keep-alive) Adds reuseExistingPanels option to fromJSON() method to preserve existing panel instances Rebased from commit 97e8a95 (Nov 30, 2024) onto current master (v4.11.0) to resolve conflicts and integrate with latest codebase changes. Key changes during rebase: - Combined MoveGroupOrPanelOptions to include both skipSetActive and keepEmptyGroups options - Updated to use movingLock() method matching current upstream conventions - Removed readonly modifiers from constraint properties to support updateFromStateModel - Preserved isDestinationGroupEmpty check logic from master - Integrated new test cases for reuseExistingPanels functionality Co-authored-by: David Tsai <david.tsai@skydio.com> Original-Author: mathuo <6710312+mathuo@users.noreply.github.com> Related-Branch: upstream/718-persistent-unused-panels-always-keep-alive
1 parent 101ccef commit e3cda20

File tree

7 files changed

+444
-67
lines changed

7 files changed

+444
-67
lines changed

packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts

Lines changed: 263 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,6 +1138,237 @@ describe('dockviewComponent', () => {
11381138
});
11391139

11401140
describe('serialization', () => {
1141+
test('reuseExistingPanels true', () => {
1142+
const parts: PanelContentPartTest[] = [];
1143+
1144+
dockview = new DockviewComponent(container, {
1145+
createComponent(options) {
1146+
switch (options.name) {
1147+
case 'default':
1148+
const part = new PanelContentPartTest(
1149+
options.id,
1150+
options.name
1151+
);
1152+
parts.push(part);
1153+
return part;
1154+
default:
1155+
throw new Error(`unsupported`);
1156+
}
1157+
},
1158+
});
1159+
1160+
dockview.layout(1000, 1000);
1161+
1162+
dockview.addPanel({ id: 'panel1', component: 'default' });
1163+
dockview.addPanel({ id: 'panel2', component: 'default' });
1164+
dockview.addPanel({ id: 'panel7', component: 'default' });
1165+
1166+
expect(parts.length).toBe(3);
1167+
1168+
expect(parts.map((part) => part.isDisposed)).toEqual([
1169+
false,
1170+
false,
1171+
false,
1172+
]);
1173+
1174+
dockview.fromJSON(
1175+
{
1176+
activeGroup: 'group-1',
1177+
grid: {
1178+
root: {
1179+
type: 'branch',
1180+
data: [
1181+
{
1182+
type: 'leaf',
1183+
data: {
1184+
views: ['panel1'],
1185+
id: 'group-1',
1186+
activeView: 'panel1',
1187+
},
1188+
size: 500,
1189+
},
1190+
{
1191+
type: 'branch',
1192+
data: [
1193+
{
1194+
type: 'leaf',
1195+
data: {
1196+
views: ['panel2', 'panel3'],
1197+
id: 'group-2',
1198+
},
1199+
size: 500,
1200+
},
1201+
{
1202+
type: 'leaf',
1203+
data: {
1204+
views: ['panel4'],
1205+
id: 'group-3',
1206+
},
1207+
size: 500,
1208+
},
1209+
],
1210+
size: 500,
1211+
},
1212+
],
1213+
size: 1000,
1214+
},
1215+
height: 1000,
1216+
width: 1000,
1217+
orientation: Orientation.VERTICAL,
1218+
},
1219+
panels: {
1220+
panel1: {
1221+
id: 'panel1',
1222+
contentComponent: 'default',
1223+
tabComponent: 'tab-default',
1224+
title: 'panel1',
1225+
},
1226+
panel2: {
1227+
id: 'panel2',
1228+
contentComponent: 'default',
1229+
title: 'panel2',
1230+
},
1231+
panel3: {
1232+
id: 'panel3',
1233+
contentComponent: 'default',
1234+
title: 'panel3',
1235+
renderer: 'onlyWhenVisible',
1236+
},
1237+
panel4: {
1238+
id: 'panel4',
1239+
contentComponent: 'default',
1240+
title: 'panel4',
1241+
renderer: 'always',
1242+
},
1243+
},
1244+
},
1245+
{ reuseExistingPanels: true }
1246+
);
1247+
1248+
expect(parts.map((part) => part.isDisposed)).toEqual([
1249+
false,
1250+
false,
1251+
true,
1252+
false,
1253+
false,
1254+
]);
1255+
});
1256+
1257+
test('reuseExistingPanels false', () => {
1258+
const parts: PanelContentPartTest[] = [];
1259+
1260+
dockview = new DockviewComponent(container, {
1261+
createComponent(options) {
1262+
switch (options.name) {
1263+
case 'default':
1264+
const part = new PanelContentPartTest(
1265+
options.id,
1266+
options.name
1267+
);
1268+
parts.push(part);
1269+
return part;
1270+
default:
1271+
throw new Error(`unsupported`);
1272+
}
1273+
},
1274+
});
1275+
1276+
dockview.layout(1000, 1000);
1277+
1278+
dockview.addPanel({ id: 'panel1', component: 'default' });
1279+
dockview.addPanel({ id: 'panel2', component: 'default' });
1280+
dockview.addPanel({ id: 'panel7', component: 'default' });
1281+
1282+
expect(parts.length).toBe(3);
1283+
1284+
expect(parts.map((part) => part.isDisposed)).toEqual([
1285+
false,
1286+
false,
1287+
false,
1288+
]);
1289+
1290+
dockview.fromJSON({
1291+
activeGroup: 'group-1',
1292+
grid: {
1293+
root: {
1294+
type: 'branch',
1295+
data: [
1296+
{
1297+
type: 'leaf',
1298+
data: {
1299+
views: ['panel1'],
1300+
id: 'group-1',
1301+
activeView: 'panel1',
1302+
},
1303+
size: 500,
1304+
},
1305+
{
1306+
type: 'branch',
1307+
data: [
1308+
{
1309+
type: 'leaf',
1310+
data: {
1311+
views: ['panel2', 'panel3'],
1312+
id: 'group-2',
1313+
},
1314+
size: 500,
1315+
},
1316+
{
1317+
type: 'leaf',
1318+
data: {
1319+
views: ['panel4'],
1320+
id: 'group-3',
1321+
},
1322+
size: 500,
1323+
},
1324+
],
1325+
size: 500,
1326+
},
1327+
],
1328+
size: 1000,
1329+
},
1330+
height: 1000,
1331+
width: 1000,
1332+
orientation: Orientation.VERTICAL,
1333+
},
1334+
panels: {
1335+
panel1: {
1336+
id: 'panel1',
1337+
contentComponent: 'default',
1338+
tabComponent: 'tab-default',
1339+
title: 'panel1',
1340+
},
1341+
panel2: {
1342+
id: 'panel2',
1343+
contentComponent: 'default',
1344+
title: 'panel2',
1345+
},
1346+
panel3: {
1347+
id: 'panel3',
1348+
contentComponent: 'default',
1349+
title: 'panel3',
1350+
renderer: 'onlyWhenVisible',
1351+
},
1352+
panel4: {
1353+
id: 'panel4',
1354+
contentComponent: 'default',
1355+
title: 'panel4',
1356+
renderer: 'always',
1357+
},
1358+
},
1359+
});
1360+
1361+
expect(parts.map((part) => part.isDisposed)).toEqual([
1362+
true,
1363+
true,
1364+
true,
1365+
false,
1366+
false,
1367+
false,
1368+
false,
1369+
]);
1370+
});
1371+
11411372
test('basic', () => {
11421373
dockview.layout(1000, 1000);
11431374

@@ -1429,14 +1660,18 @@ describe('dockviewComponent', () => {
14291660

14301661
// Verify that always visible panels have been positioned
14311662
const overlayContainer = dockview.overlayRenderContainer;
1432-
1663+
14331664
// Check that panels with renderer: 'always' are attached to overlay container
14341665
expect(panel2.api.renderer).toBe('always');
14351666
expect(panel3.api.renderer).toBe('always');
14361667

14371668
// Get the overlay elements for always visible panels
1438-
const panel2Overlay = overlayContainer.element.querySelector('[data-panel-id]') as HTMLElement;
1439-
const panel3Overlay = overlayContainer.element.querySelector('[data-panel-id]:not(:first-child)') as HTMLElement;
1669+
const panel2Overlay = overlayContainer.element.querySelector(
1670+
'[data-panel-id]'
1671+
) as HTMLElement;
1672+
const panel3Overlay = overlayContainer.element.querySelector(
1673+
'[data-panel-id]:not(:first-child)'
1674+
) as HTMLElement;
14401675

14411676
// Verify positioning has been applied (should not be 0 after layout)
14421677
if (panel2Overlay) {
@@ -1449,16 +1684,19 @@ describe('dockviewComponent', () => {
14491684
}
14501685

14511686
// Test that updateAllPositions method works correctly
1452-
const updateSpy = jest.spyOn(overlayContainer, 'updateAllPositions');
1453-
1687+
const updateSpy = jest.spyOn(
1688+
overlayContainer,
1689+
'updateAllPositions'
1690+
);
1691+
14541692
// Call fromJSON again to trigger position updates
14551693
dockview.fromJSON(dockview.toJSON());
1456-
1694+
14571695
// Wait for the position update to be called
14581696
await new Promise((resolve) => requestAnimationFrame(resolve));
1459-
1697+
14601698
expect(updateSpy).toHaveBeenCalled();
1461-
1699+
14621700
updateSpy.mockRestore();
14631701
});
14641702
});
@@ -5443,29 +5681,29 @@ describe('dockviewComponent', () => {
54435681
container.style.width = '800px';
54445682
container.style.height = '600px';
54455683
document.body.appendChild(container);
5446-
5684+
54475685
const dockview = new DockviewComponent(container, {
54485686
createComponent(options) {
54495687
const element = document.createElement('div');
54505688
element.innerHTML = `<div class="test-content-${options.id}">Test Content: ${options.id}</div>`;
54515689
element.style.background = 'lightblue';
54525690
element.style.padding = '10px';
54535691
return new PanelContentPartTest(options.id, options.name);
5454-
}
5692+
},
54555693
});
5456-
5694+
54575695
dockview.layout(800, 600);
5458-
5696+
54595697
try {
54605698
// 1. Create a panel
54615699
const panel = dockview.addPanel({
54625700
id: 'test-panel',
5463-
component: 'default'
5701+
component: 'default',
54645702
});
5465-
5703+
54665704
// Verify initial state
54675705
expect(panel.api.location.type).toBe('grid');
5468-
5706+
54695707
// 2. Move to floating group
54705708
dockview.addFloatingGroup(panel, {
54715709
position: {
@@ -5475,27 +5713,27 @@ describe('dockviewComponent', () => {
54755713
width: 400,
54765714
height: 300,
54775715
});
5478-
5716+
54795717
// Verify floating state
54805718
expect(panel.api.location.type).toBe('floating');
5481-
5719+
54825720
// 3. Move back to grid using addGroup + moveTo pattern (reproducing user's exact issue)
54835721
const addGroup = dockview.addGroup();
54845722
panel.api.moveTo({ group: addGroup });
5485-
5723+
54865724
// THIS IS THE FIX: Component should still be visible
54875725
expect(panel.api.location.type).toBe('grid');
5488-
5726+
54895727
// Test multiple scenarios
54905728
const panel2 = dockview.addPanel({
54915729
id: 'panel-2',
54925730
component: 'default',
5493-
floating: true
5731+
floating: true,
54945732
});
5495-
5733+
54965734
const group2 = dockview.addGroup();
54975735
panel2.api.moveTo({ group: group2 });
5498-
5736+
54995737
expect(panel2.api.location.type).toBe('grid');
55005738
} finally {
55015739
dockview.dispose();
@@ -6378,10 +6616,10 @@ describe('dockviewComponent', () => {
63786616
expect(dockview.groups.length).toBe(0);
63796617

63806618
dockview.fromJSON(state);
6381-
6619+
63826620
// Advance timers to trigger delayed popout creation (0ms, 100ms delays)
63836621
jest.advanceTimersByTime(200);
6384-
6622+
63856623
// Wait for the popout restoration to complete
63866624
await dockview.popoutRestorationPromise;
63876625

@@ -6417,7 +6655,7 @@ describe('dockviewComponent', () => {
64176655
url: '/custom.html',
64186656
},
64196657
]);
6420-
6658+
64216659
jest.useRealTimers();
64226660
});
64236661

packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ export class TestPanel implements IDockviewPanel {
195195
});
196196
}
197197

198+
updateFromStateModel(state: GroupviewPanelState): void {
199+
//
200+
}
201+
198202
init(params: IGroupPanelInitParameters) {
199203
this._params = params;
200204
}

0 commit comments

Comments
 (0)