@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.
- package/app/components/Viewer/ObjectTree/Views/ModelCollections.vue +3 -3
- package/app/composables/virtual_tree.js +1 -1
- package/app/stores/data_helpers/collections.js +13 -19
- package/internal/database/tables/model_components.js +1 -1
- package/internal/database/tables/model_components_relation.js +1 -1
- package/package.json +1 -1
- package/tests/unit/composables/virtual_tree.nuxt.test.js +63 -0
- package/tests/unit/stores/data.nuxt.test.js +127 -0
|
@@ -127,10 +127,10 @@ function expandAll() {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
function extractIds(node) {
|
|
130
|
-
if (node.children
|
|
130
|
+
if (node.children) {
|
|
131
131
|
return node.children.flatMap((child) => extractIds(child));
|
|
132
132
|
}
|
|
133
|
-
if (
|
|
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="
|
|
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
|
|
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
|
|
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
|
|
73
|
+
return collectionTypes
|
|
80
74
|
.filter((type) => byType[type] && byType[type].length > 0)
|
|
81
75
|
.map((type) => ({
|
|
82
76
|
id: type,
|
|
83
|
-
title:
|
|
77
|
+
title: pluralize(type),
|
|
84
78
|
children: byType[type],
|
|
85
79
|
}));
|
|
86
80
|
}
|
package/package.json
CHANGED
|
@@ -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
|
+
});
|