@geode/opengeodeweb-front 10.29.0 → 10.29.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.
@@ -127,10 +127,10 @@ function expandAll() {
127
127
  }
128
128
 
129
129
  function extractIds(node) {
130
- if (node.children && node.children.length > 0) {
130
+ if (node.children) {
131
131
  return node.children.flatMap((child) => extractIds(child));
132
132
  }
133
- if (node.viewer_id !== undefined && node.viewer_id !== null) {
133
+ if (Number.isInteger(node.viewer_id)) {
134
134
  return [node.viewer_id];
135
135
  }
136
136
  return [];
@@ -187,7 +187,7 @@ function getLeafViewerIds(item) {
187
187
 
188
188
  <template #append="{ item }">
189
189
  <v-btn
190
- v-if="item.category || (item.children && item.children.length > 0)"
190
+ v-if="getLeafViewerIds(item).length > 0"
191
191
  icon="mdi-target"
192
192
  size="medium"
193
193
  variant="text"
@@ -33,7 +33,7 @@ export function useVirtualTree(propsIn, emit) {
33
33
 
34
34
  function getLeafChildrenIds(item, ids = []) {
35
35
  const children = item[actualItemProps.value.children];
36
- if (children && children.length > 0) {
36
+ if (children) {
37
37
  for (const child of children) {
38
38
  getLeafChildrenIds(child, ids);
39
39
  }
@@ -1,8 +1,16 @@
1
+ import { MESH_COMPONENT_TYPES } from "@ogw_front/utils/default_styles";
1
2
  import { database } from "@ogw_internal/database/database.js";
2
3
  import { liveQuery } from "dexie";
3
4
  import { useDataMesh } from "./mesh.js";
4
5
  import { useObservable } from "@vueuse/rxjs";
5
6
 
7
+ function pluralize(type) {
8
+ if (type.endsWith("y")) {
9
+ return `${type.slice(0, -1)}ies`;
10
+ }
11
+ return `${type}s`;
12
+ }
13
+
6
14
  export function useDataCollections() {
7
15
  const model_components_db = database.model_components;
8
16
  const model_components_relation_db = database.model_components_relation;
@@ -12,11 +20,7 @@ export function useDataCollections() {
12
20
  const count = await model_components_db
13
21
  .where("id")
14
22
  .equals(modelId)
15
- .and((component) =>
16
- ["Horizon", "Fault", "FaultBlock", "StratigraphicUnit", "ModelBoundary"].includes(
17
- component.type,
18
- ),
19
- )
23
+ .and((component) => !MESH_COMPONENT_TYPES.includes(component.type))
20
24
  .count();
21
25
  return count > 0;
22
26
  }
@@ -24,11 +28,7 @@ export function useDataCollections() {
24
28
  async function getAllCollectionComponents(modelId) {
25
29
  const items = await model_components_db.where("id").equals(modelId).toArray();
26
30
  return items
27
- .filter((component) =>
28
- ["Horizon", "Fault", "FaultBlock", "StratigraphicUnit", "ModelBoundary"].includes(
29
- component.type,
30
- ),
31
- )
31
+ .filter((component) => !MESH_COMPONENT_TYPES.includes(component.type))
32
32
  .map((component) => ({
33
33
  id: component.geode_id,
34
34
  title: component.name,
@@ -68,19 +68,13 @@ export function useDataCollections() {
68
68
 
69
69
  async function formatedCollectionComponents(modelId) {
70
70
  const byType = await fetchAllCollectionComponents(modelId);
71
- const collectionTitles = {
72
- Horizon: "Horizons",
73
- Fault: "Faults",
74
- FaultBlock: "FaultBlocks",
75
- StratigraphicUnit: "StratigraphicUnits",
76
- ModelBoundary: "ModelBoundaries",
77
- };
71
+ const collectionTypes = Object.keys(byType);
78
72
 
79
- return Object.keys(collectionTitles)
73
+ return collectionTypes
80
74
  .filter((type) => byType[type] && byType[type].length > 0)
81
75
  .map((type) => ({
82
76
  id: type,
83
- title: collectionTitles[type],
77
+ title: pluralize(type),
84
78
  children: byType[type],
85
79
  }));
86
80
  }
@@ -1,4 +1,4 @@
1
1
  export const modelComponentsTable = {
2
2
  name: "model_components",
3
- schema: "[id+geode_id], [id+type], viewer_id, name, is_active",
3
+ schema: "[id+geode_id], id, [id+type], viewer_id, name, is_active",
4
4
  };
@@ -1,4 +1,4 @@
1
1
  export const modelComponentsRelationTable = {
2
2
  name: "model_components_relation",
3
- schema: "[id+parent+child], type",
3
+ schema: "[id+parent+child], id, type",
4
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geode/opengeodeweb-front",
3
- "version": "10.29.0",
3
+ "version": "10.29.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": {
@@ -0,0 +1,63 @@
1
+ // Third party imports
2
+ import { describe, expect, test, vi } from "vitest";
3
+ import { ref } from "vue";
4
+
5
+ // Local imports
6
+ import { useVirtualTree } from "@ogw_front/composables/virtual_tree";
7
+
8
+ describe("virtual tree composable", () => {
9
+ test("correctly handles empty collections without treating them as leaf nodes", () => {
10
+ // Structure:
11
+ // ModelBoundaries (category)
12
+ // ├── empty_collection (empty collection component)
13
+ // └── non_empty_collection (non-empty collection component)
14
+ // └── mesh1 (leaf mesh component)
15
+ const items = [
16
+ {
17
+ id: "ModelBoundaries",
18
+ title: "Model Boundaries",
19
+ children: [
20
+ {
21
+ id: "empty_collection",
22
+ title: "Empty Collection",
23
+ children: [],
24
+ },
25
+ {
26
+ id: "non_empty_collection",
27
+ title: "Non-empty Collection",
28
+ children: [
29
+ {
30
+ id: "mesh1",
31
+ title: "Mesh 1",
32
+ },
33
+ ],
34
+ },
35
+ ],
36
+ },
37
+ ];
38
+
39
+ const props = ref({
40
+ items,
41
+ opened: [],
42
+ selected: ["mesh1"],
43
+ selection: { selectable: true, strategy: "classic" },
44
+ });
45
+
46
+ const emit = vi.fn();
47
+ const { isSelected, getIndeterminate } = useVirtualTree(props, emit);
48
+
49
+ // 1. The empty collection has no leaf children, so it should not be considered selected
50
+ expect(isSelected(items[0].children[0])).toBe(false);
51
+
52
+ // 2. The non-empty collection has "mesh1" as a child (which is in the selected set), so it is selected
53
+ expect(isSelected(items[0].children[1])).toBe(true);
54
+
55
+ // 3. The category root (ModelBoundaries) only has "mesh1" as an active leaf descendant.
56
+ // Since "mesh1" is selected, the category root must evaluate to selected (true).
57
+ // (If the bug were present, it would look for both "empty_collection" and "mesh1" in selected, resulting in false)
58
+ expect(isSelected(items[0])).toBe(true);
59
+
60
+ // 4. ModelBoundaries should not be indeterminate since all its leaf descendants are selected
61
+ expect(getIndeterminate(items[0])).toBe(false);
62
+ });
63
+ });
@@ -0,0 +1,127 @@
1
+ // Third party imports
2
+ import { beforeEach, describe, expect, test } from "vitest";
3
+
4
+ // Local imports
5
+ import { setupActivePinia } from "@ogw_tests/utils";
6
+ import { useDataStore } from "@ogw_front/stores/data";
7
+
8
+ describe("useDataStore - collections", () => {
9
+ setupActivePinia();
10
+
11
+ beforeEach(async () => {
12
+ const dataStore = useDataStore();
13
+ await dataStore.clear();
14
+ });
15
+
16
+ test("correctly recognizes, formats, and pluralizes any collection component type not in MESH_COMPONENT_TYPES", async () => {
17
+ const dataStore = useDataStore();
18
+ const modelId = "test_model_id";
19
+
20
+ // 1. Add model item
21
+ await dataStore.addItem({
22
+ id: modelId,
23
+ name: "Test Model",
24
+ viewer_type: "model",
25
+ });
26
+
27
+ // 2. Add mesh components and collection components (including a custom type like "MyCustomCollection")
28
+ await dataStore.addComponents({
29
+ id: modelId,
30
+ mesh_components: [
31
+ {
32
+ geode_id: "mesh_corner",
33
+ name: "Corner 1",
34
+ type: "Corner",
35
+ viewer_id: 1,
36
+ is_active: true,
37
+ },
38
+ {
39
+ geode_id: "mesh_surface",
40
+ name: "Surface 1",
41
+ type: "Surface",
42
+ viewer_id: 2,
43
+ is_active: true,
44
+ },
45
+ ],
46
+ collection_components: [
47
+ // Standard collection type
48
+ { geode_id: "fault1", name: "Fault 1", type: "Fault", viewer_id: 3, is_active: true },
49
+ // Custom collection type (not previously hardcoded)
50
+ {
51
+ geode_id: "custom1",
52
+ name: "Custom 1",
53
+ type: "MyCustomCollection",
54
+ viewer_id: 4,
55
+ is_active: true,
56
+ },
57
+ ],
58
+ });
59
+
60
+ // Add component relations
61
+ await dataStore.addComponentRelations({
62
+ id: modelId,
63
+ collection_components: [
64
+ { geode_id: "fault1", items: ["mesh_surface"] },
65
+ { geode_id: "custom1", items: [] },
66
+ ],
67
+ });
68
+
69
+ // 3. Verify hasCollectionComponents is true
70
+ const hasCollections = await dataStore.hasCollectionComponents(modelId);
71
+ expect(hasCollections).toBe(true);
72
+
73
+ // 4. Verify getAllCollectionComponents returns both Fault and MyCustomCollection
74
+ const allCollections = await dataStore.getAllCollectionComponents(modelId);
75
+ expect(allCollections).toStrictEqual([
76
+ {
77
+ id: "custom1",
78
+ title: "Custom 1",
79
+ category: "MyCustomCollection",
80
+ viewer_id: 4,
81
+ is_active: true,
82
+ },
83
+ { id: "fault1", title: "Fault 1", category: "Fault", viewer_id: 3, is_active: true },
84
+ ]);
85
+
86
+ // 5. Verify formatedCollectionComponents groups them and pluralizes the titles
87
+ const formatted = await dataStore.formatedCollectionComponents(modelId);
88
+ expect(formatted).toStrictEqual([
89
+ {
90
+ id: "MyCustomCollection",
91
+ title: "MyCustomCollections",
92
+ children: [
93
+ {
94
+ id: "custom1",
95
+ title: "Custom 1",
96
+ category: "MyCustomCollection",
97
+ viewer_id: 4,
98
+ is_active: true,
99
+ children: [],
100
+ },
101
+ ],
102
+ },
103
+ {
104
+ id: "Fault",
105
+ title: "Faults",
106
+ children: [
107
+ {
108
+ id: "fault1",
109
+ title: "Fault 1",
110
+ category: "Fault",
111
+ viewer_id: 3,
112
+ is_active: true,
113
+ children: [
114
+ {
115
+ id: "mesh_surface",
116
+ title: "Surface 1",
117
+ category: "Surface",
118
+ viewer_id: 2,
119
+ is_active: true,
120
+ },
121
+ ],
122
+ },
123
+ ],
124
+ },
125
+ ]);
126
+ });
127
+ });