diff --git a/app/components/FeedBack/ErrorBanner.vue b/app/components/FeedBack/ErrorBanner.vue index e78a9ddb7..8b04b4844 100644 --- a/app/components/FeedBack/ErrorBanner.vue +++ b/app/components/FeedBack/ErrorBanner.vue @@ -1,5 +1,6 @@ + + + + diff --git a/app/components/InfraConnected.vue b/app/components/InfraConnected.vue index 3c7623a8a..dcfb36350 100644 --- a/app/components/InfraConnected.vue +++ b/app/components/InfraConnected.vue @@ -1,5 +1,6 @@ diff --git a/app/components/Inspector/InspectionButton.vue b/app/components/Inspector/InspectionButton.vue index 625c7894a..c9d9e7529 100644 --- a/app/components/Inspector/InspectionButton.vue +++ b/app/components/Inspector/InspectionButton.vue @@ -1,6 +1,7 @@ diff --git a/app/components/Viewer/Options/Sliders/Width.vue b/app/components/Viewer/Options/Sliders/Width.vue index c699b9d61..c0b81e991 100644 --- a/app/components/Viewer/Options/Sliders/Width.vue +++ b/app/components/Viewer/Options/Sliders/Width.vue @@ -1,5 +1,6 @@ diff --git a/app/composables/run_function_when_microservices_connected.js b/app/composables/run_function_when_microservices_connected.js index 0248f2208..e12371e56 100644 --- a/app/composables/run_function_when_microservices_connected.js +++ b/app/composables/run_function_when_microservices_connected.js @@ -1,4 +1,5 @@ import { useInfraStore } from "@ogw_front/stores/infra"; + export function run_function_when_microservices_connected(function_to_run) { const infraStore = useInfraStore(); const { microservices_connected } = storeToRefs(infraStore); diff --git a/app/stores/hybrid_viewer.js b/app/stores/hybrid_viewer.js index bce5237fd..a85936844 100644 --- a/app/stores/hybrid_viewer.js +++ b/app/stores/hybrid_viewer.js @@ -1,14 +1,15 @@ import { ACTOR_COLOR, BACKGROUND_COLOR, - HOVER_THROTTLE_MS, WHEEL_TIME_OUT_MS, computeAverageBrightness, performAddItem, + performClear, performClearHoverHighlight, performClickPicking, - performHoverHighlight, + performRemoveItem, performSetContainer, + performSetVisibility, performSetZScaling, } from "@ogw_internal/stores/hybrid_viewer"; import { @@ -19,6 +20,10 @@ import { performSetCamera, performSyncRemoteCamera, } from "@ogw_internal/stores/hybrid_viewer_camera"; +import { + createClearHoverData, + createHoverHighlight, +} from "@ogw_internal/stores/hybrid_viewer_highlight"; import { newInstance as vtkActor } from "@kitware/vtk.js/Rendering/Core/Actor"; import { newInstance as vtkGenericRenderWindow } from "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow"; import { newInstance as vtkMapper } from "@kitware/vtk.js/Rendering/Core/Mapper"; @@ -30,6 +35,7 @@ import { useViewerStore } from "@ogw_front/stores/viewer"; import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json"; +// oxlint-disable max-lines-per-function max-statements export const useHybridViewerStore = defineStore("hybridViewer", () => { const dataStore = useDataStore(); const hybridDb = reactive({}); @@ -41,6 +47,8 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { const is_picking = ref(false); const is_hover_highlight = ref(false); const hover_highlight_field_type = ref("CELL"); + const hoverData = ref(undefined); + const hoverPosition = ref({ x: 0, y: 0 }); const zScale = ref(1); let imageStyle = undefined; let viewStream = undefined; @@ -106,20 +114,14 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { } function removeItem(id) { - if (!hybridDb[id]) { - return; - } - genericRenderWindow.value.getRenderer().removeActor(hybridDb[id].actor); - genericRenderWindow.value.getRenderWindow().render(); - delete hybridDb[id]; + performRemoveItem(id, { genericRenderWindow: genericRenderWindow.value, hybridDb }); } function setVisibility(id, visibility) { - if (!hybridDb[id]) { - return; - } - hybridDb[id].actor.setVisibility(visibility); - genericRenderWindow.value.getRenderWindow().render(); + performSetVisibility(id, visibility, { + genericRenderWindow: genericRenderWindow.value, + hybridDb, + }); } async function setZScaling(z_scale) { @@ -184,20 +186,27 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { return viewerStore.request(viewer_schemas.opengeodeweb_viewer.viewer.render); } - const throttledHoverHighlight = useThrottleFn( - (event) => - performHoverHighlight(event, { - is_hover_highlight, - genericRenderWindow: genericRenderWindow.value, - viewerStore, - viewer_schemas, - hover_highlight_field_type, - hybridDb, - }), - HOVER_THROTTLE_MS, - ); + const hoverTimeoutRef = ref(undefined); + const currentHoverId = ref(undefined); + + const clearHoverData = createClearHoverData(hoverTimeoutRef, hoverData, currentHoverId); + + const hoverHighlight = createHoverHighlight({ + genericRenderWindow, + is_hover_highlight, + viewerStore, + viewer_schemas, + hover_highlight_field_type, + hybridDb, + hoverData, + hoverPosition, + currentHoverId, + hoverTimeoutRef, + clearHoverData, + }); function clearHoverHighlight() { + clearHoverData(); performClearHoverHighlight({ viewerStore, viewer_schemas, @@ -220,7 +229,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { viewerStore, viewer_schemas, syncRemoteCamera, - throttledHoverHighlight, + hoverHighlight, wheelTimeoutMs: WHEEL_TIME_OUT_MS, wheelEventEndTimeout, wheelTimeoutSetter: (timeout) => (wheelEventEndTimeout = timeout), @@ -266,14 +275,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { } function clear() { - const renderer = genericRenderWindow.value.getRenderer(); - for (const actor of renderer.getActors()) { - renderer.removeActor(actor); - } - genericRenderWindow.value.getRenderWindow().render(); - for (const id of Object.keys(hybridDb)) { - delete hybridDb[id]; - } + performClear({ genericRenderWindow: genericRenderWindow.value, hybridDb }); } return { @@ -297,6 +299,8 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { is_hover_highlight, hover_highlight_field_type, clearHoverHighlight, + hoverData, + hoverPosition, clear, exportStores, importStores, diff --git a/app/stores/treeview.js b/app/stores/treeview.js index e3db57de0..2dc6443fc 100644 --- a/app/stores/treeview.js +++ b/app/stores/treeview.js @@ -3,6 +3,7 @@ import { defineStore } from "pinia"; import { ref, toRaw, watch } from "vue"; import { compareSelections } from "@ogw_front/utils/treeview"; import { database } from "@ogw_internal/database/database"; + const PANEL_WIDTH = 300; export const useTreeviewStore = defineStore("treeview", () => { diff --git a/commitlint.config.js b/commitlint.config.js index 0055b515b..0c07c2645 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -14,6 +14,7 @@ const Configuration = { "type-empty": [0], "type-enum": [2, "always", ["feat", "fix", "perf"]], }, + defaultIgnores: false, }; export default Configuration; diff --git a/internal/stores/hybrid_viewer.js b/internal/stores/hybrid_viewer.js index ff539e708..c31dfc2f7 100644 --- a/internal/stores/hybrid_viewer.js +++ b/internal/stores/hybrid_viewer.js @@ -16,6 +16,7 @@ const ACTOR_COLOR = [ ]; const WHEEL_TIME_OUT_MS = 600; const HOVER_THROTTLE_MS = 50; +const HOVER_TIMEOUT_MS = 500; const SAMPLE_SIZE = 10; const TOTAL_CHANNELS = 400; @@ -112,7 +113,7 @@ function performHoverHighlight(event, options) { } const rect = container.getBoundingClientRect(); viewerStore.request( - viewer_schemas.opengeodeweb_viewer.viewer.hover_highlight, + viewer_schemas.opengeodeweb_viewer.viewer.highlight, { x: Math.round(event.clientX - rect.left), y: Math.round(rect.height - (event.clientY - rect.top)), @@ -127,7 +128,7 @@ function performHoverHighlight(event, options) { function performClearHoverHighlight(options) { const { viewerStore, viewer_schemas, hover_highlight_field_type, hybridDb } = options; - viewerStore.request(viewer_schemas.opengeodeweb_viewer.viewer.hover_highlight, { + viewerStore.request(viewer_schemas.opengeodeweb_viewer.viewer.highlight, { x: -1, y: -1, field_type: hover_highlight_field_type.value, @@ -200,7 +201,7 @@ function performSetContainer(options) { viewerStore, viewer_schemas, syncRemoteCamera, - throttledHoverHighlight, + hoverHighlight, wheelTimeoutMs, wheelEventEndTimeout, wheelTimeoutSetter, @@ -251,7 +252,7 @@ function performSetContainer(options) { }, }); - useEventListener(container, "mousemove", throttledHoverHighlight); + useEventListener(container, "mousemove", hoverHighlight); useEventListener(container, "wheel", () => { is_moving.value = true; if (imageStyle) { @@ -268,11 +269,43 @@ function performSetContainer(options) { }); } +function performRemoveItem(id, options) { + const { genericRenderWindow, hybridDb } = options; + if (!hybridDb[id]) { + return; + } + genericRenderWindow.getRenderer().removeActor(hybridDb[id].actor); + genericRenderWindow.getRenderWindow().render(); + delete hybridDb[id]; +} + +function performSetVisibility(id, visibility, options) { + const { genericRenderWindow, hybridDb } = options; + if (!hybridDb[id]) { + return; + } + hybridDb[id].actor.setVisibility(visibility); + genericRenderWindow.getRenderWindow().render(); +} + +function performClear(options) { + const { genericRenderWindow, hybridDb } = options; + const renderer = genericRenderWindow.getRenderer(); + for (const actor of renderer.getActors()) { + renderer.removeActor(actor); + } + genericRenderWindow.getRenderWindow().render(); + for (const id of Object.keys(hybridDb)) { + delete hybridDb[id]; + } +} + export { BACKGROUND_COLOR, ACTOR_COLOR, WHEEL_TIME_OUT_MS, HOVER_THROTTLE_MS, + HOVER_TIMEOUT_MS, computeAverageBrightness, performAddItem, performClearHoverHighlight, @@ -280,4 +313,7 @@ export { performHoverHighlight, performSetContainer, performSetZScaling, + performRemoveItem, + performSetVisibility, + performClear, }; diff --git a/internal/stores/hybrid_viewer_highlight.js b/internal/stores/hybrid_viewer_highlight.js new file mode 100644 index 000000000..c4949abdc --- /dev/null +++ b/internal/stores/hybrid_viewer_highlight.js @@ -0,0 +1,113 @@ +import { HOVER_THROTTLE_MS, HOVER_TIMEOUT_MS, performHoverHighlight } from "./hybrid_viewer"; +import { database } from "@ogw_internal/database/database.js"; + +function createClearHoverData(hoverTimeoutRef, hoverData, currentHoverId) { + return function clearHoverData() { + if (hoverTimeoutRef.value) { + clearTimeout(hoverTimeoutRef.value); + hoverTimeoutRef.value = undefined; + } + hoverData.value = undefined; + currentHoverId.value = undefined; + }; +} + +function createHoverHighlight(options) { + const { + genericRenderWindow, + is_hover_highlight, + viewerStore, + viewer_schemas, + hover_highlight_field_type, + hybridDb, + hoverData, + hoverPosition, + currentHoverId, + hoverTimeoutRef, + clearHoverData, + } = options; + + return useThrottleFn((event) => { + const containerElement = genericRenderWindow.value?.getContainer(); + let relativeMousePosition = { x: 0, y: 0 }; + if (containerElement) { + const containerRect = containerElement.getBoundingClientRect(); + relativeMousePosition = { + x: event.clientX - containerRect.left, + y: event.clientY - containerRect.top, + }; + } else { + relativeMousePosition = { x: event.clientX, y: event.clientY }; + } + + performHoverHighlight(event, { + is_hover_highlight, + genericRenderWindow: genericRenderWindow.value, + viewerStore, + viewer_schemas, + hover_highlight_field_type, + hybridDb, + onResponse: async (response) => { + const isResponseValid = + response && response.id && response.picked_id !== undefined && response.picked_id !== -1; + if (!is_hover_highlight.value || !isResponseValid) { + clearHoverData(); + return; + } + + const hoverKey = `${response.id}_${response.field_type}_${response.picked_id}`; + if (currentHoverId.value === hoverKey) { + return; + } + + if (hoverTimeoutRef.value) { + clearTimeout(hoverTimeoutRef.value); + hoverTimeoutRef.value = undefined; + } + + hoverData.value = undefined; + currentHoverId.value = hoverKey; + + let componentInfo = undefined; + let modelName = undefined; + + const modelRecord = await database.data.get(response.id); + if (modelRecord) { + modelName = modelRecord.name; + } + + if (response.geode_id) { + const component = await database.model_components + .where("[id+geode_id]") + .equals([response.id, response.geode_id]) + .first(); + if (component) { + componentInfo = { + name: component.name, + id: component.geode_id, + type: component.type, + }; + } + } + + const newHoverData = { + modelId: response.id, + modelName, + blockName: response.geode_id, + pickedId: response.picked_id, + fieldType: response.field_type, + component: componentInfo, + attributes: response.attributes || {}, + }; + + hoverTimeoutRef.value = setTimeout(() => { + hoverPosition.value = relativeMousePosition; + hoverData.value = newHoverData; + hoverTimeoutRef.value = undefined; + }, HOVER_TIMEOUT_MS); + }, + }); + }, HOVER_THROTTLE_MS); +} + +export { createClearHoverData, createHoverHighlight };