Skip to content

Commit 6efe57a

Browse files
committed
feat: Add grid layout configuration support
- Replace old layout constants with flexible gridSize configuration - Add e2e test for single view (1x1) layout configuration - Update documentation to reflect new gridSize format
1 parent 190cd79 commit 6efe57a

5 files changed

Lines changed: 100 additions & 50 deletions

File tree

‎docs/configuration_file.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22

33
By loading a JSON file, you can set VolView's configuration:
44

5-
- Starting view layout (Axial Only, 3D Primary, etc).
5+
- Starting view layout grid size
66
- Labels for tools
77
- Visibility of Sample Data section
88
- Keyboard shortcuts
99

1010
## Starting view layout
1111

12-
The `activeLayout` key has options (Axial Only, 3D Primary, etc.) defined in `config.ts`
12+
The `gridSize` key sets the initial layout grid as `[width, height]`. For example, `[2, 2]` creates a 2x2 grid of views.
1313

1414
```json
1515
{
1616
"layout": {
17-
"activeLayout": "Axial Only"
17+
"gridSize": [2, 2]
1818
}
1919
}
2020
```
@@ -110,7 +110,7 @@ Simplify the data browser by hiding the Sample Data expandable section.
110110
}
111111
},
112112
"layout": {
113-
"activeLayout": "Axial Only"
113+
"gridSize": [1, 1]
114114
}
115115
}
116116
```
@@ -143,7 +143,7 @@ Simplify the data browser by hiding the Sample Data expandable section.
143143
}
144144
},
145145
"layout": {
146-
"activeLayout": "Axial Only"
146+
"gridSize": [2, 2]
147147
},
148148
"dataBrowser": {
149149
"hideSampleData": false

‎src/io/import/configJson.ts‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { z } from 'zod';
22
import { zodEnumFromObjKeys } from '@/src/utils';
33
import { ACTIONS } from '@/src/constants';
4-
// import { Layouts } from '@/src/config';
54

65
// for applyConfig
76
import { useRectangleStore } from '@/src/store/tools/rectangles';
87
import { useRulerStore } from '@/src/store/tools/rulers';
98
import { useDataBrowserStore } from '@/src/store/data-browser';
109
import { usePolygonStore } from '@/src/store/tools/polygons';
11-
// import { useViewStore } from '@/src/store/views';
10+
import { useViewStore } from '@/src/store/views';
1211
import { useWindowingStore } from '@/src/store/view-configs/windowing';
1312
import { actionToKey } from '@/src/composables/useKeyboardShortcuts';
1413
import { useSegmentGroupStore } from '@/src/store/segmentGroups';
@@ -20,7 +19,7 @@ import useLoadDataStore from '@/src/store/load-data';
2019

2120
const layout = z
2221
.object({
23-
// activeLayout: zodEnumFromObjKeys(Layouts).optional(),
22+
gridSize: z.tuple([z.number(), z.number()]).optional(),
2423
})
2524
.optional();
2625

@@ -128,12 +127,11 @@ const applySampleData = (manifest: Config) => {
128127
useDataBrowserStore().hideSampleData = !!manifest.dataBrowser?.hideSampleData;
129128
};
130129

131-
// const applyLayout = (manifest: Config) => {
132-
// if (manifest.layout?.activeLayout) {
133-
// const startingLayout = Layouts[manifest.layout.activeLayout];
134-
// useViewStore().setLayout(startingLayout);
135-
// }
136-
// };
130+
const applyLayout = (manifest: Config) => {
131+
if (manifest.layout?.gridSize) {
132+
useViewStore().setLayoutFromGrid(manifest.layout.gridSize);
133+
}
134+
};
137135

138136
const applyShortcuts = (manifest: Config) => {
139137
if (!manifest.shortcuts) return;
@@ -159,7 +157,7 @@ const applyWindowing = (manifest: Config) => {
159157
};
160158

161159
export const applyConfig = (manifest: Config) => {
162-
// applyLayout(manifest);
160+
applyLayout(manifest);
163161
applyLabels(manifest);
164162
applySampleData(manifest);
165163
applyShortcuts(manifest);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"layout": {
3+
"gridSize": [1, 1]
4+
},
5+
"dataBrowser": {
6+
"hideSampleData": false
7+
}
8+
}

‎tests/pageobjects/volview.page.ts‎

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -50,51 +50,41 @@ class VolViewPage extends Page {
5050
await browser.waitUntil(
5151
async () => {
5252
try {
53-
// Query views fresh each time to avoid stale references
54-
const views = await this.views;
55-
const viewCount = await views.length;
53+
// Query views once per iteration to avoid multiple queries that could become stale
54+
const currentViews = await this.views;
55+
const viewCount = await currentViews.length;
5656

5757
if (viewCount === 0) {
5858
return false;
5959
}
6060

61-
// Check if at least one view has real dimensions
62-
const freshViews = await this.views;
63-
const freshCount = await freshViews.length;
64-
65-
const viewChecks = Array.from({ length: viewCount }, (_, i) => i);
66-
67-
const results = await Promise.all(
68-
viewChecks.map(async (i) => {
69-
if (i >= freshCount) {
70-
return false;
61+
// Check each view's dimensions in a single pass
62+
const viewPromises = currentViews.map(async (view) => {
63+
try {
64+
// Get attributes directly from the element reference
65+
const width = await view.getAttribute('width');
66+
const height = await view.getAttribute('height');
67+
68+
if (width && height) {
69+
const w = parseInt(width, 10);
70+
const h = parseInt(height, 10);
71+
// Canvas should have real dimensions, not be a 1x1 placeholder
72+
// Accept any size > 10 as a real view
73+
return w > 10 && h > 10;
7174
}
75+
return false;
76+
} catch (err) {
77+
// Element may have been removed/recreated - that's ok, we'll retry
78+
return false;
79+
}
80+
});
7281

73-
try {
74-
const view = freshViews[i];
75-
const [width, height] = await Promise.all([
76-
view.getAttribute('width').catch(() => null),
77-
view.getAttribute('height').catch(() => null),
78-
]);
79-
80-
if (width && height) {
81-
const w = parseInt(width, 10);
82-
const h = parseInt(height, 10);
83-
// Canvas should have real dimensions, not be a 1x1 placeholder
84-
// Accept any size > 10 as a real view
85-
return w > 10 && h > 10;
86-
}
87-
return false;
88-
} catch (err) {
89-
// Handle stale element errors
90-
return false;
91-
}
92-
})
93-
);
82+
const results = await Promise.all(await viewPromises);
9483

84+
// At least one view must have real dimensions
9585
return results.some((result) => result);
9686
} catch (error) {
97-
// Log but don't throw - let it retry
87+
// DOM may be updating, retry on next iteration
9888
return false;
9989
}
10090
},

‎tests/specs/layout-config.e2e.ts‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as path from 'path';
2+
import * as fs from 'fs';
3+
import { volViewPage } from '../pageobjects/volview.page';
4+
import { FIXTURES, DOWNLOAD_TIMEOUT } from '../../wdio.shared.conf';
5+
import { writeManifestToFile } from './utils';
6+
7+
describe('VolView Layout Configuration', () => {
8+
it('should load single view layout from config', async () => {
9+
const configPath = path.join(FIXTURES, 'config-layout-1x1.json');
10+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
11+
const configFileName = 'config-layout-1x1.json';
12+
await writeManifestToFile(config, configFileName);
13+
14+
await volViewPage.open(`?urls=[tmp/${configFileName}]`);
15+
16+
await browser.pause(2000);
17+
18+
await volViewPage.downloadProstateSample();
19+
20+
await volViewPage.waitForViews();
21+
22+
await browser.waitUntil(
23+
async () => {
24+
const currentViews = await volViewPage.views;
25+
const viewCount = await currentViews.length;
26+
27+
if (viewCount !== 1) {
28+
return false;
29+
}
30+
31+
const view = currentViews[0];
32+
const width = await view.getAttribute('width');
33+
const height = await view.getAttribute('height');
34+
35+
if (!width || !height) {
36+
return false;
37+
}
38+
39+
const w = parseInt(width, 10);
40+
const h = parseInt(height, 10);
41+
42+
return w > 200 && h > 200;
43+
},
44+
{
45+
timeout: DOWNLOAD_TIMEOUT,
46+
timeoutMsg: 'Expected exactly 1 view with rendered content',
47+
interval: 1000,
48+
}
49+
);
50+
51+
const views = await volViewPage.views;
52+
await expect(views).toHaveLength(1);
53+
});
54+
});

0 commit comments

Comments
 (0)