@geode/opengeodeweb-front 10.37.0 → 10.38.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +0 -1
  2. package/app/components/Viewer/ContextMenu/ContextMenu.vue +2 -0
  3. package/app/components/Viewer/EdgedCurve/SpecificEdgesOptions.vue +21 -1
  4. package/app/components/Viewer/Generic/Mesh/CellsOptions.vue +21 -1
  5. package/app/components/Viewer/Generic/Mesh/EdgesOptions.vue +21 -1
  6. package/app/components/Viewer/Generic/Mesh/PointsOptions.vue +13 -1
  7. package/app/components/Viewer/Generic/Mesh/PolygonsOptions.vue +21 -1
  8. package/app/components/Viewer/Generic/Mesh/PolyhedraOptions.vue +21 -1
  9. package/app/components/Viewer/Generic/Model/ModelStyleCard.vue +29 -31
  10. package/app/components/Viewer/ObjectTree/Layout.vue +2 -1
  11. package/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue +24 -10
  12. package/app/components/Viewer/ObjectTree/Views/ModelTree.vue +5 -15
  13. package/app/components/Viewer/Options/AttributeColorBar.vue +7 -0
  14. package/app/components/Viewer/Options/AttributeSelector.vue +71 -21
  15. package/app/components/Viewer/Options/ColoringTypeSelector.vue +16 -0
  16. package/app/components/Viewer/Options/TextureItem.vue +15 -11
  17. package/app/components/Viewer/PointSet/SpecificPointsOptions.vue +13 -1
  18. package/app/components/Viewer/Solid/SpecificPolyhedraOptions.vue +21 -1
  19. package/app/components/Viewer/Surface/PolygonsOptions.vue +21 -1
  20. package/app/components/Viewer/Ui.vue +7 -3
  21. package/app/composables/batch_style.ts +64 -1
  22. package/app/composables/tree_filter.ts +8 -8
  23. package/app/composables/use_overlapping_picker.ts +2 -0
  24. package/app/composables/viewer_context_menu.ts +123 -0
  25. package/app/stores/menu.ts +3 -0
  26. package/app/utils/attributes.ts +38 -1
  27. package/app/utils/request_for_targets.ts +24 -0
  28. package/app/utils/treeview.ts +30 -0
  29. package/package.json +7 -9
