From 554e4c1bd38b888ee457d8bfe35fd41e27047129 Mon Sep 17 00:00:00 2001 From: Matt Dawkins Date: Thu, 17 Sep 2026 19:37:36 -0400 Subject: [PATCH 1/2] Name the selected camera beside the filename in multi-camera datasets --- .../controls/FileNameTimeDisplay.spec.ts | 39 +++++++++++++++++++ .../controls/FileNameTimeDisplay.vue | 3 ++ 2 files changed, 42 insertions(+) create mode 100644 client/src/components/controls/FileNameTimeDisplay.spec.ts diff --git a/client/src/components/controls/FileNameTimeDisplay.spec.ts b/client/src/components/controls/FileNameTimeDisplay.spec.ts new file mode 100644 index 000000000..c3096ed6f --- /dev/null +++ b/client/src/components/controls/FileNameTimeDisplay.spec.ts @@ -0,0 +1,39 @@ +// @vitest-environment jsdom +/* eslint-disable @typescript-eslint/no-explicit-any -- vue-test-utils typing of defineComponent */ +import { ref, shallowRef } from 'vue'; +import { mount } from '@vue/test-utils'; +import FileNameTimeDisplay from './FileNameTimeDisplay.vue'; + +const selectedCamera = ref('left'); +const cameras = ref(['left', 'right']); +const controllers = { + left: { filename: ref('f0028.jpg'), duration: ref(0) }, + right: { filename: ref('f0028.jpg'), duration: ref(0) }, +}; +vi.mock('../../provides', () => ({ useSelectedCamera: () => selectedCamera })); +vi.mock('../annotators/useMediaController', () => ({ + injectAggregateController: () => shallowRef({ + currentTime: ref(0), + frame: ref(0), + cameras, + getController: (camera: string) => controllers[camera as 'left' | 'right'], + }), +})); + +it('names the selected camera beside its filename in a multi-camera dataset', async () => { + const wrapper = mount(FileNameTimeDisplay as any, { propsData: { displayType: 'filename' } }); + expect(wrapper.text()).toContain('left: f0028.jpg'); + selectedCamera.value = 'right'; + controllers.right.filename.value = 'f0029.jpg'; + await wrapper.vm.$nextTick(); + expect(wrapper.text()).toContain('right: f0029.jpg'); + expect(wrapper.text()).not.toContain('left'); +}); + +it('shows the bare filename for a single camera', () => { + cameras.value = ['singleCam']; + selectedCamera.value = 'left'; + const wrapper = mount(FileNameTimeDisplay as any, { propsData: { displayType: 'filename' } }); + expect(wrapper.text()).toContain('f0028.jpg'); + expect(wrapper.text()).not.toContain('left:'); +}); diff --git a/client/src/components/controls/FileNameTimeDisplay.vue b/client/src/components/controls/FileNameTimeDisplay.vue index a98d084a8..8f944bef9 100644 --- a/client/src/components/controls/FileNameTimeDisplay.vue +++ b/client/src/components/controls/FileNameTimeDisplay.vue @@ -28,10 +28,13 @@ export default defineComponent({ }); const filename = computed(() => (selectedCameraController.value?.filename.value)); const duration = computed(() => (selectedCameraController.value?.duration.value)); + // Cameras of a rig often share file names, so name the camera as well. + const multiCamera = computed(() => mediaController.value.cameras.value.length > 1); const display = computed(() => { let value = 'unsupported display'; if (props.displayType === 'filename') { value = filename.value || 'uninitialized'; + if (multiCamera.value) value = `${selectedCamera.value}: ${value}`; } if (props.displayType === 'time') { value = `${new Date(currentTime.value * 1000).toISOString().substr(11, 8)} / ${new Date((duration.value || 0) * 1000).toISOString().substr(11, 8)}`; } From c71a72b8a64451d1821aef4b6ffa31265993286f Mon Sep 17 00:00:00 2001 From: Matt Dawkins Date: Thu, 17 Sep 2026 22:11:28 -0400 Subject: [PATCH 2/2] Show the selected camera's filename without a camera prefix --- .../controls/FileNameTimeDisplay.spec.ts | 16 ++++------------ .../components/controls/FileNameTimeDisplay.vue | 3 --- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/client/src/components/controls/FileNameTimeDisplay.spec.ts b/client/src/components/controls/FileNameTimeDisplay.spec.ts index c3096ed6f..3ad0ce177 100644 --- a/client/src/components/controls/FileNameTimeDisplay.spec.ts +++ b/client/src/components/controls/FileNameTimeDisplay.spec.ts @@ -20,20 +20,12 @@ vi.mock('../annotators/useMediaController', () => ({ }), })); -it('names the selected camera beside its filename in a multi-camera dataset', async () => { +it('shows the selected camera filename', async () => { const wrapper = mount(FileNameTimeDisplay as any, { propsData: { displayType: 'filename' } }); - expect(wrapper.text()).toContain('left: f0028.jpg'); + expect(wrapper.text()).toContain('f0028.jpg'); selectedCamera.value = 'right'; controllers.right.filename.value = 'f0029.jpg'; await wrapper.vm.$nextTick(); - expect(wrapper.text()).toContain('right: f0029.jpg'); - expect(wrapper.text()).not.toContain('left'); -}); - -it('shows the bare filename for a single camera', () => { - cameras.value = ['singleCam']; - selectedCamera.value = 'left'; - const wrapper = mount(FileNameTimeDisplay as any, { propsData: { displayType: 'filename' } }); - expect(wrapper.text()).toContain('f0028.jpg'); - expect(wrapper.text()).not.toContain('left:'); + expect(wrapper.text()).toContain('f0029.jpg'); + expect(wrapper.text()).not.toContain('f0028.jpg'); }); diff --git a/client/src/components/controls/FileNameTimeDisplay.vue b/client/src/components/controls/FileNameTimeDisplay.vue index 8f944bef9..a98d084a8 100644 --- a/client/src/components/controls/FileNameTimeDisplay.vue +++ b/client/src/components/controls/FileNameTimeDisplay.vue @@ -28,13 +28,10 @@ export default defineComponent({ }); const filename = computed(() => (selectedCameraController.value?.filename.value)); const duration = computed(() => (selectedCameraController.value?.duration.value)); - // Cameras of a rig often share file names, so name the camera as well. - const multiCamera = computed(() => mediaController.value.cameras.value.length > 1); const display = computed(() => { let value = 'unsupported display'; if (props.displayType === 'filename') { value = filename.value || 'uninitialized'; - if (multiCamera.value) value = `${selectedCamera.value}: ${value}`; } if (props.displayType === 'time') { value = `${new Date(currentTime.value * 1000).toISOString().substr(11, 8)} / ${new Date((duration.value || 0) * 1000).toISOString().substr(11, 8)}`; }