@geode/opengeodeweb-front 10.14.1 → 10.14.2-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/.oxlintrc.json +12 -2
- package/app/components/SearchBar.vue +1 -1
- package/app/components/Viewer/ContextMenu.vue +1 -3
- package/app/components/Viewer/ContextMenuItem.vue +2 -2
- package/app/components/Viewer/ObjectTree/Base/CommonTreeView.vue +189 -0
- package/app/components/Viewer/ObjectTree/Base/Controls.vue +124 -38
- package/app/components/Viewer/ObjectTree/Base/ItemLabel.vue +43 -18
- package/app/components/Viewer/ObjectTree/Base/StickyHeader.vue +46 -0
- package/app/components/Viewer/ObjectTree/Base/TreeRow.vue +77 -0
- package/app/components/Viewer/ObjectTree/Box.vue +106 -15
- package/app/components/Viewer/ObjectTree/Layout.vue +14 -12
- package/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue +48 -33
- package/app/components/Viewer/ObjectTree/Views/ModelComponents.vue +102 -66
- package/app/composables/hover_highlight.js +85 -0
- package/app/composables/model_components.js +68 -0
- package/app/composables/{use_tree_filter.js → tree_filter.js} +49 -31
- package/app/composables/tree_keyboard_nav.js +81 -0
- package/app/composables/tree_scroll.js +91 -0
- package/app/composables/virtual_tree.js +164 -0
- package/app/stores/data.js +41 -1
- package/app/stores/hybrid_viewer.js +30 -38
- package/app/utils/hybrid_viewer.js +101 -0
- package/package.json +3 -3
- package/app/composables/use_hover_highlight.js +0 -48
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
2
|
+
import vtk_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
3
|
+
|
|
4
|
+
const HOVER_DELAY = 200;
|
|
5
|
+
|
|
6
|
+
export function useHoverhighlight() {
|
|
7
|
+
const viewerStore = useViewerStore();
|
|
8
|
+
let timer = undefined;
|
|
9
|
+
let currentId = undefined;
|
|
10
|
+
let currentType = undefined;
|
|
11
|
+
|
|
12
|
+
function onHoverEnter(id, block_ids_provider = [], type = "model", immediate = false) {
|
|
13
|
+
if (timer) {
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
timer = undefined;
|
|
16
|
+
}
|
|
17
|
+
const schema = vtk_schemas.opengeodeweb_viewer[type].highlight;
|
|
18
|
+
|
|
19
|
+
async function highlightAction() {
|
|
20
|
+
currentId = id;
|
|
21
|
+
currentType = type;
|
|
22
|
+
|
|
23
|
+
let block_ids = [];
|
|
24
|
+
if (typeof block_ids_provider === "function") {
|
|
25
|
+
block_ids = await block_ids_provider();
|
|
26
|
+
} else {
|
|
27
|
+
block_ids = block_ids_provider;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
block_ids = (Array.isArray(block_ids) ? block_ids : [])
|
|
31
|
+
.map((blockId) => Number.parseInt(blockId, 10))
|
|
32
|
+
.filter((blockId) => !Number.isNaN(blockId));
|
|
33
|
+
|
|
34
|
+
if (currentId !== id) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const params = {
|
|
39
|
+
id,
|
|
40
|
+
visibility: true,
|
|
41
|
+
...(type === "model" && { block_ids }),
|
|
42
|
+
};
|
|
43
|
+
try {
|
|
44
|
+
await viewerStore.request(schema, params);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`Highlight failed for ${type} ${id}:`, error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (immediate) {
|
|
51
|
+
highlightAction();
|
|
52
|
+
} else {
|
|
53
|
+
timer = setTimeout(highlightAction, HOVER_DELAY);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function onHoverLeave(id) {
|
|
58
|
+
if (timer) {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
timer = undefined;
|
|
61
|
+
}
|
|
62
|
+
if (currentId === id) {
|
|
63
|
+
const schema = vtk_schemas.opengeodeweb_viewer[currentType].highlight;
|
|
64
|
+
const params = {
|
|
65
|
+
id,
|
|
66
|
+
visibility: false,
|
|
67
|
+
...(currentType === "model" && { block_ids: [] }),
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
viewerStore.request(schema, params);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`Unhighlight failed for ${currentType} ${id}:`, error);
|
|
73
|
+
}
|
|
74
|
+
currentId = undefined;
|
|
75
|
+
currentType = undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!currentId) {
|
|
79
|
+
currentId = undefined;
|
|
80
|
+
currentType = undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { onHoverEnter, onHoverLeave };
|
|
85
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { compareSelections } from "@ogw_front/utils/treeview";
|
|
2
|
+
import { useDataStore } from "@ogw_front/stores/data";
|
|
3
|
+
import { useDataStyleStore } from "@ogw_front/stores/data_style";
|
|
4
|
+
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
|
|
5
|
+
|
|
6
|
+
export function useModelComponents(viewId) {
|
|
7
|
+
const dataStore = useDataStore();
|
|
8
|
+
const dataStyleStore = useDataStyleStore();
|
|
9
|
+
const hybridViewerStore = useHybridViewerStore();
|
|
10
|
+
|
|
11
|
+
const items = dataStore.refFormatedMeshComponents(viewId);
|
|
12
|
+
const componentsCache = ref(undefined);
|
|
13
|
+
const localCategories = ref([]);
|
|
14
|
+
|
|
15
|
+
onMounted(async () => {
|
|
16
|
+
const data = await dataStore.fetchAllMeshComponents(viewId);
|
|
17
|
+
componentsCache.value = markRaw(data);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
watch(
|
|
21
|
+
items,
|
|
22
|
+
(newItems) => {
|
|
23
|
+
if (!newItems) {
|
|
24
|
+
localCategories.value = [];
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
localCategories.value = newItems.map((newCategory) => {
|
|
28
|
+
const existing = localCategories.value.find((category) => category.id === newCategory.id);
|
|
29
|
+
if (existing) {
|
|
30
|
+
existing.title = newCategory.title || newCategory.id;
|
|
31
|
+
return existing;
|
|
32
|
+
}
|
|
33
|
+
return reactive({
|
|
34
|
+
...newCategory,
|
|
35
|
+
title: newCategory.title || newCategory.id,
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
{ immediate: true },
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const selection = dataStyleStore.visibleMeshComponents(viewId);
|
|
43
|
+
|
|
44
|
+
async function updateVisibility(current) {
|
|
45
|
+
const previous = selection.value;
|
|
46
|
+
const { added, removed } = compareSelections(current, previous);
|
|
47
|
+
|
|
48
|
+
if (added.length === 0 && removed.length === 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (added.length > 0) {
|
|
53
|
+
await dataStyleStore.setModelComponentsVisibility(viewId, added, true);
|
|
54
|
+
}
|
|
55
|
+
if (removed.length > 0) {
|
|
56
|
+
await dataStyleStore.setModelComponentsVisibility(viewId, removed, false);
|
|
57
|
+
}
|
|
58
|
+
hybridViewerStore.remoteRender();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
items,
|
|
63
|
+
componentsCache,
|
|
64
|
+
localCategories,
|
|
65
|
+
selection,
|
|
66
|
+
updateVisibility,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -2,30 +2,45 @@ function customFilter(value, searchQuery, item) {
|
|
|
2
2
|
if (!searchQuery) {
|
|
3
3
|
return true;
|
|
4
4
|
}
|
|
5
|
+
if (!item || !item.raw) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
5
8
|
const query = searchQuery.toLowerCase();
|
|
6
9
|
const { title = "", id = value } = item.raw || {};
|
|
7
10
|
return [title, id].some((field) => String(field).toLowerCase().includes(query));
|
|
8
11
|
}
|
|
9
12
|
|
|
10
|
-
function sortAndFormatItems(
|
|
13
|
+
function sortAndFormatItems(itemList, sortType, options = {}) {
|
|
14
|
+
if (!itemList || !Array.isArray(itemList)) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
11
17
|
const field = sortType === "name" ? "title" : "id";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const localeOptions = { numeric: true, sensitivity: "base" };
|
|
19
|
+
|
|
20
|
+
const sorted = itemList
|
|
21
|
+
.filter((item) => item !== null && item !== undefined)
|
|
22
|
+
.toSorted((itemA, itemB) => {
|
|
23
|
+
const fieldA = String(itemA[field] || itemA.id || "");
|
|
24
|
+
const fieldB = String(itemB[field] || itemB.id || "");
|
|
25
|
+
return fieldA.localeCompare(fieldB, undefined, localeOptions);
|
|
20
26
|
});
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
|
|
28
|
+
if (options.recursiveSort) {
|
|
29
|
+
return sorted.map((item) => {
|
|
30
|
+
if (item.children && item.children.length > 0) {
|
|
31
|
+
return {
|
|
32
|
+
...item,
|
|
33
|
+
children: sortAndFormatItems(item.children, sortType, options),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return item;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return sorted;
|
|
26
40
|
}
|
|
27
41
|
|
|
28
|
-
function useTreeFilter(
|
|
42
|
+
function useTreeFilter(itemsIn, options = {}) {
|
|
43
|
+
const rawItems = typeof itemsIn === "function" ? computed(itemsIn) : toRef(itemsIn);
|
|
29
44
|
const search = ref("");
|
|
30
45
|
const sortType = ref(options.defaultSort || "name");
|
|
31
46
|
const filterOptions = ref(options.defaultFilters || {});
|
|
@@ -53,24 +68,27 @@ function useTreeFilter(rawItems, options = {}) {
|
|
|
53
68
|
if (!rawItems.value) {
|
|
54
69
|
return [];
|
|
55
70
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
const filteredByCategory = rawItems.value.filter((category) => {
|
|
72
|
+
const key = category.title || category.id;
|
|
73
|
+
return filterOptions.value[key] !== false;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const sorted = sortAndFormatItems(filteredByCategory, sortType.value, options);
|
|
77
|
+
|
|
63
78
|
if (!search.value) {
|
|
64
79
|
return sorted;
|
|
65
80
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
})
|
|
73
|
-
|
|
81
|
+
|
|
82
|
+
const result = [];
|
|
83
|
+
for (const category of sorted) {
|
|
84
|
+
const children = (category.children || []).filter((child) =>
|
|
85
|
+
customFilter(child.id, search.value, { raw: child }),
|
|
86
|
+
);
|
|
87
|
+
if (children.length > 0 || customFilter(category.id, search.value, { raw: category })) {
|
|
88
|
+
result.push({ ...category, children });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
74
92
|
});
|
|
75
93
|
|
|
76
94
|
function toggleSort() {
|
|
@@ -117,4 +135,4 @@ function useTreeFilter(rawItems, options = {}) {
|
|
|
117
135
|
};
|
|
118
136
|
}
|
|
119
137
|
|
|
120
|
-
export { customFilter, useTreeFilter };
|
|
138
|
+
export { customFilter, useTreeFilter, sortAndFormatItems };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export function useTreeKeyboardNav(displayItems, emit, scrollToIndex, toggleOpen, handleItemClick) {
|
|
2
|
+
const focusedIndex = ref(-1);
|
|
3
|
+
|
|
4
|
+
function findParentIndex(item, currentIndex) {
|
|
5
|
+
for (let index = currentIndex - 1; index >= 0; index -= 1) {
|
|
6
|
+
if (displayItems.value[index].depth < item.depth) {
|
|
7
|
+
return index;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return -1;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getNextIndex(key, item) {
|
|
14
|
+
const lastIndex = displayItems.value.length - 1;
|
|
15
|
+
const currentIndex = focusedIndex.value;
|
|
16
|
+
|
|
17
|
+
if (key === "ArrowDown") {
|
|
18
|
+
return Math.min(currentIndex + 1, lastIndex);
|
|
19
|
+
}
|
|
20
|
+
if (key === "ArrowUp") {
|
|
21
|
+
return Math.max(currentIndex - 1, 0);
|
|
22
|
+
}
|
|
23
|
+
if (key === "Home") {
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
if (key === "End") {
|
|
27
|
+
return lastIndex;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (key === "ArrowRight") {
|
|
31
|
+
if (!item.isLeaf && !item.isOpen) {
|
|
32
|
+
toggleOpen(item.raw);
|
|
33
|
+
return currentIndex;
|
|
34
|
+
}
|
|
35
|
+
return Math.min(currentIndex + 1, lastIndex);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (key === "ArrowLeft") {
|
|
39
|
+
if (!item.isLeaf && item.isOpen) {
|
|
40
|
+
toggleOpen(item.raw);
|
|
41
|
+
return currentIndex;
|
|
42
|
+
}
|
|
43
|
+
const parentIndex = findParentIndex(item, currentIndex);
|
|
44
|
+
return parentIndex === -1 ? currentIndex : parentIndex;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return currentIndex;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function handleKeyDown(event) {
|
|
51
|
+
if (displayItems.value.length === 0) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const prevIndex = focusedIndex.value;
|
|
56
|
+
const item = displayItems.value[focusedIndex.value];
|
|
57
|
+
|
|
58
|
+
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
focusedIndex.value = getNextIndex(event.key, item);
|
|
61
|
+
} else if (event.key === "Enter" || event.key === " ") {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
if (focusedIndex.value !== -1) {
|
|
64
|
+
handleItemClick(item);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (focusedIndex.value !== prevIndex) {
|
|
69
|
+
if (prevIndex !== -1) {
|
|
70
|
+
emit("hover:leave", { item: displayItems.value[prevIndex] });
|
|
71
|
+
}
|
|
72
|
+
emit("hover:enter", {
|
|
73
|
+
item: displayItems.value[focusedIndex.value],
|
|
74
|
+
immediate: true,
|
|
75
|
+
});
|
|
76
|
+
scrollToIndex(focusedIndex.value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { focusedIndex, handleKeyDown };
|
|
81
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export function useTreeScroll(propsIn, emit, displayItems, actualItemProps) {
|
|
2
|
+
const SCROLL_STICKY_THRESHOLD = 10;
|
|
3
|
+
const DEFAULT_ITEM_HEIGHT = 44;
|
|
4
|
+
|
|
5
|
+
const props = toRef(propsIn);
|
|
6
|
+
const internalScrollTop = ref(props.value.scrollTop || 0);
|
|
7
|
+
const virtualScrollRef = ref(undefined);
|
|
8
|
+
|
|
9
|
+
function handleScroll(event) {
|
|
10
|
+
internalScrollTop.value = event.target.scrollTop;
|
|
11
|
+
emit("update:scrollTop", event.target.scrollTop);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
watch(
|
|
15
|
+
() => props.value.scrollTop,
|
|
16
|
+
(newVal) => {
|
|
17
|
+
if (Math.abs(newVal - internalScrollTop.value) > 1) {
|
|
18
|
+
internalScrollTop.value = newVal;
|
|
19
|
+
if (virtualScrollRef.value && virtualScrollRef.value.$el) {
|
|
20
|
+
virtualScrollRef.value.$el.scrollTop = newVal;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const stickyHeader = computed(() => {
|
|
27
|
+
if (internalScrollTop.value <= SCROLL_STICKY_THRESHOLD) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const itemHeight = actualItemProps.value.height || DEFAULT_ITEM_HEIGHT;
|
|
32
|
+
const firstVisibleIndex = Math.floor(internalScrollTop.value / itemHeight);
|
|
33
|
+
|
|
34
|
+
if (firstVisibleIndex < 0 || firstVisibleIndex >= displayItems.value.length) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const firstVisibleItem = displayItems.value[firstVisibleIndex];
|
|
39
|
+
if (!firstVisibleItem) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let current = firstVisibleIndex;
|
|
44
|
+
const firstVisibleDepth = firstVisibleItem.depth;
|
|
45
|
+
|
|
46
|
+
while (current >= 0) {
|
|
47
|
+
const item = displayItems.value[current];
|
|
48
|
+
if (item && !item.isLeaf && item.depth < firstVisibleDepth) {
|
|
49
|
+
return item;
|
|
50
|
+
}
|
|
51
|
+
if (item && item.depth === 0 && !item.isLeaf && current < firstVisibleIndex) {
|
|
52
|
+
return item;
|
|
53
|
+
}
|
|
54
|
+
current -= 1;
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
function scrollToIndex(index) {
|
|
60
|
+
if (index === -1 || !virtualScrollRef.value) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const container = virtualScrollRef.value.$el;
|
|
65
|
+
if (!container) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const itemHeight = actualItemProps.value.height;
|
|
70
|
+
const itemTop = index * itemHeight;
|
|
71
|
+
const itemBottom = itemTop + itemHeight;
|
|
72
|
+
|
|
73
|
+
const currentScrollTop = container.scrollTop;
|
|
74
|
+
const containerHeight = container.clientHeight;
|
|
75
|
+
const scrollBottom = currentScrollTop + containerHeight;
|
|
76
|
+
|
|
77
|
+
if (itemTop < currentScrollTop) {
|
|
78
|
+
container.scrollTop = itemTop;
|
|
79
|
+
} else if (itemBottom > scrollBottom) {
|
|
80
|
+
container.scrollTop = itemBottom - containerHeight;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
internalScrollTop,
|
|
86
|
+
virtualScrollRef,
|
|
87
|
+
stickyHeader,
|
|
88
|
+
handleScroll,
|
|
89
|
+
scrollToIndex,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export function useVirtualTree(propsIn, emit) {
|
|
2
|
+
const props = toRef(propsIn);
|
|
3
|
+
|
|
4
|
+
const actualItemProps = computed(() => ({
|
|
5
|
+
value: "id",
|
|
6
|
+
title: "title",
|
|
7
|
+
children: "children",
|
|
8
|
+
height: 44,
|
|
9
|
+
...props.value.itemProps,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
const actualSelection = computed(() => ({
|
|
13
|
+
selectable: false,
|
|
14
|
+
strategy: "classic",
|
|
15
|
+
...props.value.selection,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const openedSet = computed(() => new Set(props.value.opened));
|
|
19
|
+
const selectedSet = computed(() => new Set(props.value.selected));
|
|
20
|
+
|
|
21
|
+
function toggleOpen(item) {
|
|
22
|
+
const id = item[actualItemProps.value.value];
|
|
23
|
+
const { opened: openedArray = [] } = props.value;
|
|
24
|
+
const newOpened = new Set(openedArray);
|
|
25
|
+
if (newOpened.has(id)) {
|
|
26
|
+
newOpened.delete(id);
|
|
27
|
+
} else {
|
|
28
|
+
newOpened.add(id);
|
|
29
|
+
}
|
|
30
|
+
emit("update:opened", [...newOpened]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getAllChildrenIds(item, ids = []) {
|
|
34
|
+
const children = item[actualItemProps.value.children];
|
|
35
|
+
if (children) {
|
|
36
|
+
for (const child of children) {
|
|
37
|
+
ids.push(child[actualItemProps.value.value]);
|
|
38
|
+
getAllChildrenIds(child, ids);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return ids;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isSelected(item) {
|
|
45
|
+
const id = item[actualItemProps.value.value];
|
|
46
|
+
if (selectedSet.value.has(id)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (actualSelection.value.strategy === "classic") {
|
|
50
|
+
const childrenIds = getAllChildrenIds(item);
|
|
51
|
+
return (
|
|
52
|
+
childrenIds.length > 0 && childrenIds.every((childId) => selectedSet.value.has(childId))
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getIndeterminate(item) {
|
|
59
|
+
if (actualSelection.value.strategy !== "classic") {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const childrenIds = getAllChildrenIds(item);
|
|
63
|
+
if (childrenIds.length === 0) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const selectedChildren = childrenIds.filter((childId) => selectedSet.value.has(childId));
|
|
68
|
+
return selectedChildren.length > 0 && selectedChildren.length < childrenIds.length;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function toggleSelect(item) {
|
|
72
|
+
const id = item[actualItemProps.value.value];
|
|
73
|
+
const { selected: selectedArray = [] } = props.value;
|
|
74
|
+
const newSelected = new Set(selectedArray);
|
|
75
|
+
const isCurrentlySelected = newSelected.has(id) || isSelected(item);
|
|
76
|
+
|
|
77
|
+
if (actualSelection.value.strategy === "classic") {
|
|
78
|
+
const childrenIds = getAllChildrenIds(item);
|
|
79
|
+
if (isCurrentlySelected) {
|
|
80
|
+
newSelected.delete(id);
|
|
81
|
+
for (const childId of childrenIds) {
|
|
82
|
+
newSelected.delete(childId);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
newSelected.add(id);
|
|
86
|
+
for (const childId of childrenIds) {
|
|
87
|
+
newSelected.add(childId);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} else if (isCurrentlySelected) {
|
|
91
|
+
newSelected.delete(id);
|
|
92
|
+
} else {
|
|
93
|
+
newSelected.add(id);
|
|
94
|
+
}
|
|
95
|
+
emit("update:selected", [...newSelected]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function flattenTree(itemsList, depth = 0, result = []) {
|
|
99
|
+
const { search, customFilter } = props.value;
|
|
100
|
+
const lowerSearch = search ? search.toLowerCase() : "";
|
|
101
|
+
|
|
102
|
+
for (const item of itemsList) {
|
|
103
|
+
const id = item[actualItemProps.value.value];
|
|
104
|
+
const children = item[actualItemProps.value.children];
|
|
105
|
+
const hasChildren = children && children.length > 0;
|
|
106
|
+
|
|
107
|
+
const isOpen = openedSet.value.has(id);
|
|
108
|
+
|
|
109
|
+
if (lowerSearch) {
|
|
110
|
+
const matches = customFilter
|
|
111
|
+
? customFilter(id, search, { raw: item })
|
|
112
|
+
: (item[actualItemProps.value.title] || "").toLowerCase().includes(lowerSearch) ||
|
|
113
|
+
String(id).toLowerCase().includes(lowerSearch);
|
|
114
|
+
|
|
115
|
+
if (!hasChildren && !matches) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (hasChildren) {
|
|
120
|
+
const subtree = [];
|
|
121
|
+
flattenTree(children, depth + 1, subtree);
|
|
122
|
+
if (subtree.length === 0 && !matches) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
result.push({
|
|
127
|
+
raw: item,
|
|
128
|
+
id,
|
|
129
|
+
depth,
|
|
130
|
+
isOpen: true,
|
|
131
|
+
isLeaf: false,
|
|
132
|
+
});
|
|
133
|
+
result.push(...subtree);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
result.push({
|
|
139
|
+
raw: item,
|
|
140
|
+
id,
|
|
141
|
+
depth,
|
|
142
|
+
isOpen,
|
|
143
|
+
isLeaf: !hasChildren,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (isOpen && hasChildren) {
|
|
147
|
+
flattenTree(children, depth + 1, result);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const displayItems = computed(() => flattenTree(props.value.items || []));
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
actualItemProps,
|
|
157
|
+
actualSelection,
|
|
158
|
+
displayItems,
|
|
159
|
+
toggleOpen,
|
|
160
|
+
toggleSelect,
|
|
161
|
+
isSelected,
|
|
162
|
+
getIndeterminate,
|
|
163
|
+
};
|
|
164
|
+
}
|
package/app/stores/data.js
CHANGED
|
@@ -63,12 +63,49 @@ export const useDataStore = defineStore("data", () => {
|
|
|
63
63
|
id: meshComponent.geode_id,
|
|
64
64
|
title: meshComponent.name,
|
|
65
65
|
category: meshComponent.type,
|
|
66
|
-
viewer_id: meshComponent.viewer_id,
|
|
66
|
+
viewer_id: Number(meshComponent.viewer_id),
|
|
67
67
|
is_active: meshComponent.is_active,
|
|
68
68
|
})),
|
|
69
69
|
}));
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
async function getMeshComponentsByType(modelId, type) {
|
|
73
|
+
const components = await database.model_components
|
|
74
|
+
.where("[id+type]")
|
|
75
|
+
.equals([modelId, type])
|
|
76
|
+
.toArray();
|
|
77
|
+
return components.map((meshComponent) => ({
|
|
78
|
+
id: meshComponent.geode_id,
|
|
79
|
+
title: meshComponent.name,
|
|
80
|
+
category: meshComponent.type,
|
|
81
|
+
viewer_id: Number(meshComponent.viewer_id),
|
|
82
|
+
is_active: meshComponent.is_active,
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function getAllMeshComponents(modelId) {
|
|
87
|
+
const items = await database.model_components.where("id").equals(modelId).toArray();
|
|
88
|
+
return items.map((meshComponent) => ({
|
|
89
|
+
id: meshComponent.geode_id,
|
|
90
|
+
title: meshComponent.name,
|
|
91
|
+
category: meshComponent.type,
|
|
92
|
+
viewer_id: Number(meshComponent.viewer_id),
|
|
93
|
+
is_active: meshComponent.is_active,
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function fetchAllMeshComponents(modelId) {
|
|
98
|
+
const components = await getAllMeshComponents(modelId);
|
|
99
|
+
const byType = {};
|
|
100
|
+
for (const component of components) {
|
|
101
|
+
if (!byType[component.category]) {
|
|
102
|
+
byType[component.category] = [];
|
|
103
|
+
}
|
|
104
|
+
byType[component.category].push(component);
|
|
105
|
+
}
|
|
106
|
+
return byType;
|
|
107
|
+
}
|
|
108
|
+
|
|
72
109
|
function refFormatedMeshComponents(modelId) {
|
|
73
110
|
return useObservable(
|
|
74
111
|
liveQuery(() => formatedMeshComponents(modelId)),
|
|
@@ -240,6 +277,8 @@ export const useDataStore = defineStore("data", () => {
|
|
|
240
277
|
meshComponentType,
|
|
241
278
|
formatedMeshComponents,
|
|
242
279
|
refFormatedMeshComponents,
|
|
280
|
+
getMeshComponentsByType,
|
|
281
|
+
getAllMeshComponents,
|
|
243
282
|
registerObject,
|
|
244
283
|
deregisterObject,
|
|
245
284
|
addItem,
|
|
@@ -259,5 +298,6 @@ export const useDataStore = defineStore("data", () => {
|
|
|
259
298
|
exportStores,
|
|
260
299
|
importStores,
|
|
261
300
|
clear,
|
|
301
|
+
fetchAllMeshComponents,
|
|
262
302
|
};
|
|
263
303
|
});
|