@@ -0,0 +1,123 @@
1
+ import { type MenuMetaData, useMenuStore } from "@ogw_front/stores/menu";
2
+ import type { Ref, ShallowRef } from "vue";
3
+ import type { TreeMenuPayload } from "@ogw_front/utils/treeview";
4
+ import type { ViewerIdResult } from "@ogw_front/composables/use_overlapping_picker";
5
+ import { useDataStore } from "@ogw_front/stores/data";
6
+ import { useElementSize } from "@vueuse/core";
7
+
8
+ interface ViewerUIExposed {
9
+ get_viewer_id: (x: number, y: number) => Promise<ViewerIdResult>;
10
+ }
11
+
12
+ interface MenuTarget {
13
+ id: string;
14
+ meta_data: MenuMetaData;
15
+ }
16
+
17
+ async function treeMenuTarget(
18
+ dataStore: ReturnType<typeof useDataStore>,
19
+ payload: TreeMenuPayload,
20
+ ): Promise<MenuTarget | undefined> {
21
+ if (payload.context_type === "geode_object_type") {
22
+ const [referenceId] = payload.targetIds;
23
+ if (referenceId === undefined) {
24
+ return undefined;
25
+ }
26
+ const referenceItem = await dataStore.item(referenceId);
27
+ return { id: referenceId, meta_data: { ...referenceItem, targetIds: payload.targetIds } };
28
+ }
29
+ if (payload.context_type === "model_component") {
30
+ return {
31
+ id: payload.itemId,
32
+ meta_data: {
33
+ viewer_type: "model_component",
34
+ geode_object_type: "component",
35
+ modelId: payload.modelId,
36
+ pickedComponentId: payload.itemId,
37
+ },
38
+ };
39
+ }
40
+ if (payload.context_type === "model_component_type") {
41
+ return {
42
+ id: payload.itemId,
43
+ meta_data: {
44
+ viewer_type: "model_component_type",
45
+ geode_object_type: "type",
46
+ modelId: payload.modelId,
47
+ modelComponentType: payload.modelComponentType,
48
+ targetComponentIds: payload.targetComponentIds,
49
+ },
50
+ };
51
+ }
52
+ return { id: payload.itemId, meta_data: { ...(await dataStore.item(payload.itemId)) } };
53
+ }
54
+
55
+ async function viewerMenuTarget(
56
+ dataStore: ReturnType<typeof useDataStore>,
57
+ pickedId: string,
58
+ viewer_id: number | undefined,
59
+ ): Promise<MenuTarget> {
60
+ const item = await dataStore.item(pickedId);
61
+ const meta_data: MenuMetaData = { ...item };
62
+ if (item.viewer_type === "model" && viewer_id !== undefined) {
63
+ const component = await dataStore.getComponentByViewerId(pickedId, viewer_id);
64
+ if (component) {
65
+ meta_data.pickedComponentId = component.geode_id;
66
+ }
67
+ }
68
+ return { id: pickedId, meta_data };
69
+ }
70
+
71
+ export function useViewerContextMenu(
72
+ container: Readonly<ShallowRef<HTMLElement | null>>,
73
+ viewerUI: Readonly<ShallowRef<ViewerUIExposed | null>>,
74
+ ): {
75
+ containerWidth: Ref<number>;
76
+ containerHeight: Ref<number>;
77
+ openTreeMenu: (payload: TreeMenuPayload) => Promise<void>;
78
+ openViewerMenu: (event: MouseEvent) => Promise<void>;
79
+ } {
80
+ const menuStore = useMenuStore();
81
+ const dataStore = useDataStore();
82
+ const { width: containerWidth, height: containerHeight } = useElementSize(container);
83
+
84
+ function openMenuAt(event: MouseEvent, element: HTMLElement, target: MenuTarget): void {
85
+ const rect = element.getBoundingClientRect();
86
+ menuStore.openMenu({
87
+ id: target.id,
88
+ x: event.clientX - rect.left,
89
+ y: event.clientY - rect.top,
90
+ width: containerWidth.value,
91
+ height: containerHeight.value,
92
+ top: rect.top,
93
+ left: rect.left,
94
+ meta_data: target.meta_data,
95
+ });
96
+ }
97
+
98
+ async function openTreeMenu(payload: TreeMenuPayload): Promise<void> {
99
+ const element = container.value;
100
+ const target = await treeMenuTarget(dataStore, payload);
101
+ if (element && target) {
102
+ openMenuAt(payload.event, element, target);
103
+ }
104
+ }
105
+
106
+ async function openViewerMenu(event: MouseEvent): Promise<void> {
107
+ const element = container.value;
108
+ const viewerComponent = viewerUI.value;
109
+ if (!element || !viewerComponent) {
110
+ return;
111
+ }
112
+ const rect = element.getBoundingClientRect();
113
+ const x = event.clientX - rect.left;
114
+ const yPicking = containerHeight.value - (event.clientY - rect.top);
115
+ const { id: pickedId, viewer_id } = await viewerComponent.get_viewer_id(x, yPicking);
116
+ if (pickedId === undefined) {
117
+ return;
118
+ }
119
+ openMenuAt(event, element, await viewerMenuTarget(dataStore, pickedId, viewer_id));
120
+ }
121
+
122
+ return { containerWidth, containerHeight, openTreeMenu, openViewerMenu };
123
+ }
@@ -45,6 +45,7 @@ type MenuItems = Component[];
45
45
  interface MenuMetaData {
46
46
  viewer_type?: string;
47
47
  geode_object_type?: string;
48
+ targetIds?: string[];
48
49
  [key: string]: unknown;
49
50
  }
50
51
 
@@ -246,3 +247,5 @@ export const useMenuStore = defineStore("menu", () => {
246
247
  toggleItemOptions,
247
248
  };
248
249
  });
250
+
251
+ export type { MenuMetaData };
@@ -62,4 +62,41 @@ const MODEL_COMPONENT_KINDS: Record<string, string[]> = {
62
62
  Block: ["vertex", "polyhedron"],
63
63
  };
64
64
 
65
- export { MESH_ELEMENT_KINDS, MODEL_COMPONENT_KINDS, getAttributeRange };
65
+ function intersectBy<TItem>(
66
+ lists: readonly (readonly TItem[])[],
67
+ key: (item: TItem) => string,
68
+ ): TItem[] {
69
+ const [first = [], ...others] = lists;
70
+ return first.filter((item) =>
71
+ others.every((list) => list.some((other) => key(other) === key(item))),
72
+ );
73
+ }
74
+
75
+ interface NamedAttribute {
76
+ attribute_name: string;
77
+ nb_items: number;
78
+ }
79
+
80
+ function intersectAttributes<TAttribute extends NamedAttribute>(
81
+ attributesPerData: readonly (readonly TAttribute[])[],
82
+ ): TAttribute[] {
83
+ const allAttributes = attributesPerData.flat();
84
+ const common: TAttribute[] = [];
85
+ for (const attribute of intersectBy(attributesPerData, (item) => item.attribute_name)) {
86
+ const nb_items = Math.min(
87
+ ...allAttributes
88
+ .filter((other) => other.attribute_name === attribute.attribute_name)
89
+ .map((other) => other.nb_items),
90
+ );
91
+ common.push({ ...attribute, nb_items });
92
+ }
93
+ return common;
94
+ }
95
+
96
+ export {
97
+ MESH_ELEMENT_KINDS,
98
+ MODEL_COMPONENT_KINDS,
99
+ getAttributeRange,
100
+ intersectAttributes,
101
+ intersectBy,
102
+ };
@@ -0,0 +1,24 @@
1
+ import type { JsonRpcSchema } from "@ogw_shared/utils/types.js";
2
+ import { useBackStore } from "@ogw_front/stores/back";
3
+
4
+ async function requestForTargets<TResponse>(
5
+ schema: JsonRpcSchema,
6
+ targetIds: readonly string[],
7
+ fallback: TResponse,
8
+ ): Promise<[string, TResponse][]> {
9
+ const backStore = useBackStore();
10
+ const responses = await Promise.all(
11
+ targetIds.map(async (targetId): Promise<[string, TResponse]> => {
12
+ try {
13
+ const response = await backStore.request({ schema, params: { id: targetId } });
14
+ // oxlint-disable-next-line no-unsafe-type-assertion -- this is the trusted API boundary.
15
+ return [targetId, response as TResponse];
16
+ } catch {
17
+ return [targetId, fallback];
18
+ }
19
+ }),
20
+ );
21
+ return responses;
22
+ }
23
+
24
+ export { requestForTargets };
@@ -7,4 +7,34 @@ function compareSelections<Item>(
7
7
  return { added, removed };
8
8
  }
