Skip to content

Commit 6623f1a

Browse files
committed
feat(config): add disabledViewTypes to filter view options
1 parent ca957c8 commit 6623f1a

File tree

4 files changed

+108
-7
lines changed

4 files changed

+108
-7
lines changed

src/components/ViewTypeSwitcher.vue

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<script setup lang="ts">
2-
import { getAvailableViews } from '@/src/config';
32
import { useViewStore } from '@/src/store/views';
43
import { Maybe } from '@/src/types';
54
import { computed, toRefs } from 'vue';
@@ -17,11 +16,14 @@ const viewName = computed(() => {
1716
return viewInfo?.name ?? '';
1817
});
1918
20-
const availableViews = getAvailableViews().list;
21-
const availableViewNames = availableViews.map((v) => v.name);
19+
const availableViewNames = computed(() =>
20+
viewStore.availableViewsForSwitcher.map((v) => v.name)
21+
);
2222
2323
function updateView(newViewName: string) {
24-
const selectedView = availableViews.find((v) => v.name === newViewName);
24+
const selectedView = viewStore.availableViewsForSwitcher.find(
25+
(v) => v.name === newViewName
26+
);
2527
if (!selectedView) return;
2628
viewStore.replaceView(viewId.value, {
2729
...selectedView,

src/io/import/configJson.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,15 @@ const windowing = z
133133
})
134134
.optional();
135135

136+
const disabledViewTypes = z.array(z.enum(['2D', '3D', 'Oblique'])).optional();
137+
136138
export const config = z.object({
137139
layout,
138140
labels,
139141
shortcuts,
140142
io,
141143
windowing,
144+
disabledViewTypes,
142145
});
143146

144147
export type Config = z.infer<typeof config>;
@@ -363,7 +366,14 @@ const applyWindowing = (manifest: Config) => {
363366
useWindowingStore().runtimeConfigWindowLevel = manifest.windowing;
364367
};
365368

369+
const applyDisabledViewTypes = (manifest: Config) => {
370+
if (!manifest.disabledViewTypes) return;
371+
372+
useViewStore().disabledViewTypes = manifest.disabledViewTypes;
373+
};
374+
366375
export const applyPreStateConfig = (manifest: Config) => {
376+
applyDisabledViewTypes(manifest);
367377
applyLayout(manifest);
368378
applyShortcuts(manifest);
369379
applyIo(manifest);

src/store/views.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { defineStore } from 'pinia';
2-
import { computed, markRaw, reactive, ref } from 'vue';
2+
import { computed, markRaw, reactive, ref, watch } from 'vue';
33
import { createEventHook } from '@vueuse/core';
44
import type { Maybe } from '@/src/types';
55
import type { Layout, LayoutItem } from '@/src/types/layout';
66
import { useIdStore } from '@/src/store/id';
7-
import type { ViewInfo, ViewInfoInit } from '@/src/types/views';
8-
import { DefaultLayout, DefaultLayoutSlots } from '@/src/config';
7+
import type { ViewInfo, ViewInfoInit, ViewType } from '@/src/types/views';
8+
import { DefaultLayout, DefaultLayoutSlots, getAvailableViews } from '@/src/config';
99
import type { StateFile } from '../io/state-file/schema';
1010

1111
const DEFAULT_VIEW_INIT: ViewInfoInit = {
@@ -70,6 +70,7 @@ export const useViewStore = defineStore('view', () => {
7070
const layoutSlots = ref<string[]>([]);
7171
const viewByID = reactive<Record<string, ViewInfo>>({});
7272
const activeView = ref<Maybe<string>>();
73+
const disabledViewTypes = ref<ViewType[]>([]);
7374

7475
const isActiveViewMaximized = ref(false);
7576
const maximizedView = computed(() => {
@@ -78,6 +79,13 @@ export const useViewStore = defineStore('view', () => {
7879
return undefined;
7980
});
8081

82+
const availableViewsForSwitcher = computed(() => {
83+
const allViews = getAvailableViews();
84+
return allViews.list.filter(
85+
(view) => !disabledViewTypes.value.includes(view.type)
86+
);
87+
});
88+
8189
const visibleViews = computed(() => {
8290
if (maximizedView.value) return [maximizedView.value];
8391
const views: ViewInfo[] = [];
@@ -211,6 +219,20 @@ export const useViewStore = defineStore('view', () => {
211219
});
212220
}
213221

222+
function applyDisabledViewTypesFilter() {
223+
if (!disabledViewTypes.value.length) return;
224+
225+
layoutSlots.value.forEach((id) => {
226+
const view = viewByID[id];
227+
if (disabledViewTypes.value.includes(view.type)) {
228+
const replacement = availableViewsForSwitcher.value[0];
229+
if (replacement) {
230+
replaceView(id, replacement);
231+
}
232+
}
233+
});
234+
}
235+
214236
function serialize(stateFile: StateFile) {
215237
const { manifest } = stateFile;
216238
manifest.layout = layout.value;
@@ -247,6 +269,10 @@ export const useViewStore = defineStore('view', () => {
247269
layoutSlots.value.push(addView(viewInit));
248270
});
249271

272+
watch(disabledViewTypes, () => {
273+
applyDisabledViewTypesFilter();
274+
});
275+
250276
return {
251277
visibleLayout: computed<Layout>(() => {
252278
if (maximizedView.value)
@@ -260,6 +286,8 @@ export const useViewStore = defineStore('view', () => {
260286
viewIDs,
261287
activeView,
262288
viewByID,
289+
disabledViewTypes,
290+
availableViewsForSwitcher,
263291
getView,
264292
getAllViews,
265293
getViewsForData,

tests/specs/layout-config.e2e.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,65 @@ describe('VolView Layout Configuration', () => {
213213
expect(await views2D.length).toBe(2);
214214
expect(view3D).not.toBeNull();
215215
});
216+
217+
it('should disable 3D and Oblique view types', async () => {
218+
const config = {
219+
disabledViewTypes: ['3D', 'Oblique'],
220+
};
221+
222+
const configFileName = 'disabled-view-types-config.json';
223+
await writeManifestToFile(config, configFileName);
224+
225+
const manifest = {
226+
resources: [
227+
{ url: `/tmp/${configFileName}` },
228+
{
229+
url: 'https://data.kitware.com/api/v1/file/6566aa81c5a2b36857ad1783/download',
230+
name: 'CT000085.dcm',
231+
},
232+
],
233+
};
234+
235+
const manifestFileName = 'disabled-view-types-manifest.json';
236+
await writeManifestToFile(manifest, manifestFileName);
237+
238+
await volViewPage.open(`?urls=[tmp/${manifestFileName}]`);
239+
await volViewPage.waitForViews();
240+
241+
await browser.waitUntil(
242+
async () => {
243+
const views2D = await volViewPage.getViews2D();
244+
const view3D = await volViewPage.getView3D();
245+
return (await views2D.length) === 4 && view3D === null;
246+
},
247+
{
248+
timeout: DOWNLOAD_TIMEOUT,
249+
timeoutMsg: 'Expected 4 2D views and no 3D view',
250+
interval: 1000,
251+
}
252+
);
253+
254+
const views2D = await volViewPage.getViews2D();
255+
const view3D = await volViewPage.getView3D();
256+
257+
expect(await views2D.length).toBe(4);
258+
expect(view3D).toBeNull();
259+
260+
const viewTypeSwitchers = await $$('.view-type-select');
261+
expect(viewTypeSwitchers.length).toBeGreaterThan(0);
262+
263+
const firstSwitcher = viewTypeSwitchers[0];
264+
await firstSwitcher.click();
265+
266+
const optionTexts = await browser.execute(() => {
267+
const items = Array.from(document.querySelectorAll('.v-list-item-title'));
268+
return items.map((item) => item.textContent?.trim() ?? '');
269+
});
270+
271+
expect(optionTexts).not.toContain('Volume');
272+
expect(optionTexts).not.toContain('Oblique');
273+
expect(optionTexts).toContain('Axial');
274+
expect(optionTexts).toContain('Coronal');
275+
expect(optionTexts).toContain('Sagittal');
276+
});
216277
});

0 commit comments

Comments
 (0)