@geode/opengeodeweb-front 10.8.0 → 10.9.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.
- package/app/components/ActionButton.vue +30 -0
- package/app/components/VeaseViewToolbar.vue +5 -7
- package/app/components/ViewToolbar.vue +7 -8
- package/app/components/Viewer/BreadCrumb.vue +15 -22
- package/app/components/Viewer/TreeComponent.vue +116 -28
- package/app/components/Viewer/TreeObject.vue +93 -4
- package/app/stores/data.js +16 -6
- package/app/stores/data_style.js +1 -0
- package/app/stores/treeview.js +2 -2
- package/internal/stores/data_style/model/index.js +112 -70
- package/package.json +3 -3
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const DEFAULT_ICON_SIZE = 28;
|
|
3
|
+
const { icon, tooltip, color, size, variant, density, tooltipLocation, iconSize } = defineProps({
|
|
4
|
+
icon: { type: String, required: true },
|
|
5
|
+
tooltip: { type: String, required: true },
|
|
6
|
+
color: { type: String, default: undefined },
|
|
7
|
+
size: { type: [String, Number], default: undefined },
|
|
8
|
+
variant: { type: String },
|
|
9
|
+
density: { type: String, default: "comfortable" },
|
|
10
|
+
tooltipLocation: { type: String, default: "left" },
|
|
11
|
+
iconSize: { type: [String, Number], default: DEFAULT_ICON_SIZE },
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const emit = defineEmits(["click"]);
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<v-btn
|
|
19
|
+
:color="color"
|
|
20
|
+
:size="size"
|
|
21
|
+
:variant="variant"
|
|
22
|
+
:density="density"
|
|
23
|
+
v-tooltip:[tooltipLocation]="tooltip"
|
|
24
|
+
v-bind="$attrs"
|
|
25
|
+
icon
|
|
26
|
+
@click="emit('click', $event)"
|
|
27
|
+
>
|
|
28
|
+
<v-icon :size="iconSize">{{ icon }}</v-icon>
|
|
29
|
+
</v-btn>
|
|
30
|
+
</template>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
3
3
|
|
|
4
|
+
import ActionButton from "@ogw_front/components/ActionButton.vue";
|
|
4
5
|
import Screenshot from "@ogw_front/components/Screenshot";
|
|
5
6
|
import ZScaling from "@ogw_front/components/ZScaling";
|
|
6
7
|
|
|
@@ -71,14 +72,11 @@ const camera_options = [
|
|
|
71
72
|
<v-container :class="[$style.floatToolbar, 'pa-0']" width="auto">
|
|
72
73
|
<v-row v-for="camera_option in camera_options" :key="camera_option.icon" dense>
|
|
73
74
|
<v-col>
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
<ActionButton
|
|
76
|
+
:icon="camera_option.icon"
|
|
77
|
+
:tooltip="camera_option.tooltip"
|
|
77
78
|
@click.stop="camera_option.action"
|
|
78
|
-
|
|
79
|
-
>
|
|
80
|
-
<v-icon :icon="camera_option.icon" size="32" />
|
|
81
|
-
</v-btn>
|
|
79
|
+
/>
|
|
82
80
|
</v-col>
|
|
83
81
|
</v-row>
|
|
84
82
|
</v-container>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
3
3
|
|
|
4
|
+
import ActionButton from "@ogw_front/components/ActionButton.vue";
|
|
4
5
|
import Screenshot from "@ogw_front/components/Screenshot";
|
|
5
6
|
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
6
7
|
|
|
@@ -45,14 +46,12 @@ const camera_options = [
|
|
|
45
46
|
<v-container :class="[$style.floatToolbar, 'pa-0']" width="auto">
|
|
46
47
|
<v-row v-for="camera_option in camera_options" :key="camera_option.icon" dense>
|
|
47
48
|
<v-col>
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
icon
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<v-icon :icon="camera_option.icon" size="32" />
|
|
55
|
-
</v-btn>
|
|
49
|
+
<ActionButton
|
|
50
|
+
:tooltip="camera_option.tooltip"
|
|
51
|
+
:icon="camera_option.icon"
|
|
52
|
+
tooltip-location="left"
|
|
53
|
+
@click="camera_option.action"
|
|
54
|
+
/>
|
|
56
55
|
</v-col>
|
|
57
56
|
</v-row>
|
|
58
57
|
</v-container>
|
|
@@ -11,33 +11,24 @@ function goBackToFileTree() {
|
|
|
11
11
|
treeviewStore.displayFileTree();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const metaDatas = computed(() => {
|
|
17
|
-
if (!model_id.value) {
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
return dataStore.refItem(model_id.value).value || {};
|
|
21
|
-
});
|
|
14
|
+
const metaDatas = computed(() => dataStore.refItem(treeviewStore.model_id));
|
|
22
15
|
</script>
|
|
23
16
|
|
|
24
17
|
<template>
|
|
25
18
|
<v-breadcrumbs class="mb-n10 breadcrumb-container">
|
|
26
19
|
<div class="d-flex align-center gap-2 ml-2 mt-2 mb-1">
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
<v-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
</template>
|
|
40
|
-
</v-menu>
|
|
20
|
+
<template v-if="treeviewStore.isAdditionnalTreeDisplayed">
|
|
21
|
+
<v-btn icon variant="text" size="medium" @click.stop="goBackToFileTree">
|
|
22
|
+
<v-icon size="large">mdi-file-tree</v-icon>
|
|
23
|
+
</v-btn>
|
|
24
|
+
<span class="text-h5 font-weight-bold">/</span>
|
|
25
|
+
<v-icon size="large">
|
|
26
|
+
{{ selectedTree && selectedTree.icon ? selectedTree.icon : "mdi-shape-outline" }}
|
|
27
|
+
</v-icon>
|
|
28
|
+
<span class="text-subtitle-1 font-weight-regular align-center mt-1">
|
|
29
|
+
Model Explorer ({{ metaDatas.name }})
|
|
30
|
+
</span>
|
|
31
|
+
</template>
|
|
41
32
|
|
|
42
33
|
<div v-else class="d-flex align-center gap-2">
|
|
43
34
|
<v-icon size="large">mdi-file-tree</v-icon>
|
|
@@ -49,6 +40,8 @@ const metaDatas = computed(() => {
|
|
|
49
40
|
|
|
50
41
|
<style scoped>
|
|
51
42
|
.breadcrumb-container {
|
|
43
|
+
position: relative;
|
|
44
|
+
z-index: 10;
|
|
52
45
|
max-width: 100%;
|
|
53
46
|
overflow: hidden;
|
|
54
47
|
white-space: nowrap;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import ActionButton from "@ogw_front/components/ActionButton.vue";
|
|
3
|
+
import SearchBar from "@ogw_front/components/SearchBar.vue";
|
|
2
4
|
import { compareSelections } from "@ogw_front/utils/treeview";
|
|
3
5
|
import { useDataStore } from "@ogw_front/stores/data";
|
|
4
6
|
import { useDataStyleStore } from "@ogw_front/stores/data_style";
|
|
@@ -11,50 +13,132 @@ const hybridViewerStore = useHybridViewerStore();
|
|
|
11
13
|
const { id } = defineProps({ id: { type: String, required: true } });
|
|
12
14
|
const emit = defineEmits(["show-menu"]);
|
|
13
15
|
|
|
14
|
-
const items = dataStore.refFormatedMeshComponents(id);
|
|
15
|
-
const mesh_components_selection =
|
|
16
|
+
const items = dataStore.refFormatedMeshComponents(toRef(() => id));
|
|
17
|
+
const mesh_components_selection = dataStyleStore.visibleMeshComponents(toRef(() => id));
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const search = ref("");
|
|
20
|
+
const sortType = ref("name");
|
|
21
|
+
const filterOptions = ref({
|
|
22
|
+
Corner: true,
|
|
23
|
+
Line: true,
|
|
24
|
+
Surface: true,
|
|
25
|
+
Block: true,
|
|
26
|
+
});
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
if (removed.length > 0) {
|
|
35
|
-
await dataStyleStore.setModelMeshComponentsVisibility(id, removed, false);
|
|
36
|
-
}
|
|
37
|
-
hybridViewerStore.remoteRender();
|
|
38
|
-
},
|
|
39
|
-
{ deep: true },
|
|
28
|
+
const processedItems = computed(() =>
|
|
29
|
+
items.value
|
|
30
|
+
.filter((category) => filterOptions.value[category.id])
|
|
31
|
+
.map((category) => {
|
|
32
|
+
const field = sortType.value === "name" ? "title" : "id";
|
|
33
|
+
const children = (category.children || []).toSorted((first, second) =>
|
|
34
|
+
first[field].localeCompare(second[field]),
|
|
35
|
+
);
|
|
36
|
+
return { id: category.id, title: category.title, children };
|
|
37
|
+
}),
|
|
40
38
|
);
|
|
39
|
+
|
|
40
|
+
const availableFilterOptions = computed(() => items.value.map((category) => category.id));
|
|
41
|
+
|
|
42
|
+
function toggleSort() {
|
|
43
|
+
sortType.value = sortType.value === "name" ? "id" : "name";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function customFilter(value, searchQuery, item) {
|
|
47
|
+
if (!searchQuery) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
const query = searchQuery.toLowerCase();
|
|
51
|
+
return (
|
|
52
|
+
item.raw.title.toLowerCase().includes(query) ||
|
|
53
|
+
(item.raw.id && item.raw.id.toLowerCase().includes(query))
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function onSelectionChange(current) {
|
|
58
|
+
const previous = mesh_components_selection.value;
|
|
59
|
+
const { added, removed } = compareSelections(current, previous);
|
|
60
|
+
|
|
61
|
+
if (added.length === 0 && removed.length === 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (added.length > 0) {
|
|
66
|
+
await dataStyleStore.setComponentsVisibility(id, added, true);
|
|
67
|
+
}
|
|
68
|
+
if (removed.length > 0) {
|
|
69
|
+
await dataStyleStore.setComponentsVisibility(id, removed, false);
|
|
70
|
+
}
|
|
71
|
+
hybridViewerStore.remoteRender();
|
|
72
|
+
}
|
|
41
73
|
</script>
|
|
42
74
|
|
|
43
75
|
<template>
|
|
76
|
+
<v-row dense align="center" class="mr-1 ml-3 mt-2 pa-1">
|
|
77
|
+
<v-col>
|
|
78
|
+
<SearchBar v-model="search" label="Search" color="black" base-color="black" />
|
|
79
|
+
</v-col>
|
|
80
|
+
<v-col cols="auto" class="d-flex align-center">
|
|
81
|
+
<ActionButton
|
|
82
|
+
:tooltip="'Sort by ' + (sortType === 'name' ? 'ID' : 'Name')"
|
|
83
|
+
:icon="
|
|
84
|
+
sortType === 'name' ? 'mdi-sort-alphabetical-ascending' : 'mdi-sort-numeric-ascending'
|
|
85
|
+
"
|
|
86
|
+
tooltipLocation="bottom"
|
|
87
|
+
@click="toggleSort"
|
|
88
|
+
/>
|
|
89
|
+
<v-menu :close-on-content-click="false">
|
|
90
|
+
<template #activator="{ props }">
|
|
91
|
+
<ActionButton
|
|
92
|
+
tooltip="Filter options"
|
|
93
|
+
icon="mdi-filter-variant"
|
|
94
|
+
tooltipLocation="bottom"
|
|
95
|
+
class="ml-1"
|
|
96
|
+
v-bind="props"
|
|
97
|
+
/>
|
|
98
|
+
</template>
|
|
99
|
+
<v-list class="mt-1">
|
|
100
|
+
<v-list-item v-for="category_id in availableFilterOptions" :key="category_id">
|
|
101
|
+
<v-checkbox
|
|
102
|
+
v-model="filterOptions[category_id]"
|
|
103
|
+
:label="category_id"
|
|
104
|
+
hide-details
|
|
105
|
+
density="compact"
|
|
106
|
+
/>
|
|
107
|
+
</v-list-item>
|
|
108
|
+
</v-list>
|
|
109
|
+
</v-menu>
|
|
110
|
+
</v-col>
|
|
111
|
+
</v-row>
|
|
44
112
|
<v-treeview
|
|
45
|
-
|
|
46
|
-
:items="
|
|
113
|
+
:selected="mesh_components_selection"
|
|
114
|
+
:items="processedItems"
|
|
115
|
+
:search="search"
|
|
116
|
+
:custom-filter="customFilter"
|
|
47
117
|
class="transparent-treeview"
|
|
48
118
|
item-value="id"
|
|
49
|
-
select-strategy="
|
|
119
|
+
select-strategy="independent"
|
|
50
120
|
selectable
|
|
121
|
+
@update:selected="onSelectionChange"
|
|
51
122
|
>
|
|
52
123
|
<template #title="{ item }">
|
|
53
124
|
<span
|
|
54
125
|
class="treeview-item"
|
|
126
|
+
:class="{ 'inactive-item': item.is_active === false }"
|
|
55
127
|
@contextmenu.prevent.stop="emit('show-menu', { event: $event, itemId: item })"
|
|
56
|
-
>{{ item.title }}</span
|
|
57
128
|
>
|
|
129
|
+
{{ item.title }}
|
|
130
|
+
<v-tooltip v-if="item.category" activator="parent" location="right">
|
|
131
|
+
<div class="d-flex flex-column pa-1">
|
|
132
|
+
<span class="text-caption"><strong>ID:</strong> {{ item.id }}</span>
|
|
133
|
+
<span v-if="item.title" class="text-caption"
|
|
134
|
+
><strong>Name:</strong> {{ item.title }}</span
|
|
135
|
+
>
|
|
136
|
+
<span class="text-caption font-italic border-t-sm d-flex align-center">
|
|
137
|
+
<strong class="mr-1">Status:</strong> {{ item.is_active ? "Active" : "Inactive" }}
|
|
138
|
+
</span>
|
|
139
|
+
</div>
|
|
140
|
+
</v-tooltip>
|
|
141
|
+
</span>
|
|
58
142
|
</template>
|
|
59
143
|
</v-treeview>
|
|
60
144
|
</template>
|
|
@@ -67,6 +151,10 @@ watch(
|
|
|
67
151
|
max-width: 100%;
|
|
68
152
|
display: inline-block;
|
|
69
153
|
}
|
|
154
|
+
.inactive-item {
|
|
155
|
+
opacity: 0.4;
|
|
156
|
+
font-style: italic;
|
|
157
|
+
}
|
|
70
158
|
|
|
71
159
|
.transparent-treeview {
|
|
72
160
|
background-color: transparent;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import ActionButton from "@ogw_front/components/ActionButton.vue";
|
|
3
|
+
import SearchBar from "@ogw_front/components/SearchBar.vue";
|
|
2
4
|
import { useDataStyleStore } from "@ogw_front/stores/data_style";
|
|
3
5
|
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
4
6
|
import { useTreeviewStore } from "@ogw_front/stores/treeview";
|
|
@@ -11,6 +13,56 @@ const hybridViewerStore = useHybridViewerStore();
|
|
|
11
13
|
|
|
12
14
|
const emit = defineEmits(["show-menu"]);
|
|
13
15
|
|
|
16
|
+
const search = ref("");
|
|
17
|
+
const sortType = ref("name");
|
|
18
|
+
const filterOptions = ref({});
|
|
19
|
+
|
|
20
|
+
const processedItems = computed(() =>
|
|
21
|
+
treeviewStore.items
|
|
22
|
+
.filter((category) => filterOptions.value[category.title] !== false)
|
|
23
|
+
.map((category) => {
|
|
24
|
+
const field = sortType.value === "name" ? "title" : "id";
|
|
25
|
+
const children = (category.children || []).toSorted((first, second) =>
|
|
26
|
+
first[field].localeCompare(second[field], undefined, {
|
|
27
|
+
numeric: true,
|
|
28
|
+
sensitivity: "base",
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
return { id: category.title, title: category.title, children };
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const availableFilterOptions = computed(() =>
|
|
36
|
+
treeviewStore.items.map((category) => category.title),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
watch(
|
|
40
|
+
availableFilterOptions,
|
|
41
|
+
(newOptions) => {
|
|
42
|
+
for (const option of newOptions) {
|
|
43
|
+
if (filterOptions.value[option] === undefined) {
|
|
44
|
+
filterOptions.value[option] = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{ immediate: true },
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
function toggleSort() {
|
|
52
|
+
sortType.value = sortType.value === "name" ? "id" : "name";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function customFilter(value, searchQuery, item) {
|
|
56
|
+
if (!searchQuery) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
const query = searchQuery.toLowerCase();
|
|
60
|
+
return (
|
|
61
|
+
item.raw.title.toLowerCase().includes(query) ||
|
|
62
|
+
(item.raw.id && item.raw.id.toLowerCase().includes(query))
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
14
66
|
function isLeafNode(item) {
|
|
15
67
|
return !item.children || item.children.length === 0;
|
|
16
68
|
}
|
|
@@ -24,8 +76,8 @@ watch(
|
|
|
24
76
|
}
|
|
25
77
|
const { added, removed } = compareSelections(current, previous);
|
|
26
78
|
const updates = [
|
|
27
|
-
...added.map((
|
|
28
|
-
...removed.map((
|
|
79
|
+
...added.map((id) => dataStyleStore.setVisibility(id, true)),
|
|
80
|
+
...removed.map((id) => dataStyleStore.setVisibility(id, false)),
|
|
29
81
|
];
|
|
30
82
|
await Promise.all(updates);
|
|
31
83
|
hybridViewerStore.remoteRender();
|
|
@@ -45,10 +97,47 @@ onMounted(() => {
|
|
|
45
97
|
</script>
|
|
46
98
|
|
|
47
99
|
<template>
|
|
100
|
+
<v-row v-if="treeviewStore.items.length > 0" dense align="center" class="mr-1 ml-3 mt-2 pa-1">
|
|
101
|
+
<v-col>
|
|
102
|
+
<SearchBar v-model="search" label="Search" color="black" base-color="black" />
|
|
103
|
+
</v-col>
|
|
104
|
+
<v-col cols="auto" class="d-flex align-center">
|
|
105
|
+
<ActionButton
|
|
106
|
+
:tooltip="'Sort by ' + (sortType === 'name' ? 'ID' : 'Name')"
|
|
107
|
+
:icon="
|
|
108
|
+
sortType === 'name' ? 'mdi-sort-alphabetical-ascending' : 'mdi-sort-numeric-ascending'
|
|
109
|
+
"
|
|
110
|
+
tooltipLocation="bottom"
|
|
111
|
+
@click="toggleSort"
|
|
112
|
+
/>
|
|
113
|
+
<v-menu :close-on-content-click="false">
|
|
114
|
+
<template #activator="{ props }">
|
|
115
|
+
<ActionButton
|
|
116
|
+
tooltip="Filter options"
|
|
117
|
+
icon="mdi-filter-variant"
|
|
118
|
+
tooltipLocation="bottom"
|
|
119
|
+
class="ml-1"
|
|
120
|
+
v-bind="props"
|
|
121
|
+
/>
|
|
122
|
+
</template>
|
|
123
|
+
<v-list class="mt-1">
|
|
124
|
+
<v-list-item v-for="category_id in availableFilterOptions" :key="category_id">
|
|
125
|
+
<v-checkbox
|
|
126
|
+
v-model="filterOptions[category_id]"
|
|
127
|
+
:label="category_id"
|
|
128
|
+
hide-details
|
|
129
|
+
density="compact"
|
|
130
|
+
/>
|
|
131
|
+
</v-list-item>
|
|
132
|
+
</v-list>
|
|
133
|
+
</v-menu>
|
|
134
|
+
</v-col>
|
|
135
|
+
</v-row>
|
|
48
136
|
<v-treeview
|
|
49
137
|
v-model:selected="treeviewStore.selection"
|
|
50
|
-
:items="
|
|
51
|
-
|
|
138
|
+
:items="processedItems"
|
|
139
|
+
:search="search"
|
|
140
|
+
:custom-filter="customFilter"
|
|
52
141
|
class="transparent-treeview"
|
|
53
142
|
item-value="id"
|
|
54
143
|
select-strategy="classic"
|
package/app/stores/data.js
CHANGED
|
@@ -35,7 +35,7 @@ export const useDataStore = defineStore("data", () => {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
async function formatedMeshComponents(modelId) {
|
|
38
|
-
const items = await database.model_components.where(
|
|
38
|
+
const items = await database.model_components.where("id").equals(modelId).toArray();
|
|
39
39
|
const componentTitles = {
|
|
40
40
|
Corner: "Corners",
|
|
41
41
|
Line: "Lines",
|
|
@@ -62,19 +62,26 @@ export const useDataStore = defineStore("data", () => {
|
|
|
62
62
|
id: meshComponent.geode_id,
|
|
63
63
|
title: meshComponent.name,
|
|
64
64
|
category: meshComponent.type,
|
|
65
|
+
is_active: meshComponent.is_active,
|
|
65
66
|
})),
|
|
66
67
|
}));
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function refFormatedMeshComponents(id) {
|
|
70
71
|
return useObservable(
|
|
71
|
-
liveQuery(() =>
|
|
72
|
+
liveQuery(() => {
|
|
73
|
+
const unwrapped_id = unref(id);
|
|
74
|
+
return formatedMeshComponents(unwrapped_id);
|
|
75
|
+
}),
|
|
72
76
|
{ initialValue: [] },
|
|
73
77
|
);
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
async function meshComponentType(modelId, geode_id) {
|
|
77
|
-
const component = await database.model_components
|
|
81
|
+
const component = await database.model_components
|
|
82
|
+
.where("[id+geode_id]")
|
|
83
|
+
.equals([modelId, geode_id])
|
|
84
|
+
.first();
|
|
78
85
|
return component?.type;
|
|
79
86
|
}
|
|
80
87
|
|
|
@@ -109,6 +116,7 @@ export const useDataStore = defineStore("data", () => {
|
|
|
109
116
|
type: component.type,
|
|
110
117
|
viewer_id: component.viewer_id,
|
|
111
118
|
name: component.name,
|
|
119
|
+
is_active: component.is_active,
|
|
112
120
|
});
|
|
113
121
|
}
|
|
114
122
|
}
|
|
@@ -155,13 +163,14 @@ export const useDataStore = defineStore("data", () => {
|
|
|
155
163
|
}
|
|
156
164
|
|
|
157
165
|
async function deleteModelComponents(modelId) {
|
|
158
|
-
await database.model_components.where(
|
|
159
|
-
await database.model_components_relation.where(
|
|
166
|
+
await database.model_components.where("id").equals(modelId).delete();
|
|
167
|
+
await database.model_components_relation.where("id").equals(modelId).delete();
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
async function getMeshComponentGeodeIds(modelId, component_type) {
|
|
163
171
|
const components = await database.model_components
|
|
164
|
-
.where(
|
|
172
|
+
.where("[id+type]")
|
|
173
|
+
.equals([modelId, component_type])
|
|
165
174
|
.toArray();
|
|
166
175
|
return components.map((component) => component.geode_id);
|
|
167
176
|
}
|
|
@@ -221,6 +230,7 @@ export const useDataStore = defineStore("data", () => {
|
|
|
221
230
|
getLinesGeodeIds,
|
|
222
231
|
getSurfacesGeodeIds,
|
|
223
232
|
getBlocksGeodeIds,
|
|
233
|
+
getMeshComponentGeodeIds,
|
|
224
234
|
getMeshComponentsViewerIds,
|
|
225
235
|
|
|
226
236
|
exportStores,
|
package/app/stores/data_style.js
CHANGED
|
@@ -85,6 +85,7 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
|
|
|
85
85
|
selectedObjects: dataStyleState.selectedObjects,
|
|
86
86
|
...meshStyleStore,
|
|
87
87
|
...modelStyleStore,
|
|
88
|
+
setComponentsVisibility: modelStyleStore.setModelComponentsVisibility,
|
|
88
89
|
addDataStyle,
|
|
89
90
|
applyDefaultStyle,
|
|
90
91
|
setVisibility,
|
package/app/stores/treeview.js
CHANGED
|
@@ -25,7 +25,7 @@ export const useTreeviewStore = defineStore("treeview", () => {
|
|
|
25
25
|
sensitivity: "base",
|
|
26
26
|
}),
|
|
27
27
|
);
|
|
28
|
-
selection.value.push(
|
|
28
|
+
selection.value.push(id);
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -36,7 +36,7 @@ export const useTreeviewStore = defineStore("treeview", () => {
|
|
|
36
36
|
sensitivity: "base",
|
|
37
37
|
}),
|
|
38
38
|
);
|
|
39
|
-
selection.value.push(
|
|
39
|
+
selection.value.push(id);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function displayAdditionalTree(id) {
|
|
@@ -14,6 +14,10 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
|
|
|
14
14
|
|
|
15
15
|
const model_schemas = viewer_schemas.opengeodeweb_viewer.model;
|
|
16
16
|
|
|
17
|
+
const MESH_CONFIG = [{ type: "Corner" }, { type: "Line" }, { type: "Surface" }, { type: "Block" }];
|
|
18
|
+
|
|
19
|
+
const MESH_TYPES = MESH_CONFIG.map((config) => config.type);
|
|
20
|
+
|
|
17
21
|
export function useModelStyle() {
|
|
18
22
|
const dataStore = useDataStore();
|
|
19
23
|
const dataStyleStateStore = useDataStyleStateStore();
|
|
@@ -26,19 +30,54 @@ export function useModelStyle() {
|
|
|
26
30
|
const hybridViewerStore = useHybridViewerStore();
|
|
27
31
|
const viewerStore = useViewerStore();
|
|
28
32
|
|
|
29
|
-
function
|
|
30
|
-
|
|
33
|
+
function buildSelection(modelId, components, stylesMap) {
|
|
34
|
+
const componentsByType = Object.fromEntries(MESH_TYPES.map((type) => [type, []]));
|
|
35
|
+
for (const component of components) {
|
|
36
|
+
if (componentsByType[component.type]) {
|
|
37
|
+
componentsByType[component.type].push(component);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const groupStyles = dataStyleStateStore.getStyle(modelId);
|
|
42
|
+
const selection = [];
|
|
43
|
+
for (const type of MESH_TYPES) {
|
|
44
|
+
const typeComponents = componentsByType[type];
|
|
45
|
+
if (typeComponents.length === 0) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const typeKey = `${type.toLowerCase()}s`;
|
|
50
|
+
const defaultVisibility = groupStyles[typeKey].visibility;
|
|
51
|
+
|
|
52
|
+
let allVisible = true;
|
|
53
|
+
for (const component of typeComponents) {
|
|
54
|
+
const isVisible = stylesMap[component.geode_id]?.visibility ?? defaultVisibility;
|
|
55
|
+
if (isVisible) {
|
|
56
|
+
selection.push(component.geode_id);
|
|
57
|
+
} else {
|
|
58
|
+
allVisible = false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (allVisible) {
|
|
62
|
+
selection.push(type);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return selection;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function modelVisibility(modelId) {
|
|
69
|
+
return dataStyleStateStore.getStyle(modelId).visibility;
|
|
31
70
|
}
|
|
32
71
|
|
|
33
|
-
function setModelVisibility(
|
|
72
|
+
function setModelVisibility(modelId, visibility) {
|
|
34
73
|
return viewerStore.request(
|
|
35
74
|
model_schemas.visibility,
|
|
36
|
-
{ id, visibility },
|
|
75
|
+
{ id: modelId, visibility },
|
|
37
76
|
{
|
|
38
77
|
response_function: async () => {
|
|
39
|
-
await hybridViewerStore.setVisibility(
|
|
40
|
-
await dataStyleStateStore.mutateStyle(
|
|
41
|
-
return { id, visibility };
|
|
78
|
+
await hybridViewerStore.setVisibility(modelId, visibility);
|
|
79
|
+
await dataStyleStateStore.mutateStyle(modelId, { visibility });
|
|
80
|
+
return { id: modelId, visibility };
|
|
42
81
|
},
|
|
43
82
|
},
|
|
44
83
|
);
|
|
@@ -47,60 +86,34 @@ export function useModelStyle() {
|
|
|
47
86
|
function visibleMeshComponents(id_ref) {
|
|
48
87
|
const selection = ref([]);
|
|
49
88
|
watch(
|
|
50
|
-
() => (
|
|
51
|
-
(
|
|
52
|
-
if (!
|
|
89
|
+
() => unref(id_ref),
|
|
90
|
+
(modelId, _prev, onCleanup) => {
|
|
91
|
+
if (!modelId) {
|
|
92
|
+
selection.value = [];
|
|
53
93
|
return;
|
|
54
94
|
}
|
|
55
95
|
const observable = liveQuery(async () => {
|
|
56
|
-
const
|
|
57
|
-
|
|
96
|
+
const allComponents = await database.model_components
|
|
97
|
+
.where("id")
|
|
98
|
+
.equals(modelId)
|
|
99
|
+
.toArray();
|
|
100
|
+
if (allComponents.length === 0) {
|
|
58
101
|
return [];
|
|
59
102
|
}
|
|
60
|
-
|
|
61
|
-
const all_styles = await database.model_component_datastyle
|
|
103
|
+
const componentStyles = await database.model_component_datastyle
|
|
62
104
|
.where("id_model")
|
|
63
|
-
.equals(
|
|
105
|
+
.equals(modelId)
|
|
64
106
|
.toArray();
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const current_selection = [];
|
|
71
|
-
const meshTypes = new Set(["Corner", "Line", "Surface", "Block"]);
|
|
72
|
-
const componentsByType = {};
|
|
73
|
-
for (const component of components) {
|
|
74
|
-
if (meshTypes.has(component.type)) {
|
|
75
|
-
if (!componentsByType[component.type]) {
|
|
76
|
-
componentsByType[component.type] = [];
|
|
77
|
-
}
|
|
78
|
-
componentsByType[component.type].push(component.geode_id);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
for (const [type, geode_ids] of Object.entries(componentsByType)) {
|
|
83
|
-
let all_visible = true;
|
|
84
|
-
for (const gid of geode_ids) {
|
|
85
|
-
const is_visible = styles[gid] === undefined ? true : styles[gid].visibility;
|
|
86
|
-
if (is_visible) {
|
|
87
|
-
current_selection.push(gid);
|
|
88
|
-
} else {
|
|
89
|
-
all_visible = false;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (all_visible) {
|
|
93
|
-
current_selection.push(type);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return current_selection;
|
|
107
|
+
const stylesMap = Object.fromEntries(
|
|
108
|
+
componentStyles.map((style) => [style.id_component, style]),
|
|
109
|
+
);
|
|
110
|
+
return buildSelection(modelId, allComponents, stylesMap);
|
|
97
111
|
});
|
|
98
112
|
|
|
99
113
|
const subscription = observable.subscribe({
|
|
100
114
|
next: (val) => {
|
|
101
115
|
selection.value = val;
|
|
102
116
|
},
|
|
103
|
-
error: (err) => console.error(err),
|
|
104
117
|
});
|
|
105
118
|
onCleanup(() => subscription.unsubscribe());
|
|
106
119
|
},
|
|
@@ -109,41 +122,69 @@ export function useModelStyle() {
|
|
|
109
122
|
return selection;
|
|
110
123
|
}
|
|
111
124
|
|
|
112
|
-
function
|
|
113
|
-
const
|
|
125
|
+
async function setModelComponentsVisibility(modelId, componentIds, visibility) {
|
|
126
|
+
const allComponents = await database.model_components.where("id").equals(modelId).toArray();
|
|
127
|
+
const componentsMap = Object.fromEntries(
|
|
128
|
+
allComponents.map((component) => [component.geode_id, component]),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
return Promise.all(
|
|
132
|
+
MESH_TYPES.map(async (type) => {
|
|
133
|
+
const typeComponents = allComponents.filter((component) => component.type === type);
|
|
134
|
+
const isSelectedType = componentIds.includes(type);
|
|
135
|
+
const idsToUpdate = isSelectedType
|
|
136
|
+
? typeComponents.map((component) => component.geode_id)
|
|
137
|
+
: componentIds.filter((id) => componentsMap[id]?.type === type);
|
|
138
|
+
|
|
139
|
+
if (idsToUpdate.length === 0) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const viewerIds = await dataStore.getMeshComponentsViewerIds(modelId, idsToUpdate);
|
|
144
|
+
if (viewerIds.length > 0) {
|
|
145
|
+
const schema = model_schemas[`${type.toLowerCase()}s`].visibility;
|
|
146
|
+
await viewerStore.request(schema, { id: modelId, block_ids: viewerIds, visibility });
|
|
147
|
+
}
|
|
148
|
+
return dataStyleStateStore.mutateComponentStyles(modelId, idsToUpdate, { visibility });
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function applyModelStyle(modelId) {
|
|
154
|
+
const style = dataStyleStateStore.getStyle(modelId);
|
|
114
155
|
const handlers = {
|
|
115
|
-
visibility: () => setModelVisibility(
|
|
116
|
-
corners: () => modelCornersStyleStore.applyModelCornersStyle(
|
|
117
|
-
lines: () => modelLinesStyleStore.applyModelLinesStyle(
|
|
118
|
-
surfaces: () => modelSurfacesStyleStore.applyModelSurfacesStyle(
|
|
119
|
-
blocks: () => modelBlocksStyleStore.applyModelBlocksStyle(
|
|
120
|
-
points: () => modelPointsStyleStore.applyModelPointsStyle(
|
|
121
|
-
edges: () => modelEdgesStyleStore.applyModelEdgesStyle(
|
|
156
|
+
visibility: () => setModelVisibility(modelId, style.visibility),
|
|
157
|
+
corners: () => modelCornersStyleStore.applyModelCornersStyle(modelId),
|
|
158
|
+
lines: () => modelLinesStyleStore.applyModelLinesStyle(modelId),
|
|
159
|
+
surfaces: () => modelSurfacesStyleStore.applyModelSurfacesStyle(modelId),
|
|
160
|
+
blocks: () => modelBlocksStyleStore.applyModelBlocksStyle(modelId),
|
|
161
|
+
points: () => modelPointsStyleStore.applyModelPointsStyle(modelId),
|
|
162
|
+
edges: () => modelEdgesStyleStore.applyModelEdgesStyle(modelId),
|
|
122
163
|
};
|
|
123
164
|
|
|
124
|
-
|
|
125
|
-
.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
165
|
+
return Promise.all(
|
|
166
|
+
Object.keys(style)
|
|
167
|
+
.filter((key) => handlers[key])
|
|
168
|
+
.map((key) => handlers[key]()),
|
|
169
|
+
);
|
|
129
170
|
}
|
|
130
171
|
|
|
131
|
-
function setModelMeshComponentsDefaultStyle(
|
|
132
|
-
return dataStore.item(
|
|
172
|
+
function setModelMeshComponentsDefaultStyle(modelId) {
|
|
173
|
+
return dataStore.item(modelId).then((item) => {
|
|
133
174
|
if (!item) {
|
|
134
175
|
return [];
|
|
135
176
|
}
|
|
136
177
|
const { mesh_components } = item;
|
|
137
178
|
const handlers = {
|
|
138
|
-
Corner: () => modelCornersStyleStore.setModelCornersDefaultStyle(
|
|
139
|
-
Line: () => modelLinesStyleStore.setModelLinesDefaultStyle(
|
|
140
|
-
Surface: () => modelSurfacesStyleStore.setModelSurfacesDefaultStyle(
|
|
141
|
-
Block: () => modelBlocksStyleStore.setModelBlocksDefaultStyle(
|
|
179
|
+
Corner: () => modelCornersStyleStore.setModelCornersDefaultStyle(modelId),
|
|
180
|
+
Line: () => modelLinesStyleStore.setModelLinesDefaultStyle(modelId),
|
|
181
|
+
Surface: () => modelSurfacesStyleStore.setModelSurfacesDefaultStyle(modelId),
|
|
182
|
+
Block: () => modelBlocksStyleStore.setModelBlocksDefaultStyle(modelId),
|
|
142
183
|
};
|
|
143
184
|
return Promise.all(
|
|
144
185
|
Object.keys(mesh_components)
|
|
145
|
-
.filter((
|
|
146
|
-
.map((
|
|
186
|
+
.filter((key) => handlers[key])
|
|
187
|
+
.map((key) => handlers[key]()),
|
|
147
188
|
);
|
|
148
189
|
});
|
|
149
190
|
}
|
|
@@ -152,6 +193,7 @@ export function useModelStyle() {
|
|
|
152
193
|
modelVisibility,
|
|
153
194
|
visibleMeshComponents,
|
|
154
195
|
setModelVisibility,
|
|
196
|
+
setModelComponentsVisibility,
|
|
155
197
|
applyModelStyle,
|
|
156
198
|
setModelMeshComponentsDefaultStyle,
|
|
157
199
|
...useModelBlocksStyle(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geode/opengeodeweb-front",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.9.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": {
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"build": ""
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@geode/opengeodeweb-back": "
|
|
38
|
-
"@geode/opengeodeweb-viewer": "
|
|
37
|
+
"@geode/opengeodeweb-back": "next",
|
|
38
|
+
"@geode/opengeodeweb-viewer": "next",
|
|
39
39
|
"@kitware/vtk.js": "33.3.0",
|
|
40
40
|
"@mdi/font": "7.4.47",
|
|
41
41
|
"@pinia/nuxt": "0.11.3",
|