9
9
 
10
+ interface DataMenuPayload {
11
+ event: MouseEvent;
12
+ itemId: string;
13
+ context_type?: undefined;
14
+ }
15
+
16
+ interface GeodeObjectTypeMenuPayload {
17
+ event: MouseEvent;
18
+ itemId: string;
19
+ context_type: "geode_object_type";
20
+ targetIds: string[];
21
+ }
22
+
23
+ interface ModelComponentMenuPayload {
24
+ event: MouseEvent;
25
+ itemId: string;
26
+ context_type: "model_component" | "model_component_type";
27
+ modelId: string;
28
+ modelComponentType?: string;
29
+ targetComponentIds?: string[];
30
+ }
31
+
32
+ type TreeMenuPayload = DataMenuPayload | GeodeObjectTypeMenuPayload | ModelComponentMenuPayload;
33
+
10
34
  export { compareSelections };
35
+ export type {
36
+ DataMenuPayload,
37
+ GeodeObjectTypeMenuPayload,
38
+ ModelComponentMenuPayload,
39
+ TreeMenuPayload,
40
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geode/opengeodeweb-front",
3
- "version": "10.37.0",
3
+ "version": "10.38.0-rc.1",
4
4
  "description": "OpenSource Vue/Nuxt/Pinia/Vuetify framework for web applications",
5
5
  "homepage": "https://github.com/Geode-solutions/OpenGeodeWeb-Front",
6
6
  "bugs": {
@@ -51,7 +51,6 @@
51
51
  "scripts": {
52
52
  "lint": "oxlint --fix .",
53
53
  "prepare:types": "cross-env MODE=BROWSER nuxi prepare",
54
- "typecheck": "nuxt typecheck && tsc -p tsconfig.tests.json --noEmit",
55
54
  "build:back": "npx opengeodeweb-microservice-pyinstaller ../OpenGeodeWeb-Back",
56
55
  "build:viewer": "npx opengeodeweb-microservice-pyinstaller ../OpenGeodeWeb-Viewer",
57
56
  "build:microservices": "concurrently \"npm run build:back\" \"npm run build:viewer\"",
@@ -65,9 +64,9 @@
65
64
  "prepublishOnly": "npm run build"
66
65
  },
67
66
  "dependencies": {
68
- "@geode/cloud-api": "latest",
69
- "@geode/opengeodeweb-back": "latest",
70
- "@geode/opengeodeweb-viewer": "latest",
67
+ "@geode/cloud-api": "next",
68
+ "@geode/opengeodeweb-back": "next",
69
+ "@geode/opengeodeweb-viewer": "next",
71
70
  "@kitware/vtk.js": "33.3.0",
72
71
  "@mdi/font": "7.4.47",
73
72
  "@pinia/nuxt": "1.0.1",
@@ -114,20 +113,19 @@
114
113
  "cross-env": "10.0.0",
115
114
  "happy-dom": "20.0.11",
116
115
  "msw": "2.11.1",
117
- "oxlint": "1.82.0",
116
+ "oxlint": "latest",
117
+ "oxlint-tsgolint": "latest",
118
118
  "playwright-core": "1.52.0",
119
119
  "prettier": "3.8.1",
120
120
  "resize-observer-polyfill": "1.5.1",
121
121
  "rolldown": "1.2.8",
122
122
  "tsx": "4.23.13",
123
- "typescript": "5.9.3",
124
123
  "unplugin-auto-import": "20.0.0",
125
124
  "vite": "8.2.0",
126
125
  "vite-plugin-vuetify": "2.1.3",
127
126
  "vitest": "4.1.10",
128
127
  "vitest-environment-nuxt": "2.0.0",
129
- "vitest-indexeddb": "0.0.1",
130
- "vue-tsc": "3.3.11"
128
+ "vitest-indexeddb": "0.0.1"
131
129
  },
132
130
  "overrides": {
133
131
  "vue": "3.5.40"