From 71cf6129faec1feeebbae322bcb46b538b15502f Mon Sep 17 00:00:00 2001 From: Arnaud Botella Date: Tue, 15 Sep 2026 11:31:10 +0200 Subject: [PATCH 01/28] fix(Oxlint): add more types --- .oxlintrc.json | 117 +++++++++------- app/components/CameraManager/List.vue | 19 ++- app/components/CameraManager/Saver.vue | 8 +- app/components/CameraOrientation.vue | 6 +- app/components/ClippingPlanes.vue | 12 +- app/components/CrsSelector.vue | 13 +- app/components/DragAndDrop.vue | 20 +-- .../DragAndDropInternal/DragAndDropInline.vue | 10 +- app/components/FeedBack/ErrorBanner.vue | 9 +- app/components/FeedBack/Snackers.vue | 12 +- app/components/Loading/EcoMessages.vue | 28 +++- app/components/MissingFilesSelector.vue | 4 +- app/components/ObjectSelector.vue | 10 +- app/components/PackagesVersions.vue | 2 +- .../Viewer/ContextMenu/CenterButton.vue | 25 ++-- .../Viewer/ContextMenu/CircularItems.vue | 11 +- .../Viewer/ContextMenu/ContextMenu.vue | 45 +++--- .../Viewer/ContextMenu/ContextMenuItem.vue | 61 ++++++--- .../Viewer/ContextMenu/InfoCard.vue | 43 ++++-- .../EdgedCurve/SpecificEdgesOptions.vue | 94 +++++++++---- .../Viewer/Generic/Mesh/CellsOptions.vue | 88 ++++++++---- .../Viewer/Generic/Mesh/EdgesOptions.vue | 87 ++++++++---- .../Viewer/Generic/Mesh/PointsOptions.vue | 65 ++++++--- .../Viewer/Generic/Mesh/PolygonsOptions.vue | 100 +++++++++----- .../Viewer/Generic/Mesh/PolyhedraOptions.vue | 111 ++++++++++----- .../Viewer/ObjectTree/Base/CommonTreeView.vue | 129 +++++++++++------- app/components/Viewer/ObjectTree/Layout.vue | 26 ++-- .../ObjectTree/Views/ModelComponents.vue | 12 +- .../Viewer/Options/ColorMapPicker.vue | 9 +- app/components/Viewer/Options/TextureItem.vue | 4 +- app/components/csv-preview/CsvPreviewer.vue | 76 +++++++---- app/components/csv-preview/CsvSettings.vue | 30 ++-- app/components/csv-preview/CsvTable.vue | 16 ++- app/composables/clipping_planes_widget.ts | 12 +- app/composables/use_overlapping_picker.ts | 2 +- app/stores/app.ts | 8 +- app/stores/back.ts | 12 +- app/stores/camera_manager.ts | 19 +-- app/stores/cloud.ts | 6 +- app/stores/data.ts | 51 ++++--- app/stores/data_helpers/collections.ts | 2 +- app/stores/data_helpers/mesh.ts | 10 +- app/stores/data_style.ts | 8 +- app/stores/hybrid_viewer.ts | 2 +- app/stores/infra.ts | 2 +- app/stores/treeview.ts | 3 +- app/stores/viewer.ts | 4 +- app/utils/default_styles/models.ts | 93 +++++++++++-- app/utils/extension.ts | 6 +- app/utils/import_workflow.ts | 5 +- internal/database/base_database.ts | 4 +- internal/database/database.ts | 27 ++-- internal/stores/data_style/mesh/cells/cell.ts | 8 +- .../stores/data_style/mesh/cells/index.ts | 2 +- .../stores/data_style/mesh/cells/vertex.ts | 67 +++++++-- .../stores/data_style/mesh/edges/color.ts | 7 +- internal/stores/data_style/mesh/edges/edge.ts | 8 +- .../stores/data_style/mesh/edges/index.ts | 2 +- .../stores/data_style/mesh/edges/vertex.ts | 8 +- .../data_style/mesh/edges/visibility.ts | 7 +- internal/stores/data_style/mesh/index.ts | 4 +- .../stores/data_style/mesh/points/index.ts | 2 +- .../stores/data_style/mesh/points/vertex.ts | 71 ++++++++-- .../stores/data_style/mesh/polygons/index.ts | 2 +- .../data_style/mesh/polygons/polygon.ts | 8 +- .../stores/data_style/mesh/polygons/vertex.ts | 8 +- .../data_style/mesh/polygons/visibility.ts | 10 +- .../data_style/mesh/polyhedra/common.ts | 11 +- .../stores/data_style/mesh/polyhedra/index.ts | 2 +- .../data_style/mesh/polyhedra/polyhedron.ts | 50 ++++++- .../data_style/mesh/polyhedra/vertex.ts | 70 ++++++++-- .../stores/data_style/model/blocks/index.ts | 8 +- .../data_style/model/blocks/polyhedron.ts | 89 +++++++++--- .../stores/data_style/model/blocks/vertex.ts | 90 +++++++++--- .../data_style/model/blocks/visibility.ts | 11 +- internal/stores/data_style/model/color.ts | 65 ++++++--- internal/stores/data_style/model/common.ts | 2 +- .../stores/data_style/model/corners/common.ts | 20 ++- .../stores/data_style/model/corners/index.ts | 6 +- .../stores/data_style/model/corners/vertex.ts | 28 ++-- .../stores/data_style/model/edges/common.ts | 7 +- .../data_style/model/edges/visibility.ts | 4 +- internal/stores/data_style/model/index.ts | 4 +- .../stores/data_style/model/lines/edge.ts | 20 +-- .../stores/data_style/model/lines/index.ts | 8 +- .../stores/data_style/model/lines/vertex.ts | 28 ++-- .../stores/data_style/model/points/index.ts | 8 +- .../data_style/model/points/visibility.ts | 7 +- internal/stores/data_style/model/selection.ts | 6 +- .../stores/data_style/model/surfaces/color.ts | 21 ++- .../stores/data_style/model/surfaces/index.ts | 8 +- .../data_style/model/surfaces/polygon.ts | 20 +-- .../data_style/model/surfaces/vertex.ts | 20 +-- .../stores/data_style/model/visibility.ts | 37 +++-- internal/stores/data_style/state.ts | 7 +- internal/stores/hybrid_viewer/brightness.ts | 2 +- .../stores/hybrid_viewer/camera_animation.ts | 4 +- internal/stores/hybrid_viewer/highlight.ts | 4 +- internal/stores/hybrid_viewer/scene.ts | 6 +- internal/stores/hybrid_viewer/viewport.ts | 4 +- server/api/cloud/extensions/run.post.ts | 2 +- server/api/local/extensions/run.post.ts | 2 +- server/api/local/extensions/upload.put.ts | 6 +- .../microservice/extensions/download.post.ts | 2 +- server/utils/app_config.ts | 2 +- server/utils/cleanup.ts | 8 +- server/utils/microservices.ts | 4 +- server/utils/scripts.ts | 20 +-- server/utils/server_config.ts | 8 +- shared/scripts.ts | 10 +- shared/utils/call_raw.ts | 4 +- shared/utils/call_schema.ts | 2 +- shared/utils/fetch_raw.ts | 8 +- shared/utils/fetch_schema.ts | 2 +- .../data_style/model/blocks.nuxt.test.ts | 2 +- .../data_style/model/corners.nuxt.test.ts | 2 +- .../data_style/model/edges.nuxt.test.ts | 2 +- .../data_style/model/index.nuxt.test.ts | 2 +- .../data_style/model/lines.nuxt.test.ts | 2 +- .../data_style/model/points.nuxt.test.ts | 2 +- .../data_style/model/surfaces.nuxt.test.ts | 2 +- .../unit/components/CrsSelector.nuxt.test.ts | 4 +- .../components/ExtensionSelector.nuxt.test.ts | 4 +- .../MissingFilesSelector.nuxt.test.ts | 6 +- .../components/ObjectSelector.nuxt.test.ts | 10 +- .../composables/project_manager.nuxt.test.ts | 15 +- ..._when_microservices_connected.nuxt.test.ts | 4 +- tests/unit/plugins/project_load.nuxt.test.ts | 14 +- tests/unit/stores/cloud.nuxt.test.ts | 8 +- tests/unit/stores/infra.nuxt.test.ts | 42 +++--- 130 files changed, 1771 insertions(+), 899 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index c38a3e298..ea7ca9c08 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -7,22 +7,23 @@ "style": "error", "restriction": "error" }, - "plugins": ["import", "node", "oxc", "promise", "unicorn", "vitest", "vue"], + "options": { + "typeAware": true, + "typeCheck": true + }, + "plugins": [ + "eslint", + "import", + "node", + "oxc", + "promise", + "typescript", + "unicorn", + "vitest", + "vue" + ], "rules": { - "unicorn/filename-case": [ - "error", - { - "case": "snakeCase" - } - ], "eslint/func-style": ["error", "declaration"], - "eslint/sort-keys": "off", - "eslint/no-ternary": "off", // A utiliser pour des opérations simples - "unicorn/prefer-ternary": "off", - "oxc/no-async-await": "off", - "one-var": ["error", "never"], - "oxc/no-rest-spread-properties": "off", // Enable if older browser support is needed - "eslint/max-statements": ["warn", 20], "eslint/id-length": [ "error", { @@ -30,37 +31,41 @@ "min": 3 } ], + "eslint/max-statements": ["warn", 20], "eslint/no-console": "warn", // Disable for debugging. Disable later to not have browser logs - "sort-imports": [ + "eslint/no-magic-numbers": [ "error", { - "allowSeparatedGroups": true + "ignore": [-1, 0, 1, 2, 3, 4], + "ignoreArrayIndexes": true } ], + "eslint/no-ternary": "off", // A utiliser pour des opérations simples "eslint/no-undefined": "off", // Conflict with unicorn/no-typeof-undefined which prefers direct undefined comparison - "import/prefer-default-export": "off", - "import/no-named-export": "off", - "import/no-namespace": [ + "eslint/no-underscore-dangle": [ "error", { - "ignore": ["vuetify/*"] + "allow": ["_data", "__dirname", "__VEASE_UTILS__", "__VEASE_STORES__", "__VEASE_SCHEMAS__"] } ], + "eslint/sort-keys": "off", + "import/consistent-type-specifier-style": ["error", "prefer-top-level-if-only-type-imports"], "import/max-dependencies": [ "warn", { "max": 10 } ], - "max-lines-per-function": [ - "warn", + "import/no-named-export": "off", + "import/no-namespace": [ + "error", { - "max": 50, - "skipBlankLines": true, - "skipComments": true + "ignore": ["vuetify/*"] } ], - "unicorn/no-useless-undefined": "off", + "import/no-nodejs-modules": "off", + "import/prefer-default-export": "off", + "import/unambiguous": "off", "max-lines": [ "error", { @@ -69,36 +74,46 @@ "skipComments": true } ], - "vue/max-props": [ - "error", + "max-lines-per-function": [ + "warn", { - "maxProps": 8 + "max": 50, + "skipBlankLines": true, + "skipComments": true } ], - "oxc/no-optional-chaining": "off", - "node/no-process-env": "off", - "no-continue": "off", - "vitest/require-hook": "off", - "import/unambiguous": "off", - "node/no-sync": "off", "max-params": [ "warn", { "max": 4 } ], - "import/no-nodejs-modules": "off", - "eslint/no-magic-numbers": [ + "no-continue": "off", + "node/no-process-env": "off", + "node/no-sync": "off", + "one-var": ["error", "never"], + "oxc/no-async-await": "off", + "oxc/no-optional-chaining": "off", + "oxc/no-rest-spread-properties": "off", // Enable if older browser support is needed + "sort-imports": [ "error", { - "ignore": [-1, 0, 1, 2, 3, 4], - "ignoreArrayIndexes": true + "allowSeparatedGroups": true } ], - "eslint/no-underscore-dangle": [ + "unicorn/filename-case": [ "error", { - "allow": ["_data", "__dirname", "__VEASE_UTILS__", "__VEASE_STORES__", "__VEASE_SCHEMAS__"] + "case": "snakeCase" + } + ], + "unicorn/no-useless-undefined": "off", + "unicorn/prefer-ternary": "off", + "vitest/require-hook": "off", + "vue/max-props": [ + "error", + { + "maxProps": 8 } ] }, @@ -123,21 +138,21 @@ { "files": ["tests/**"], "rules": { - "vitest/require-hook": "off", - "vitest/no-hooks": "off", + "jest/consistent-test-it": "off", "jest/no-hooks": "off", - "vitest/no-importing-vitest-globals": "off", + "jest/prefer-expect-assertions": "off", "max-lines-per-function": "off", "max-statements": "off", - "vitest/prefer-to-be-truthy": "off", - "vitest/prefer-to-be-falsy": "off", - "vitest/require-test-timeout": "warn", - "vitest/prefer-importing-vitest-globals": "off", - "jest/consistent-test-it": "off", "vitest/consistent-test-it": ["error", { "fn": "test", "withinDescribe": "test" }], - "vitest/prefer-spy-on": "off", + "vitest/no-hooks": "off", + "vitest/no-importing-vitest-globals": "off", "vitest/prefer-expect-assertions": "off", - "jest/prefer-expect-assertions": "off" + "vitest/prefer-importing-vitest-globals": "off", + "vitest/prefer-spy-on": "off", + "vitest/prefer-to-be-falsy": "off", + "vitest/prefer-to-be-truthy": "off", + "vitest/require-hook": "off", + "vitest/require-test-timeout": "warn" } }, { diff --git a/app/components/CameraManager/List.vue b/app/components/CameraManager/List.vue index 25896cb8e..fe6eb8370 100644 --- a/app/components/CameraManager/List.vue +++ b/app/components/CameraManager/List.vue @@ -8,9 +8,9 @@ const hybridViewerStore = useHybridViewerStore(); const savedPositions = cameraManagerStore.refAllCameraPositions(); const editingId = ref(undefined); -const editingName = ref(""); +const editingName = ref(""); -async function restorePosition(positionId: number) { +async function restorePosition(positionId: number): void { const position = await cameraManagerStore.getCameraPosition(positionId); if (position) { if (hybridViewerStore.genericRenderWindow) { @@ -21,18 +21,21 @@ async function restorePosition(positionId: number) { } } -async function deletePosition(positionId: number) { +async function deletePosition(positionId: number): void { await cameraManagerStore.deleteCameraPosition(positionId); } -function startEditing(position: { id?: number; name?: string }) { +function startEditing(position: { id?: number; name?: string }): void { editingId.value = position.id; editingName.value = position.name ?? ""; } -async function saveRename() { +async function saveRename(): void { if (editingName.value && editingId.value !== undefined) { - await cameraManagerStore.renameCameraPosition(editingId.value, editingName.value); + await cameraManagerStore.renameCameraPosition( + editingId.value, + editingName.value, + ); } editingId.value = undefined; } @@ -104,7 +107,9 @@ async function saveRename() {
- mdi-camera-off + mdi-camera-off No saved positions yet.
diff --git a/app/components/CameraManager/Saver.vue b/app/components/CameraManager/Saver.vue index 66f857722..8efd623cb 100644 --- a/app/components/CameraManager/Saver.vue +++ b/app/components/CameraManager/Saver.vue @@ -1,16 +1,14 @@ - mdi-check-circle-outline + + mdi-check-circle-outline + - mdi-alert-outline + + mdi-alert-outline + diff --git a/app/components/Loading/EcoMessages.vue b/app/components/Loading/EcoMessages.vue index d20d72053..313062e3c 100644 --- a/app/components/Loading/EcoMessages.vue +++ b/app/components/Loading/EcoMessages.vue @@ -5,7 +5,13 @@ interface Props { const { appName } = defineProps(); -const ecoMessages = computed(() => [ +interface EcoMessage { + icon: string; + title: string; + message: string; +} + +const ecoMessages = computed(() => [ { icon: "mdi-leaf", title: "Why the wait?", @@ -14,7 +20,8 @@ const ecoMessages = computed(() => [ { icon: "mdi-lightning-bolt-outline", title: "Lower carbon footprint", - message: "On-demand computing uses up to 70% less energy than always-on servers.", + message: + "On-demand computing uses up to 70% less energy than always-on servers.", }, { icon: "mdi-earth", @@ -24,18 +31,19 @@ const ecoMessages = computed(() => [ ]); const MESSAGE_INTERVAL_MS = 5000; -const currentMessage = ref(0); +const currentMessage = ref(0); let interval: ReturnType | undefined = undefined; // Fallback only guards against noUncheckedIndexedAccess; currentMessage is always kept in range by the modulo below, so it is never actually used. const FALLBACK_ECO_MESSAGE = { icon: "", title: "", message: "" }; -const currentEcoMessage = computed( +const currentEcoMessage = computed( () => ecoMessages.value[currentMessage.value] ?? FALLBACK_ECO_MESSAGE, ); onMounted(() => { interval = setInterval(() => { - currentMessage.value = (currentMessage.value + 1) % ecoMessages.value.length; + currentMessage.value = + (currentMessage.value + 1) % ecoMessages.value.length; }, MESSAGE_INTERVAL_MS); }); @@ -45,7 +53,11 @@ onUnmounted(() => { @@ -105,8 +112,10 @@ function onCenterClick(event: MouseEvent) { position: absolute; inset: 0; background: rgba(255, 255, 255, var(--adaptive-opacity)); - backdrop-filter: blur(var(--adaptive-blur)) brightness(var(--adaptive-brightness)); - -webkit-backdrop-filter: blur(var(--adaptive-blur)) brightness(var(--adaptive-brightness)); + backdrop-filter: blur(var(--adaptive-blur)) + brightness(var(--adaptive-brightness)); + -webkit-backdrop-filter: blur(var(--adaptive-blur)) + brightness(var(--adaptive-brightness)); z-index: 0; pointer-events: none; border-radius: inherit; diff --git a/app/components/Viewer/ContextMenu/CircularItems.vue b/app/components/Viewer/ContextMenu/CircularItems.vue index 17f2469a4..26c82884c 100644 --- a/app/components/Viewer/ContextMenu/CircularItems.vue +++ b/app/components/Viewer/ContextMenu/CircularItems.vue @@ -22,17 +22,20 @@ const ANGLE_315 = 315; const menuStore = useMenuStore(); -function getItemStyle(index: number) { +function getItemStyle(index: number): Record { const angle = (index / menuItemCount) * 2 * Math.PI; return { transform: `translate(${Math.cos(angle) * RADIUS}px, ${Math.sin(angle) * RADIUS}px)`, transition: "opacity 0.2s ease, transform 0.2s ease", position: "absolute", - zIndex: menuStore.active_item_index === index ? Z_INDEX_ACTIVE_ITEM : Z_INDEX_BASE_ITEM, + zIndex: + menuStore.active_item_index === index + ? Z_INDEX_ACTIVE_ITEM + : Z_INDEX_BASE_ITEM, }; } -function getTooltipLocation(index: number) { +function getTooltipLocation(index: number): string { const angle = (index / menuItemCount) * FULL_ANGLE; if (angle < ANGLE_45 || angle >= ANGLE_315) { return "right"; @@ -46,7 +49,7 @@ function getTooltipLocation(index: number) { return "bottom"; } -function getTooltipOrigin(index: number) { +function getTooltipOrigin(index: number): string { const angle = (index / menuItemCount) * FULL_ANGLE; if (angle < ANGLE_45 || angle >= ANGLE_315) { return "left"; diff --git a/app/components/Viewer/ContextMenu/ContextMenu.vue b/app/components/Viewer/ContextMenu/ContextMenu.vue index 3d6c0a884..ed75e2ecf 100644 --- a/app/components/Viewer/ContextMenu/ContextMenu.vue +++ b/app/components/Viewer/ContextMenu/ContextMenu.vue @@ -1,13 +1,11 @@ diff --git a/app/components/Viewer/ContextMenu/ContextMenuItem.vue b/app/components/Viewer/ContextMenu/ContextMenuItem.vue index b8fe5154f..e03f8ee5a 100644 --- a/app/components/Viewer/ContextMenu/ContextMenuItem.vue +++ b/app/components/Viewer/ContextMenu/ContextMenuItem.vue @@ -55,8 +55,10 @@ const TREEVIEW_MARGIN_RIGHT = 20; const TREEVIEW_ICON_WIDTH = 48; const treeviewStore = useTreeviewStore(); -const isOverTreeview = computed(() => { - const hasAdditional = treeviewStore.opened_views.some((view) => view.id !== "main"); +const isOverTreeview = computed(() => { + const hasAdditional = treeviewStore.opened_views.some( + (view) => view.id !== "main", + ); const hasMain = treeviewStore.opened_views.some((view) => view.id === "main"); const firstColWidth = hasMain ? treeviewStore.panelWidth : 0; const secondColWidth = hasAdditional ? treeviewStore.additionalPanelWidth : 0; @@ -69,7 +71,7 @@ const isOverTreeview = computed(() => { return buttonCoords.value.x < treeviewWidth; }); -const isOverToolbar = computed(() => { +const isOverToolbar = computed(() => { const toolbarEl = document.querySelector(".view-toolbar"); if (!toolbarEl) { return false; @@ -84,7 +86,7 @@ const isOverToolbar = computed(() => { ); }); -const computedItemStyles = computed(() => { +const computedItemStyles = computed>(() => { if (isOverTreeview.value || isOverToolbar.value) { return { "--adaptive-blur": "15px", @@ -95,13 +97,17 @@ const computedItemStyles = computed(() => { return adaptiveStyles.value; }); -const is_active = computed(() => menuStore.active_item_index === index); +const is_active = computed( + () => menuStore.active_item_index === index, +); const optionsRef = ref(undefined); const { height: optionsHeight } = useElementSize(optionsRef); -const maxCardHeight = computed(() => Math.min(CARD_HEIGHT, menuStore.containerHeight - OFFSET)); +const maxCardHeight = computed(() => + Math.min(CARD_HEIGHT, menuStore.containerHeight - OFFSET), +); -const optionsStyle = computed(() => { +const optionsStyle = computed>(() => { if (!is_active.value || !optionsHeight.value) { return {}; } @@ -114,25 +120,32 @@ const optionsStyle = computed(() => { if (absoluteButtonY - height / 2 < margin) { offsetY = margin - (absoluteButtonY - height / 2); - } else if (absoluteButtonY + height / 2 > menuStore.containerHeight - margin) { - offsetY = menuStore.containerHeight - margin - (absoluteButtonY + height / 2); + } else if ( + absoluteButtonY + height / 2 > + menuStore.containerHeight - margin + ) { + offsetY = + menuStore.containerHeight - margin - (absoluteButtonY + height / 2); } return { top: `calc(50% + ${offsetY}px)` }; }); -const optionsClass = computed(() => { +const optionsClass = computed(() => { const loc = itemProps.tooltip_location; const margin = MARGIN; const radius = RADIUS; if (loc === "right") { - return menuStore.menuX + radius + margin + CARD_WIDTH > menuStore.containerWidth + return menuStore.menuX + radius + margin + CARD_WIDTH > + menuStore.containerWidth ? "options-left" : "options-right"; } - return menuStore.menuX - radius - margin - CARD_WIDTH < 0 ? "options-right" : "options-left"; + return menuStore.menuX - radius - margin - CARD_WIDTH < 0 + ? "options-right" + : "options-left"; }); -function toggleOptions() { +function toggleOptions(): void { menuStore.toggleItemOptions(index); } @@ -140,7 +153,10 @@ export type { ItemProps };