From a9580a0dd0b1bd67bb7e2278d46c3c3bebb41506 Mon Sep 17 00:00:00 2001 From: Marc-Lorenz <169900493+Marc-Lorenz@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:15:30 +0200 Subject: [PATCH 01/25] add navbar and move components --- components.d.ts | 16 ++- e2e/vue.spec.js | 40 ++++++++ e2e/vue.spec.ts | 2 +- index.html | 14 --- src/App.vue | 12 ++- src/components/Main/SidebarContainer.vue | 21 ---- src/components/Main/TopNavbar.vue | 31 ++++++ .../Main/__tests__/SidebarCointainer.spec.ts | 15 --- src/components/MenuItems/ButtonWithIcon.vue | 6 +- .../Modals/WebserviceSelectModal.vue | 94 ++++++++++++++++++ src/components/Navbar/AboutSection.vue | 48 +++++++++ src/components/Navbar/EditButtons.vue | 54 ++++++++++ .../{Sidebar => Navbar}/LoadSaveActions.vue | 62 +++++++----- src/components/Sidebar/AboutSection.vue | 32 ------ src/components/Sidebar/EditButtons.vue | 98 ------------------- src/components/Sidebar/FeatureDragBar.vue | 53 ++++++++++ src/components/Sidebar/FeatureSelector.vue | 3 +- src/components/Sidebar/ModelSelector.vue | 69 +------------ src/components/Sidebar/ViewOptions.vue | 26 +++-- src/editor2d.ts | 1 + src/view/checkbox.ts | 32 ------ src/view/slider.ts | 51 ---------- 22 files changed, 403 insertions(+), 377 deletions(-) create mode 100644 e2e/vue.spec.js create mode 100644 src/components/Main/TopNavbar.vue create mode 100644 src/components/Modals/WebserviceSelectModal.vue create mode 100644 src/components/Navbar/AboutSection.vue create mode 100644 src/components/Navbar/EditButtons.vue rename src/components/{Sidebar => Navbar}/LoadSaveActions.vue (84%) delete mode 100644 src/components/Sidebar/AboutSection.vue delete mode 100644 src/components/Sidebar/EditButtons.vue create mode 100644 src/components/Sidebar/FeatureDragBar.vue delete mode 100644 src/view/checkbox.ts delete mode 100644 src/view/slider.ts diff --git a/components.d.ts b/components.d.ts index 2733c04..a5b662b 100644 --- a/components.d.ts +++ b/components.d.ts @@ -7,17 +7,27 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { - AboutSection: typeof import('./src/components/Sidebar/AboutSection.vue')['default'] + AboutSection: typeof import('./src/components/Navbar/AboutSection.vue')['default'] + BButton: typeof import('bootstrap-vue-next')['BButton'] + BDropdownDivider: typeof import('bootstrap-vue-next')['BDropdownDivider'] + BDropdownItem: typeof import('bootstrap-vue-next')['BDropdownItem'] BModal: typeof import('bootstrap-vue-next')['BModal'] + BNavbarBrand: typeof import('bootstrap-vue-next')['BNavbarBrand'] + BNavbarNav: typeof import('bootstrap-vue-next')['BNavbarNav'] + BNavItem: typeof import('bootstrap-vue-next')['BNavItem'] + BNavItemDropdown: typeof import('bootstrap-vue-next')['BNavItemDropdown'] ButtonWithIcon: typeof import('./src/components/MenuItems/ButtonWithIcon.vue')['default'] CentralCanvas: typeof import('./src/components/Main/CentralCanvas.vue')['default'] - EditButtons: typeof import('./src/components/Sidebar/EditButtons.vue')['default'] + EditButtons: typeof import('./src/components/Navbar/EditButtons.vue')['default'] + FeatureDragBar: typeof import('./src/components/Sidebar/FeatureDragBar.vue')['default'] FeatureSelector: typeof import('./src/components/Sidebar/FeatureSelector.vue')['default'] - LoadSaveActions: typeof import('./src/components/Sidebar/LoadSaveActions.vue')['default'] + LoadSaveActions: typeof import('./src/components/Navbar/LoadSaveActions.vue')['default'] ModelSelector: typeof import('./src/components/Sidebar/ModelSelector.vue')['default'] SidebarContainer: typeof import('./src/components/Main/SidebarContainer.vue')['default'] ThumbnailContainer: typeof import('./src/components/ThumbnailContainer.vue')['default'] ThumbnailGallery: typeof import('./src/components/Main/ThumbnailGallery.vue')['default'] + TopNavbar: typeof import('./src/components/Main/TopNavbar.vue')['default'] ViewOptions: typeof import('./src/components/Sidebar/ViewOptions.vue')['default'] + WebserviceSelectModal: typeof import('./src/components/Modals/WebserviceSelectModal.vue')['default'] } } diff --git a/e2e/vue.spec.js b/e2e/vue.spec.js new file mode 100644 index 0000000..90c1ae4 --- /dev/null +++ b/e2e/vue.spec.js @@ -0,0 +1,40 @@ +import { test, expect } from '@playwright/test'; +// See here how to get started: +// https://playwright.dev/docs/intro +test('visits the app root url', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('h3')).toHaveText('Face Mesh Editor'); +}); +test('Check Element Relative Width to Container', async ({ page }) => { + await page.goto('/'); + const containerHandle = page.locator('#app'); + const sidebar = page.locator('#sidebar'); + const canvas = page.locator('#canvas-div'); + const thumbnails = page.locator('#thumbnail-gallery'); + const containerBox = await containerHandle.boundingBox(); + expect(containerBox).not.toBe(null); + const sidebarBox = await sidebar.boundingBox(); + expect(sidebarBox).not.toBe(null); + const canvasBox = await canvas.boundingBox(); + expect(canvasBox).not.toBe(null); + const thumbnailsBox = await thumbnails.boundingBox(); + expect(thumbnailsBox).not.toBe(null); + // eslint-disable-next-line playwright/no-conditional-in-test + if (!containerBox) + return; + // eslint-disable-next-line playwright/no-conditional-in-test + if (!sidebarBox) + return; + // eslint-disable-next-line playwright/no-conditional-in-test + if (!canvasBox) + return; + // eslint-disable-next-line playwright/no-conditional-in-test + if (!thumbnailsBox) + return; + let relativeWidth = sidebarBox.width / containerBox.width; + expect(relativeWidth).toBeCloseTo(0.2, 1); + relativeWidth = canvasBox.width / containerBox.width; + expect(relativeWidth).toBeCloseTo(0.7, 1); + relativeWidth = thumbnailsBox.width / containerBox.width; + expect(relativeWidth).toBeCloseTo(0.1, 1); +}); diff --git a/e2e/vue.spec.ts b/e2e/vue.spec.ts index 5067124..9d7bce8 100644 --- a/e2e/vue.spec.ts +++ b/e2e/vue.spec.ts @@ -4,7 +4,7 @@ import { test, expect } from '@playwright/test'; // https://playwright.dev/docs/intro test('visits the app root url', async ({ page }) => { await page.goto('/'); - await expect(page.locator('h2')).toHaveText('Face Mesh Editor'); + await expect(page.locator('h3')).toHaveText('Face Mesh Editor'); }); test('Check Element Relative Width to Container', async ({ page }) => { diff --git a/index.html b/index.html index 389bcc4..eb36a6c 100644 --- a/index.html +++ b/index.html @@ -15,20 +15,6 @@
- -
diff --git a/src/App.vue b/src/App.vue index 5fc6da7..eb19314 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,12 +2,16 @@ import Sidebar from '@/components/Main/SidebarContainer.vue'; import CentralCanvas from '@/components/Main/CentralCanvas.vue'; import ThumbnailGallery from '@/components/Main/ThumbnailGallery.vue'; +import TopNavbar from '@/components/Main/TopNavbar.vue'; diff --git a/src/components/Main/SidebarContainer.vue b/src/components/Main/SidebarContainer.vue index 153105c..719edd4 100644 --- a/src/components/Main/SidebarContainer.vue +++ b/src/components/Main/SidebarContainer.vue @@ -1,11 +1,8 @@ + + + + diff --git a/src/components/Main/__tests__/SidebarCointainer.spec.ts b/src/components/Main/__tests__/SidebarCointainer.spec.ts index 741f41c..ce0fb4b 100644 --- a/src/components/Main/__tests__/SidebarCointainer.spec.ts +++ b/src/components/Main/__tests__/SidebarCointainer.spec.ts @@ -16,19 +16,4 @@ describe('Sidebar Component', () => { const wrapper = mount(Component); expect(wrapper.exists()).toBe(true); }); - - it('renders table', () => { - const wrapper = mount(Component); - expect(wrapper.find('table').exists()).toBe(true); - }); - - it('header text', () => { - const wrapper = mount(Component); - expect(wrapper.find('h2').text()).toContain('Face Mesh Editor'); - }); - - it('displays the correct image', () => { - const wrapper = mount(Component); - expect(wrapper.find('img').attributes().src).toContain('static/images/FaceMesh.png'); - }); }); diff --git a/src/components/MenuItems/ButtonWithIcon.vue b/src/components/MenuItems/ButtonWithIcon.vue index 66611bc..7b90e6b 100644 --- a/src/components/MenuItems/ButtonWithIcon.vue +++ b/src/components/MenuItems/ButtonWithIcon.vue @@ -22,14 +22,14 @@ function handleClick(e: MouseEvent) { diff --git a/src/components/Modals/WebserviceSelectModal.vue b/src/components/Modals/WebserviceSelectModal.vue new file mode 100644 index 0000000..82e7b50 --- /dev/null +++ b/src/components/Modals/WebserviceSelectModal.vue @@ -0,0 +1,94 @@ + + + diff --git a/src/components/Navbar/AboutSection.vue b/src/components/Navbar/AboutSection.vue new file mode 100644 index 0000000..bddf9f7 --- /dev/null +++ b/src/components/Navbar/AboutSection.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/src/components/Navbar/EditButtons.vue b/src/components/Navbar/EditButtons.vue new file mode 100644 index 0000000..5790893 --- /dev/null +++ b/src/components/Navbar/EditButtons.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/components/Sidebar/LoadSaveActions.vue b/src/components/Navbar/LoadSaveActions.vue similarity index 84% rename from src/components/Sidebar/LoadSaveActions.vue rename to src/components/Navbar/LoadSaveActions.vue index 9f61da8..b7d9771 100644 --- a/src/components/Sidebar/LoadSaveActions.vue +++ b/src/components/Navbar/LoadSaveActions.vue @@ -162,33 +162,41 @@ onBeforeUnmount(() => { diff --git a/src/components/Sidebar/AboutSection.vue b/src/components/Sidebar/AboutSection.vue deleted file mode 100644 index 4f6b9bb..0000000 --- a/src/components/Sidebar/AboutSection.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - - - diff --git a/src/components/Sidebar/EditButtons.vue b/src/components/Sidebar/EditButtons.vue deleted file mode 100644 index c68af1b..0000000 --- a/src/components/Sidebar/EditButtons.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - - - diff --git a/src/components/Sidebar/FeatureDragBar.vue b/src/components/Sidebar/FeatureDragBar.vue new file mode 100644 index 0000000..c5d187e --- /dev/null +++ b/src/components/Sidebar/FeatureDragBar.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/components/Sidebar/FeatureSelector.vue b/src/components/Sidebar/FeatureSelector.vue index d4c596f..e51f58a 100644 --- a/src/components/Sidebar/FeatureSelector.vue +++ b/src/components/Sidebar/FeatureSelector.vue @@ -45,7 +45,7 @@ const features = ['Left Eye', 'Left Eyebrow', 'Right Eye', 'Right Eyebrow', 'Nos diff --git a/src/components/Sidebar/ModelSelector.vue b/src/components/Sidebar/ModelSelector.vue index e38c5e2..f4bf170 100644 --- a/src/components/Sidebar/ModelSelector.vue +++ b/src/components/Sidebar/ModelSelector.vue @@ -7,6 +7,7 @@ import { WebServiceModel } from '@/model/webservice'; import { MediapipeModel } from '@/model/mediapipe'; import { useModelStore } from '@/stores/modelStore'; import { urlError } from '@/enums/urlError'; +import WebserviceSelectModal from '@/components/Modals/WebserviceSelectModal.vue'; const modelStore = useModelStore(); const showModal = ref(false); @@ -111,72 +112,8 @@ function setModel(model: ModelType): boolean { >Webservice
Online +
- -

- The webservice address will be used to detect a face mesh on selected images. Therefore, the - images must be transferred to the webservice for processing. The open format allows it to - create individual webservices by everyone and can be easily swapped. -

- -
-
API
-

- The webservice API must provide the following addresses: -
/detect
- This call is used to detect a single face on a provided image file inside a POST request. -
/annotations
- This call is used to sync the annotations inside a POST request when the user triggers the - download. -

-
-
URL
-

- Insert the webservice URL in the text field below and submit with hitting the Save button. -

- - - - -
+ - - diff --git a/src/components/Sidebar/ViewOptions.vue b/src/components/Sidebar/ViewOptions.vue index d96ca5e..5bd8669 100644 --- a/src/components/Sidebar/ViewOptions.vue +++ b/src/components/Sidebar/ViewOptions.vue @@ -1,34 +1,44 @@ diff --git a/src/editor2d.ts b/src/editor2d.ts index fa6adc7..1bcfee5 100644 --- a/src/editor2d.ts +++ b/src/editor2d.ts @@ -94,6 +94,7 @@ export class Editor2D { setBackgroundSource(source: ImageFile): void { this.image.src = source.html; this.draw(); + this.center(); } setOnPointsEditedCallback(callback: (graph: Graph) => void) { diff --git a/src/view/checkbox.ts b/src/view/checkbox.ts deleted file mode 100644 index 7bdd545..0000000 --- a/src/view/checkbox.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Represents a checkbox element. - */ -export class CheckBox { - private elem: HTMLInputElement; - - /** - * Creates a new CheckBox instance. - * @param {string} id - The ID of the checkbox element. - * @param {() => void} onChangeCallback - A callback function to execute when the checkbox value changes. - */ - constructor(id: string, onChangeCallback: () => void) { - this.elem = document.getElementById(id) as HTMLInputElement; - this.elem.onchange = onChangeCallback; - } - - /** - * Checks whether the checkbox is currently checked. - * @returns {boolean} - True if checked, false otherwise. - */ - isChecked(): boolean { - return this.elem.checked; - } - - /** - * Sets the checkbox to the specified checked state. - * @param {boolean} checked - The desired checked state (true or false). - */ - setChecked(checked: boolean): void { - this.elem.checked = checked; - } -} diff --git a/src/view/slider.ts b/src/view/slider.ts deleted file mode 100644 index 908cb1d..0000000 --- a/src/view/slider.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Represents a slider input element. - */ -export class Slider { - private readonly slider: HTMLInputElement; - private readonly onChangeCallback: () => void; - - /** - * Creates a new Slider instance. - * @param {string} id - The ID of the slider element. - * @param {() => void} onChangeCallback - A callback function to execute when the slider value changes. - */ - constructor(id: string, onChangeCallback: () => void) { - this.slider = document.getElementById(id) as HTMLInputElement; - this.slider.oninput = onChangeCallback; - this.onChangeCallback = onChangeCallback; - } - - /** - * Gets the minimum value of the slider. - * @returns {number} - The minimum value. - */ - getMin(): number { - return parseInt(this.slider.min); - } - - /** - * Gets the maximum value of the slider. - * @returns {number} - The maximum value. - */ - getMax(): number { - return parseInt(this.slider.max); - } - - /** - * Gets the current value of the slider. - * @returns {number} - The current value. - */ - getValue(): number { - return parseInt(this.slider.value); - } - - /** - * Sets the value of the slider. - * @param {number} value - The desired value. - */ - setValue(value: number): void { - this.slider.value = String(value); - this.onChangeCallback(); - } -} From bcc4c482535eb9e7b46d5b372f8a5e7c952f4318 Mon Sep 17 00:00:00 2001 From: Marc-Lorenz <169900493+Marc-Lorenz@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:15:30 +0200 Subject: [PATCH 02/25] add navbar and move components --- components.d.ts | 16 ++- e2e/vue.spec.js | 40 ++++++++ e2e/vue.spec.ts | 2 +- index.html | 14 --- src/App.vue | 12 ++- src/components/Main/SidebarContainer.vue | 21 ---- src/components/Main/TopNavbar.vue | 31 ++++++ .../Main/__tests__/SidebarCointainer.spec.ts | 15 --- src/components/MenuItems/ButtonWithIcon.vue | 6 +- .../Modals/WebserviceSelectModal.vue | 94 ++++++++++++++++++ src/components/Navbar/AboutSection.vue | 48 +++++++++ src/components/Navbar/EditButtons.vue | 54 ++++++++++ .../{Sidebar => Navbar}/LoadSaveActions.vue | 62 +++++++----- src/components/Sidebar/AboutSection.vue | 32 ------ src/components/Sidebar/EditButtons.vue | 98 ------------------- src/components/Sidebar/FeatureDragBar.vue | 53 ++++++++++ src/components/Sidebar/FeatureSelector.vue | 3 +- src/components/Sidebar/ModelSelector.vue | 69 +------------ src/components/Sidebar/ViewOptions.vue | 26 +++-- src/editor2d.ts | 1 + src/view/checkbox.ts | 32 ------ src/view/slider.ts | 51 ---------- 22 files changed, 403 insertions(+), 377 deletions(-) create mode 100644 e2e/vue.spec.js create mode 100644 src/components/Main/TopNavbar.vue create mode 100644 src/components/Modals/WebserviceSelectModal.vue create mode 100644 src/components/Navbar/AboutSection.vue create mode 100644 src/components/Navbar/EditButtons.vue rename src/components/{Sidebar => Navbar}/LoadSaveActions.vue (84%) delete mode 100644 src/components/Sidebar/AboutSection.vue delete mode 100644 src/components/Sidebar/EditButtons.vue create mode 100644 src/components/Sidebar/FeatureDragBar.vue delete mode 100644 src/view/checkbox.ts delete mode 100644 src/view/slider.ts diff --git a/components.d.ts b/components.d.ts index 2733c04..a5b662b 100644 --- a/components.d.ts +++ b/components.d.ts @@ -7,17 +7,27 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { - AboutSection: typeof import('./src/components/Sidebar/AboutSection.vue')['default'] + AboutSection: typeof import('./src/components/Navbar/AboutSection.vue')['default'] + BButton: typeof import('bootstrap-vue-next')['BButton'] + BDropdownDivider: typeof import('bootstrap-vue-next')['BDropdownDivider'] + BDropdownItem: typeof import('bootstrap-vue-next')['BDropdownItem'] BModal: typeof import('bootstrap-vue-next')['BModal'] + BNavbarBrand: typeof import('bootstrap-vue-next')['BNavbarBrand'] + BNavbarNav: typeof import('bootstrap-vue-next')['BNavbarNav'] + BNavItem: typeof import('bootstrap-vue-next')['BNavItem'] + BNavItemDropdown: typeof import('bootstrap-vue-next')['BNavItemDropdown'] ButtonWithIcon: typeof import('./src/components/MenuItems/ButtonWithIcon.vue')['default'] CentralCanvas: typeof import('./src/components/Main/CentralCanvas.vue')['default'] - EditButtons: typeof import('./src/components/Sidebar/EditButtons.vue')['default'] + EditButtons: typeof import('./src/components/Navbar/EditButtons.vue')['default'] + FeatureDragBar: typeof import('./src/components/Sidebar/FeatureDragBar.vue')['default'] FeatureSelector: typeof import('./src/components/Sidebar/FeatureSelector.vue')['default'] - LoadSaveActions: typeof import('./src/components/Sidebar/LoadSaveActions.vue')['default'] + LoadSaveActions: typeof import('./src/components/Navbar/LoadSaveActions.vue')['default'] ModelSelector: typeof import('./src/components/Sidebar/ModelSelector.vue')['default'] SidebarContainer: typeof import('./src/components/Main/SidebarContainer.vue')['default'] ThumbnailContainer: typeof import('./src/components/ThumbnailContainer.vue')['default'] ThumbnailGallery: typeof import('./src/components/Main/ThumbnailGallery.vue')['default'] + TopNavbar: typeof import('./src/components/Main/TopNavbar.vue')['default'] ViewOptions: typeof import('./src/components/Sidebar/ViewOptions.vue')['default'] + WebserviceSelectModal: typeof import('./src/components/Modals/WebserviceSelectModal.vue')['default'] } } diff --git a/e2e/vue.spec.js b/e2e/vue.spec.js new file mode 100644 index 0000000..90c1ae4 --- /dev/null +++ b/e2e/vue.spec.js @@ -0,0 +1,40 @@ +import { test, expect } from '@playwright/test'; +// See here how to get started: +// https://playwright.dev/docs/intro +test('visits the app root url', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('h3')).toHaveText('Face Mesh Editor'); +}); +test('Check Element Relative Width to Container', async ({ page }) => { + await page.goto('/'); + const containerHandle = page.locator('#app'); + const sidebar = page.locator('#sidebar'); + const canvas = page.locator('#canvas-div'); + const thumbnails = page.locator('#thumbnail-gallery'); + const containerBox = await containerHandle.boundingBox(); + expect(containerBox).not.toBe(null); + const sidebarBox = await sidebar.boundingBox(); + expect(sidebarBox).not.toBe(null); + const canvasBox = await canvas.boundingBox(); + expect(canvasBox).not.toBe(null); + const thumbnailsBox = await thumbnails.boundingBox(); + expect(thumbnailsBox).not.toBe(null); + // eslint-disable-next-line playwright/no-conditional-in-test + if (!containerBox) + return; + // eslint-disable-next-line playwright/no-conditional-in-test + if (!sidebarBox) + return; + // eslint-disable-next-line playwright/no-conditional-in-test + if (!canvasBox) + return; + // eslint-disable-next-line playwright/no-conditional-in-test + if (!thumbnailsBox) + return; + let relativeWidth = sidebarBox.width / containerBox.width; + expect(relativeWidth).toBeCloseTo(0.2, 1); + relativeWidth = canvasBox.width / containerBox.width; + expect(relativeWidth).toBeCloseTo(0.7, 1); + relativeWidth = thumbnailsBox.width / containerBox.width; + expect(relativeWidth).toBeCloseTo(0.1, 1); +}); diff --git a/e2e/vue.spec.ts b/e2e/vue.spec.ts index 5067124..9d7bce8 100644 --- a/e2e/vue.spec.ts +++ b/e2e/vue.spec.ts @@ -4,7 +4,7 @@ import { test, expect } from '@playwright/test'; // https://playwright.dev/docs/intro test('visits the app root url', async ({ page }) => { await page.goto('/'); - await expect(page.locator('h2')).toHaveText('Face Mesh Editor'); + await expect(page.locator('h3')).toHaveText('Face Mesh Editor'); }); test('Check Element Relative Width to Container', async ({ page }) => { diff --git a/index.html b/index.html index 389bcc4..eb36a6c 100644 --- a/index.html +++ b/index.html @@ -15,20 +15,6 @@
- -
diff --git a/src/App.vue b/src/App.vue index 5fc6da7..eb19314 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,12 +2,16 @@ import Sidebar from '@/components/Main/SidebarContainer.vue'; import CentralCanvas from '@/components/Main/CentralCanvas.vue'; import ThumbnailGallery from '@/components/Main/ThumbnailGallery.vue'; +import TopNavbar from '@/components/Main/TopNavbar.vue'; diff --git a/src/components/Main/SidebarContainer.vue b/src/components/Main/SidebarContainer.vue index 153105c..719edd4 100644 --- a/src/components/Main/SidebarContainer.vue +++ b/src/components/Main/SidebarContainer.vue @@ -1,11 +1,8 @@ + + + + diff --git a/src/components/Main/__tests__/SidebarCointainer.spec.ts b/src/components/Main/__tests__/SidebarCointainer.spec.ts index 741f41c..ce0fb4b 100644 --- a/src/components/Main/__tests__/SidebarCointainer.spec.ts +++ b/src/components/Main/__tests__/SidebarCointainer.spec.ts @@ -16,19 +16,4 @@ describe('Sidebar Component', () => { const wrapper = mount(Component); expect(wrapper.exists()).toBe(true); }); - - it('renders table', () => { - const wrapper = mount(Component); - expect(wrapper.find('table').exists()).toBe(true); - }); - - it('header text', () => { - const wrapper = mount(Component); - expect(wrapper.find('h2').text()).toContain('Face Mesh Editor'); - }); - - it('displays the correct image', () => { - const wrapper = mount(Component); - expect(wrapper.find('img').attributes().src).toContain('static/images/FaceMesh.png'); - }); }); diff --git a/src/components/MenuItems/ButtonWithIcon.vue b/src/components/MenuItems/ButtonWithIcon.vue index 66611bc..7b90e6b 100644 --- a/src/components/MenuItems/ButtonWithIcon.vue +++ b/src/components/MenuItems/ButtonWithIcon.vue @@ -22,14 +22,14 @@ function handleClick(e: MouseEvent) { diff --git a/src/components/Modals/WebserviceSelectModal.vue b/src/components/Modals/WebserviceSelectModal.vue new file mode 100644 index 0000000..82e7b50 --- /dev/null +++ b/src/components/Modals/WebserviceSelectModal.vue @@ -0,0 +1,94 @@ + + + diff --git a/src/components/Navbar/AboutSection.vue b/src/components/Navbar/AboutSection.vue new file mode 100644 index 0000000..bddf9f7 --- /dev/null +++ b/src/components/Navbar/AboutSection.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/src/components/Navbar/EditButtons.vue b/src/components/Navbar/EditButtons.vue new file mode 100644 index 0000000..5790893 --- /dev/null +++ b/src/components/Navbar/EditButtons.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/components/Sidebar/LoadSaveActions.vue b/src/components/Navbar/LoadSaveActions.vue similarity index 84% rename from src/components/Sidebar/LoadSaveActions.vue rename to src/components/Navbar/LoadSaveActions.vue index 9f61da8..b7d9771 100644 --- a/src/components/Sidebar/LoadSaveActions.vue +++ b/src/components/Navbar/LoadSaveActions.vue @@ -162,33 +162,41 @@ onBeforeUnmount(() => { diff --git a/src/components/Sidebar/AboutSection.vue b/src/components/Sidebar/AboutSection.vue deleted file mode 100644 index 4f6b9bb..0000000 --- a/src/components/Sidebar/AboutSection.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - - - diff --git a/src/components/Sidebar/EditButtons.vue b/src/components/Sidebar/EditButtons.vue deleted file mode 100644 index c68af1b..0000000 --- a/src/components/Sidebar/EditButtons.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - - - diff --git a/src/components/Sidebar/FeatureDragBar.vue b/src/components/Sidebar/FeatureDragBar.vue new file mode 100644 index 0000000..c5d187e --- /dev/null +++ b/src/components/Sidebar/FeatureDragBar.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/components/Sidebar/FeatureSelector.vue b/src/components/Sidebar/FeatureSelector.vue index d4c596f..e51f58a 100644 --- a/src/components/Sidebar/FeatureSelector.vue +++ b/src/components/Sidebar/FeatureSelector.vue @@ -45,7 +45,7 @@ const features = ['Left Eye', 'Left Eyebrow', 'Right Eye', 'Right Eyebrow', 'Nos diff --git a/src/components/Sidebar/ModelSelector.vue b/src/components/Sidebar/ModelSelector.vue index e38c5e2..f4bf170 100644 --- a/src/components/Sidebar/ModelSelector.vue +++ b/src/components/Sidebar/ModelSelector.vue @@ -7,6 +7,7 @@ import { WebServiceModel } from '@/model/webservice'; import { MediapipeModel } from '@/model/mediapipe'; import { useModelStore } from '@/stores/modelStore'; import { urlError } from '@/enums/urlError'; +import WebserviceSelectModal from '@/components/Modals/WebserviceSelectModal.vue'; const modelStore = useModelStore(); const showModal = ref(false); @@ -111,72 +112,8 @@ function setModel(model: ModelType): boolean { >Webservice
Online +
- -

- The webservice address will be used to detect a face mesh on selected images. Therefore, the - images must be transferred to the webservice for processing. The open format allows it to - create individual webservices by everyone and can be easily swapped. -

- -
-
API
-

- The webservice API must provide the following addresses: -
/detect
- This call is used to detect a single face on a provided image file inside a POST request. -
/annotations
- This call is used to sync the annotations inside a POST request when the user triggers the - download. -

-
-
URL
-

- Insert the webservice URL in the text field below and submit with hitting the Save button. -

- - - - -
+ - - diff --git a/src/components/Sidebar/ViewOptions.vue b/src/components/Sidebar/ViewOptions.vue index d96ca5e..5bd8669 100644 --- a/src/components/Sidebar/ViewOptions.vue +++ b/src/components/Sidebar/ViewOptions.vue @@ -1,34 +1,44 @@ diff --git a/src/editor2d.ts b/src/editor2d.ts index ff09126..2378148 100644 --- a/src/editor2d.ts +++ b/src/editor2d.ts @@ -99,6 +99,7 @@ export class Editor2D { setBackgroundSource(source: ImageFile): void { this.image.src = source.html; this.draw(); + this.center(); } setOnPointsEditedCallback(callback: (graph: Graph) => void) { diff --git a/src/view/checkbox.ts b/src/view/checkbox.ts deleted file mode 100644 index 7bdd545..0000000 --- a/src/view/checkbox.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Represents a checkbox element. - */ -export class CheckBox { - private elem: HTMLInputElement; - - /** - * Creates a new CheckBox instance. - * @param {string} id - The ID of the checkbox element. - * @param {() => void} onChangeCallback - A callback function to execute when the checkbox value changes. - */ - constructor(id: string, onChangeCallback: () => void) { - this.elem = document.getElementById(id) as HTMLInputElement; - this.elem.onchange = onChangeCallback; - } - - /** - * Checks whether the checkbox is currently checked. - * @returns {boolean} - True if checked, false otherwise. - */ - isChecked(): boolean { - return this.elem.checked; - } - - /** - * Sets the checkbox to the specified checked state. - * @param {boolean} checked - The desired checked state (true or false). - */ - setChecked(checked: boolean): void { - this.elem.checked = checked; - } -} diff --git a/src/view/slider.ts b/src/view/slider.ts deleted file mode 100644 index 908cb1d..0000000 --- a/src/view/slider.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Represents a slider input element. - */ -export class Slider { - private readonly slider: HTMLInputElement; - private readonly onChangeCallback: () => void; - - /** - * Creates a new Slider instance. - * @param {string} id - The ID of the slider element. - * @param {() => void} onChangeCallback - A callback function to execute when the slider value changes. - */ - constructor(id: string, onChangeCallback: () => void) { - this.slider = document.getElementById(id) as HTMLInputElement; - this.slider.oninput = onChangeCallback; - this.onChangeCallback = onChangeCallback; - } - - /** - * Gets the minimum value of the slider. - * @returns {number} - The minimum value. - */ - getMin(): number { - return parseInt(this.slider.min); - } - - /** - * Gets the maximum value of the slider. - * @returns {number} - The maximum value. - */ - getMax(): number { - return parseInt(this.slider.max); - } - - /** - * Gets the current value of the slider. - * @returns {number} - The current value. - */ - getValue(): number { - return parseInt(this.slider.value); - } - - /** - * Sets the value of the slider. - * @param {number} value - The desired value. - */ - setValue(value: number): void { - this.slider.value = String(value); - this.onChangeCallback(); - } -} From 984005b3292b5ec11334ef760f4d22a45da3f9f5 Mon Sep 17 00:00:00 2001 From: Marc-Lorenz <169900493+Marc-Lorenz@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:38:50 +0200 Subject: [PATCH 03/25] add infrastructure to dynamically build the sidebar menu --- components.d.ts | 14 +- package-lock.json | 443 ++++++++++-------- package.json | 3 +- src/App.vue | 29 ++ src/components/Main/SidebarContainer.vue | 45 +- .../Main/__tests__/SidebarCointainer.spec.ts | 20 +- .../Sidebar/AnnotationToolSelector.vue | 25 + src/components/Sidebar/ToolMenu/FaceMesh.vue | 13 + .../FaceMesh}/FeatureDragBar.vue | 0 .../FaceMesh}/FeatureSelector.vue | 0 .../{ => ToolMenu/FaceMesh}/ModelSelector.vue | 5 +- .../{ => ToolMenu/FaceMesh}/ViewOptions.vue | 2 +- src/components/Sidebar/ToolMenuContainer.vue | 48 ++ src/enums/annotationTool.ts | 7 + .../__tests__/annotationToolStore.spec.ts | 22 + src/stores/annotationToolStore.ts | 17 + 16 files changed, 438 insertions(+), 255 deletions(-) create mode 100644 src/components/Sidebar/AnnotationToolSelector.vue create mode 100644 src/components/Sidebar/ToolMenu/FaceMesh.vue rename src/components/Sidebar/{ => ToolMenu/FaceMesh}/FeatureDragBar.vue (100%) rename src/components/Sidebar/{ => ToolMenu/FaceMesh}/FeatureSelector.vue (100%) rename src/components/Sidebar/{ => ToolMenu/FaceMesh}/ModelSelector.vue (97%) rename src/components/Sidebar/{ => ToolMenu/FaceMesh}/ViewOptions.vue (92%) create mode 100644 src/components/Sidebar/ToolMenuContainer.vue create mode 100644 src/enums/annotationTool.ts create mode 100644 src/stores/__tests__/annotationToolStore.spec.ts create mode 100644 src/stores/annotationToolStore.ts diff --git a/components.d.ts b/components.d.ts index a5b662b..1ed35a4 100644 --- a/components.d.ts +++ b/components.d.ts @@ -8,7 +8,11 @@ export {} declare module 'vue' { export interface GlobalComponents { AboutSection: typeof import('./src/components/Navbar/AboutSection.vue')['default'] + AnnotationToolSelector: typeof import('./src/components/Sidebar/AnnotationToolSelector.vue')['default'] BButton: typeof import('bootstrap-vue-next')['BButton'] + BCard: typeof import('bootstrap-vue-next')['BCard'] + BCollapse: typeof import('bootstrap-vue-next')['BCollapse'] + BDropdown: typeof import('bootstrap-vue-next')['BDropdown'] BDropdownDivider: typeof import('bootstrap-vue-next')['BDropdownDivider'] BDropdownItem: typeof import('bootstrap-vue-next')['BDropdownItem'] BModal: typeof import('bootstrap-vue-next')['BModal'] @@ -19,15 +23,17 @@ declare module 'vue' { ButtonWithIcon: typeof import('./src/components/MenuItems/ButtonWithIcon.vue')['default'] CentralCanvas: typeof import('./src/components/Main/CentralCanvas.vue')['default'] EditButtons: typeof import('./src/components/Navbar/EditButtons.vue')['default'] - FeatureDragBar: typeof import('./src/components/Sidebar/FeatureDragBar.vue')['default'] - FeatureSelector: typeof import('./src/components/Sidebar/FeatureSelector.vue')['default'] + FaceMesh: typeof import('./src/components/Sidebar/ToolMenu/FaceMesh.vue')['default'] + FeatureDragBar: typeof import('./src/components/Sidebar/ToolMenu/FaceMesh/FeatureDragBar.vue')['default'] + FeatureSelector: typeof import('./src/components/Sidebar/ToolMenu/FaceMesh/FeatureSelector.vue')['default'] LoadSaveActions: typeof import('./src/components/Navbar/LoadSaveActions.vue')['default'] - ModelSelector: typeof import('./src/components/Sidebar/ModelSelector.vue')['default'] + ModelSelector: typeof import('./src/components/Sidebar/ToolMenu/FaceMesh/ModelSelector.vue')['default'] SidebarContainer: typeof import('./src/components/Main/SidebarContainer.vue')['default'] ThumbnailContainer: typeof import('./src/components/ThumbnailContainer.vue')['default'] ThumbnailGallery: typeof import('./src/components/Main/ThumbnailGallery.vue')['default'] + ToolMenuContainer: typeof import('./src/components/Sidebar/ToolMenuContainer.vue')['default'] TopNavbar: typeof import('./src/components/Main/TopNavbar.vue')['default'] - ViewOptions: typeof import('./src/components/Sidebar/ViewOptions.vue')['default'] + ViewOptions: typeof import('./src/components/Sidebar/ToolMenu/FaceMesh/ViewOptions.vue')['default'] WebserviceSelectModal: typeof import('./src/components/Modals/WebserviceSelectModal.vue')['default'] } } diff --git a/package-lock.json b/package-lock.json index b4b8702..9f9a8e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ }, "devDependencies": { "@eslint/js": "^8.57.0", + "@floating-ui/vue": "^1.1.4", "@playwright/test": "^1.44.1", "@rushstack/eslint-patch": "^1.8.0", "@sindresorhus/tsconfig": "^5.0.0", @@ -63,7 +64,7 @@ "typescript-eslint": "^7.12.0", "unplugin-vue-components": "^0.27.4", "vite": "^5.3.1", - "vitest": "^1.6.0", + "vitest": "^2.0.5", "vue-tsc": "^2.0.21" }, "engines": { @@ -79,6 +80,20 @@ "node": ">=0.10.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@antfu/utils": { "version": "0.7.10", "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", @@ -1275,6 +1290,73 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz", + "integrity": "sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.7" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.10.tgz", + "integrity": "sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.7" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz", + "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@floating-ui/vue": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.4.tgz", + "integrity": "sha512-ammH7T3vyCx7pmm9OF19Wc42zrGnUw0QvLoidgypWsCLJMtGXEwY7paYIHO+K+oLC3mbWpzIHzeTVienYenlNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0", + "@floating-ui/utils": "^0.2.7", + "vue-demi": ">=0.13.0" + } + }, + "node_modules/@floating-ui/vue/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -1413,16 +1495,19 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, + "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1434,11 +1519,32 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", @@ -1887,12 +1993,6 @@ "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", "dev": true }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, "node_modules/@sindresorhus/tsconfig": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/tsconfig/-/tsconfig-5.0.0.tgz", @@ -2519,96 +2619,87 @@ } }, "node_modules/@vitest/expect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", - "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", + "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "chai": "^4.3.10" + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz", - "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==", + "node_modules/@vitest/pretty-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/utils": "1.6.0", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "node_modules/@vitest/runner": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz", + "integrity": "sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==", "dev": true, + "license": "MIT", "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", - "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", - "dev": true, - "engines": { - "node": ">=12.20" + "@vitest/utils": "2.0.5", + "pathe": "^1.1.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", - "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", "dev": true, + "license": "MIT", "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "2.0.5", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz", - "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", + "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", "dev": true, + "license": "MIT", "dependencies": { - "tinyspy": "^2.2.0" + "tinyspy": "^3.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz", - "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", + "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", "dev": true, + "license": "MIT", "dependencies": { - "diff-sequences": "^29.6.3", + "@vitest/pretty-format": "2.0.5", "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -2619,6 +2710,7 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -3218,12 +3310,13 @@ } }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/async": { @@ -3420,6 +3513,7 @@ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3645,21 +3739,20 @@ } }, "node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", "dev": true, + "license": "MIT", "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=12" } }, "node_modules/chalk": { @@ -3710,15 +3803,13 @@ "dev": true }, "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 16" } }, "node_modules/chokidar": { @@ -5484,13 +5575,11 @@ "dev": true }, "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, + "license": "MIT", "engines": { "node": ">=6" } @@ -5647,15 +5736,6 @@ "node": ">=0.3.1" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7257,6 +7337,7 @@ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -9662,10 +9743,11 @@ } }, "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", "dev": true, + "license": "MIT", "dependencies": { "get-func-name": "^2.0.1" } @@ -11165,12 +11247,13 @@ "dev": true }, "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 14.16" } }, "node_modules/peek-readable": { @@ -11439,32 +11522,6 @@ "node": ">=6.0.0" } }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/proc-log": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", @@ -11559,12 +11616,6 @@ "node": ">=8" } }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/read-package-json-fast": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", @@ -12677,24 +12728,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", - "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", - "dev": true, - "dependencies": { - "js-tokens": "^9.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", - "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", - "dev": true - }, "node_modules/strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", @@ -12963,19 +12996,31 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", + "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -13153,15 +13198,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -13604,15 +13640,16 @@ } }, "node_modules/vite-node": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", - "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz", + "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==", "dev": true, + "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", + "debug": "^4.3.5", + "pathe": "^1.1.2", + "tinyrainbow": "^1.2.0", "vite": "^5.0.0" }, "bin": { @@ -13626,31 +13663,31 @@ } }, "node_modules/vitest": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", - "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", - "dev": true, - "dependencies": { - "@vitest/expect": "1.6.0", - "@vitest/runner": "1.6.0", - "@vitest/snapshot": "1.6.0", - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz", + "integrity": "sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@vitest/expect": "2.0.5", + "@vitest/pretty-format": "^2.0.5", + "@vitest/runner": "2.0.5", + "@vitest/snapshot": "2.0.5", + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", + "chai": "^5.1.1", + "debug": "^4.3.5", "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", + "magic-string": "^0.30.10", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.8.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "1.6.0", - "why-is-node-running": "^2.2.2" + "vite-node": "2.0.5", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" @@ -13664,8 +13701,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.0", - "@vitest/ui": "1.6.0", + "@vitest/browser": "2.0.5", + "@vitest/ui": "2.0.5", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index 2315891..812d103 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@vue/eslint-config-typescript": "^13.0.0", "@vue/test-utils": "^2.4.6", "@vue/tsconfig": "^0.5.1", + "@floating-ui/vue": "^1.1.4", "codecov": "^3.8.3", "cross-env": "^7.0.3", "cspell": "^8.8.4", @@ -92,7 +93,7 @@ "typescript-eslint": "^7.12.0", "unplugin-vue-components": "^0.27.4", "vite": "^5.3.1", - "vitest": "^1.6.0", + "vitest": "^2.0.5", "vue-tsc": "^2.0.21" }, "files": [ diff --git a/src/App.vue b/src/App.vue index eb19314..3ec69ee 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,8 +1,37 @@