@geode/opengeodeweb-front 9.13.2-rc.1 → 9.14.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/components/VeaseViewToolbar.vue +1 -0
- package/composables/project_manager.js +144 -0
- package/composables/viewer_call.js +3 -2
- package/package.json +1 -1
- package/stores/{app_store.js → app.js} +21 -30
- package/stores/data_base.js +40 -0
- package/stores/data_style.js +35 -0
- package/stores/hybrid_viewer.js +81 -0
- package/stores/treeview.js +63 -3
- package/tests/integration/microservices/back/requirements.txt +1 -1
- package/tests/integration/microservices/viewer/requirements.txt +1 -1
- package/tests/unit/composables/ProjectManager.nuxt.test.js +230 -0
- package/tests/unit/plugins/project_load.nuxt.test.js +83 -0
- package/tests/unit/stores/Appstore.nuxt.test.js +50 -68
- package/utils/file_import_workflow.js +73 -42
- /package/plugins/{autoStoreRegister.js → auto_store_register.js} +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json"
|
|
2
|
+
import fileDownload from "js-file-download"
|
|
3
|
+
import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json"
|
|
4
|
+
|
|
5
|
+
export function useProjectManager() {
|
|
6
|
+
const exportProject = async function () {
|
|
7
|
+
console.log("[export triggered]")
|
|
8
|
+
const appStore = useAppStore()
|
|
9
|
+
const geode = useGeodeStore()
|
|
10
|
+
const infraStore = useInfraStore()
|
|
11
|
+
const snapshot = appStore.exportStores()
|
|
12
|
+
const schema = back_schemas.opengeodeweb_back.export_project
|
|
13
|
+
const defaultName = "project.vease"
|
|
14
|
+
|
|
15
|
+
await infraStore.create_connection()
|
|
16
|
+
let downloaded = false
|
|
17
|
+
const result = await api_fetch(
|
|
18
|
+
{ schema, params: { snapshot, filename: defaultName } },
|
|
19
|
+
{
|
|
20
|
+
response_function: function (response) {
|
|
21
|
+
if (downloaded) return
|
|
22
|
+
downloaded = true
|
|
23
|
+
const data = response._data
|
|
24
|
+
const headerName =
|
|
25
|
+
(response.headers &&
|
|
26
|
+
typeof response.headers.get === "function" &&
|
|
27
|
+
(response.headers
|
|
28
|
+
.get("Content-Disposition")
|
|
29
|
+
?.match(/filename=\"(.+?)\"/)?.[1] ||
|
|
30
|
+
response.headers.get("new-file-name"))) ||
|
|
31
|
+
defaultName
|
|
32
|
+
if (!headerName.toLowerCase().endsWith(".vease")) {
|
|
33
|
+
throw new Error("Server returned non-.vease project archive")
|
|
34
|
+
}
|
|
35
|
+
fileDownload(data, headerName)
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
return result
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const importProjectFile = async function (file) {
|
|
43
|
+
const geode = useGeodeStore()
|
|
44
|
+
const viewerStore = useViewerStore()
|
|
45
|
+
const dataBaseStore = useDataBaseStore()
|
|
46
|
+
const treeviewStore = useTreeviewStore()
|
|
47
|
+
const hybridViewerStore = useHybridViewerStore()
|
|
48
|
+
const infraStore = useInfraStore()
|
|
49
|
+
|
|
50
|
+
await infraStore.create_connection()
|
|
51
|
+
await viewerStore.ws_connect()
|
|
52
|
+
|
|
53
|
+
const client = viewerStore.client
|
|
54
|
+
if (client && client.getConnection && client.getConnection().getSession) {
|
|
55
|
+
await client
|
|
56
|
+
.getConnection()
|
|
57
|
+
.getSession()
|
|
58
|
+
.call("opengeodeweb_viewer.release_database", [{}])
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await viewer_call({
|
|
62
|
+
schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization,
|
|
63
|
+
params: {},
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
treeviewStore.clear()
|
|
67
|
+
dataBaseStore.clear()
|
|
68
|
+
hybridViewerStore.clear()
|
|
69
|
+
|
|
70
|
+
const schemaImport = back_schemas.opengeodeweb_back.import_project
|
|
71
|
+
const form = new FormData()
|
|
72
|
+
const originalFileName = file && file.name ? file.name : "project.vease"
|
|
73
|
+
if (!originalFileName.toLowerCase().endsWith(".vease")) {
|
|
74
|
+
throw new Error("Uploaded file must be a .vease")
|
|
75
|
+
}
|
|
76
|
+
form.append("file", file, originalFileName)
|
|
77
|
+
|
|
78
|
+
const result = await $fetch(schemaImport.$id, {
|
|
79
|
+
baseURL: geode.base_url,
|
|
80
|
+
method: "POST",
|
|
81
|
+
body: form,
|
|
82
|
+
})
|
|
83
|
+
const snapshot = result && result.snapshot ? result.snapshot : {}
|
|
84
|
+
|
|
85
|
+
treeviewStore.isImporting = true
|
|
86
|
+
|
|
87
|
+
const client2 = viewerStore.client
|
|
88
|
+
if (
|
|
89
|
+
client2 &&
|
|
90
|
+
client2.getConnection &&
|
|
91
|
+
client2.getConnection().getSession
|
|
92
|
+
) {
|
|
93
|
+
await client2
|
|
94
|
+
.getConnection()
|
|
95
|
+
.getSession()
|
|
96
|
+
.call("opengeodeweb_viewer.import_project", [{}])
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await treeviewStore.importStores(snapshot.treeview)
|
|
100
|
+
await hybridViewerStore.initHybridViewer()
|
|
101
|
+
await hybridViewerStore.importStores(snapshot.hybridViewer)
|
|
102
|
+
|
|
103
|
+
const snapshotDataBase =
|
|
104
|
+
snapshot && snapshot.dataBase && snapshot.dataBase.db
|
|
105
|
+
? snapshot.dataBase.db
|
|
106
|
+
: {}
|
|
107
|
+
const items = Object.entries(snapshotDataBase).map(function (pair) {
|
|
108
|
+
const id = pair[0]
|
|
109
|
+
const item = pair[1]
|
|
110
|
+
const binaryLightViewable =
|
|
111
|
+
item && item.vtk_js && item.vtk_js.binary_light_viewable
|
|
112
|
+
? item.vtk_js.binary_light_viewable
|
|
113
|
+
: undefined
|
|
114
|
+
return {
|
|
115
|
+
id: id,
|
|
116
|
+
object_type: item.object_type,
|
|
117
|
+
geode_object: item.geode_object,
|
|
118
|
+
native_filename: item.native_filename,
|
|
119
|
+
viewable_filename: item.viewable_filename,
|
|
120
|
+
displayed_name: item.displayed_name,
|
|
121
|
+
vtk_js: { binary_light_viewable: binaryLightViewable },
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
await importWorkflowFromSnapshot(items)
|
|
126
|
+
await hybridViewerStore.importStores(snapshot.hybridViewer)
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
const dataStyleStore = useDataStyleStore()
|
|
130
|
+
await dataStyleStore.importStores(snapshot.dataStyle)
|
|
131
|
+
}
|
|
132
|
+
{
|
|
133
|
+
const dataStyleStore = useDataStyleStore()
|
|
134
|
+
await dataStyleStore.applyAllStylesFromState()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
treeviewStore.finalizeImportSelection()
|
|
138
|
+
treeviewStore.isImporting = false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { exportProject, importProjectFile }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default useProjectManager
|
package/package.json
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"description": "OpenSource Vue/Nuxt/Pinia/Vuetify framework for web applications",
|
|
41
41
|
"type": "module",
|
|
42
|
-
"version": "9.
|
|
42
|
+
"version": "9.14.0-rc.1",
|
|
43
43
|
"main": "./nuxt.config.js",
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@geode/opengeodeweb-back": "next",
|
|
@@ -5,81 +5,72 @@ export const useAppStore = defineStore("app", () => {
|
|
|
5
5
|
const isAlreadyRegistered = stores.some(
|
|
6
6
|
(registeredStore) => registeredStore.$id === store.$id,
|
|
7
7
|
)
|
|
8
|
-
|
|
9
8
|
if (isAlreadyRegistered) {
|
|
10
9
|
console.log(
|
|
11
10
|
`[AppStore] Store "${store.$id}" already registered, skipping`,
|
|
12
11
|
)
|
|
13
12
|
return
|
|
14
13
|
}
|
|
15
|
-
|
|
16
14
|
console.log("[AppStore] Registering store", store.$id)
|
|
17
15
|
stores.push(store)
|
|
18
16
|
}
|
|
19
17
|
|
|
20
|
-
function
|
|
18
|
+
function exportStores() {
|
|
21
19
|
const snapshot = {}
|
|
22
|
-
let
|
|
20
|
+
let exportCount = 0
|
|
23
21
|
|
|
24
22
|
for (const store of stores) {
|
|
25
|
-
if (!store.
|
|
26
|
-
continue
|
|
27
|
-
}
|
|
23
|
+
if (!store.exportStores) continue
|
|
28
24
|
const storeId = store.$id
|
|
29
25
|
try {
|
|
30
|
-
snapshot[storeId] = store.
|
|
31
|
-
|
|
26
|
+
snapshot[storeId] = store.exportStores()
|
|
27
|
+
exportCount++
|
|
32
28
|
} catch (error) {
|
|
33
|
-
console.error(`[AppStore] Error
|
|
29
|
+
console.error(`[AppStore] Error exporting store "${storeId}":`, error)
|
|
34
30
|
}
|
|
35
31
|
}
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
console.log(
|
|
33
|
+
`[AppStore] Exported ${exportCount} stores; snapshot keys:`,
|
|
34
|
+
Object.keys(snapshot),
|
|
35
|
+
)
|
|
38
36
|
return snapshot
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
function
|
|
39
|
+
async function importStores(snapshot) {
|
|
42
40
|
if (!snapshot) {
|
|
43
|
-
console.warn("[AppStore]
|
|
41
|
+
console.warn("[AppStore] import called with invalid snapshot")
|
|
44
42
|
return
|
|
45
43
|
}
|
|
44
|
+
console.log("[AppStore] Import snapshot keys:", Object.keys(snapshot || {}))
|
|
46
45
|
|
|
47
|
-
let
|
|
46
|
+
let importedCount = 0
|
|
48
47
|
const notFoundStores = []
|
|
49
|
-
|
|
50
48
|
for (const store of stores) {
|
|
51
|
-
if (!store.
|
|
52
|
-
continue
|
|
53
|
-
}
|
|
54
|
-
|
|
49
|
+
if (!store.importStores) continue
|
|
55
50
|
const storeId = store.$id
|
|
56
|
-
|
|
57
51
|
if (!snapshot[storeId]) {
|
|
58
52
|
notFoundStores.push(storeId)
|
|
59
53
|
continue
|
|
60
54
|
}
|
|
61
|
-
|
|
62
55
|
try {
|
|
63
|
-
store.
|
|
64
|
-
|
|
56
|
+
await store.importStores(snapshot[storeId])
|
|
57
|
+
importedCount++
|
|
65
58
|
} catch (error) {
|
|
66
|
-
console.error(`[AppStore] Error
|
|
59
|
+
console.error(`[AppStore] Error importing store "${storeId}":`, error)
|
|
67
60
|
}
|
|
68
61
|
}
|
|
69
|
-
|
|
70
62
|
if (notFoundStores.length > 0) {
|
|
71
63
|
console.warn(
|
|
72
64
|
`[AppStore] Stores not found in snapshot: ${notFoundStores.join(", ")}`,
|
|
73
65
|
)
|
|
74
66
|
}
|
|
75
|
-
|
|
76
|
-
console.log(`[AppStore] Loaded ${loadedCount} stores`)
|
|
67
|
+
console.log(`[AppStore] Imported ${importedCount} stores`)
|
|
77
68
|
}
|
|
78
69
|
|
|
79
70
|
return {
|
|
80
71
|
stores,
|
|
81
72
|
registerStore,
|
|
82
|
-
|
|
83
|
-
|
|
73
|
+
exportStores,
|
|
74
|
+
importStores,
|
|
84
75
|
}
|
|
85
76
|
})
|
package/stores/data_base.js
CHANGED
|
@@ -120,6 +120,43 @@ export const useDataBaseStore = defineStore("dataBase", () => {
|
|
|
120
120
|
return flat_indexes
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
function exportStores() {
|
|
124
|
+
const snapshotDb = {}
|
|
125
|
+
for (const [id, item] of Object.entries(db)) {
|
|
126
|
+
if (!item) continue
|
|
127
|
+
snapshotDb[id] = {
|
|
128
|
+
object_type: item.object_type,
|
|
129
|
+
geode_object: item.geode_object,
|
|
130
|
+
native_filename: item.native_filename,
|
|
131
|
+
viewable_filename: item.viewable_filename,
|
|
132
|
+
displayed_name: item.displayed_name,
|
|
133
|
+
vtk_js: {
|
|
134
|
+
binary_light_viewable: item?.vtk_js?.binary_light_viewable,
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { db: snapshotDb }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function importStores(snapshot) {
|
|
142
|
+
await hybridViewerStore.initHybridViewer()
|
|
143
|
+
hybridViewerStore.clear()
|
|
144
|
+
console.log(
|
|
145
|
+
"[DataBase] importStores entries:",
|
|
146
|
+
Object.keys(snapshot?.db || {}),
|
|
147
|
+
)
|
|
148
|
+
for (const [id, item] of Object.entries(snapshot?.db || {})) {
|
|
149
|
+
await registerObject(id)
|
|
150
|
+
await addItem(id, item)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function clear() {
|
|
155
|
+
for (const id of Object.keys(db)) {
|
|
156
|
+
delete db[id]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
123
160
|
return {
|
|
124
161
|
db,
|
|
125
162
|
itemMetaDatas,
|
|
@@ -134,5 +171,8 @@ export const useDataBaseStore = defineStore("dataBase", () => {
|
|
|
134
171
|
getSurfacesUuids,
|
|
135
172
|
getBlocksUuids,
|
|
136
173
|
getFlatIndexes,
|
|
174
|
+
exportStores,
|
|
175
|
+
importStores,
|
|
176
|
+
clear,
|
|
137
177
|
}
|
|
138
178
|
})
|
package/stores/data_style.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getDefaultStyle } from "../utils/default_styles.js"
|
|
1
2
|
import useDataStyleState from "../internal_stores/data_style_state.js"
|
|
2
3
|
import useMeshStyle from "../internal_stores/mesh/index.js"
|
|
3
4
|
import useModelStyle from "../internal_stores/model/index.js"
|
|
@@ -7,6 +8,7 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
|
|
|
7
8
|
const meshStyleStore = useMeshStyle()
|
|
8
9
|
const modelStyleStore = useModelStyle()
|
|
9
10
|
const dataBaseStore = useDataBaseStore()
|
|
11
|
+
const hybridViewerStore = useHybridViewerStore()
|
|
10
12
|
|
|
11
13
|
function addDataStyle(id, geode_object) {
|
|
12
14
|
dataStyleState.styles[id] = getDefaultStyle(geode_object)
|
|
@@ -37,6 +39,36 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
const exportStores = () => {
|
|
43
|
+
return { styles: dataStyleState.styles }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const importStores = (snapshot) => {
|
|
47
|
+
const stylesSnapshot = snapshot.styles || {}
|
|
48
|
+
for (const id of Object.keys(dataStyleState.styles)) {
|
|
49
|
+
delete dataStyleState.styles[id]
|
|
50
|
+
}
|
|
51
|
+
for (const [id, style] of Object.entries(stylesSnapshot)) {
|
|
52
|
+
dataStyleState.styles[id] = style
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const applyAllStylesFromState = () => {
|
|
57
|
+
const ids = Object.keys(dataStyleState.styles || {})
|
|
58
|
+
const promises = []
|
|
59
|
+
for (const id of ids) {
|
|
60
|
+
const meta = dataBaseStore.itemMetaDatas(id)
|
|
61
|
+
const objectType = meta?.object_type
|
|
62
|
+
const style = dataStyleState.styles[id]
|
|
63
|
+
if (style && objectType === "mesh") {
|
|
64
|
+
promises.push(meshStyleStore.applyMeshStyle(id))
|
|
65
|
+
} else if (style && objectType === "model") {
|
|
66
|
+
promises.push(modelStyleStore.applyModelStyle(id))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return Promise.all(promises)
|
|
70
|
+
}
|
|
71
|
+
|
|
40
72
|
return {
|
|
41
73
|
...dataStyleState,
|
|
42
74
|
...meshStyleStore,
|
|
@@ -44,5 +76,8 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
|
|
|
44
76
|
addDataStyle,
|
|
45
77
|
applyDefaultStyle,
|
|
46
78
|
setVisibility,
|
|
79
|
+
exportStores,
|
|
80
|
+
importStores,
|
|
81
|
+
applyAllStylesFromState,
|
|
47
82
|
}
|
|
48
83
|
})
|
package/stores/hybrid_viewer.js
CHANGED
|
@@ -97,6 +97,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
97
97
|
z_scale: z_scale,
|
|
98
98
|
},
|
|
99
99
|
})
|
|
100
|
+
remoteRender()
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
function syncRemoteCamera() {
|
|
@@ -198,6 +199,83 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
198
199
|
remoteRender()
|
|
199
200
|
}
|
|
200
201
|
|
|
202
|
+
const exportStores = () => {
|
|
203
|
+
const renderer = genericRenderWindow.value.getRenderer()
|
|
204
|
+
const camera = renderer.getActiveCamera()
|
|
205
|
+
const cameraSnapshot = camera
|
|
206
|
+
? {
|
|
207
|
+
focal_point: [...camera.getFocalPoint()],
|
|
208
|
+
view_up: [...camera.getViewUp()],
|
|
209
|
+
position: [...camera.getPosition()],
|
|
210
|
+
view_angle: camera.getViewAngle(),
|
|
211
|
+
clipping_range: [...camera.getClippingRange()],
|
|
212
|
+
distance: camera.getDistance(),
|
|
213
|
+
}
|
|
214
|
+
: camera_options
|
|
215
|
+
return { zScale: zScale.value, camera_options: cameraSnapshot }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const importStores = (snapshot) => {
|
|
219
|
+
const z_scale = snapshot.zScale
|
|
220
|
+
|
|
221
|
+
const applyCamera = () => {
|
|
222
|
+
const { camera_options } = snapshot
|
|
223
|
+
if (!camera_options) {
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const renderer = genericRenderWindow.value.getRenderer()
|
|
228
|
+
const camera = renderer.getActiveCamera()
|
|
229
|
+
|
|
230
|
+
camera.setFocalPoint(...camera_options.focal_point)
|
|
231
|
+
camera.setViewUp(...camera_options.view_up)
|
|
232
|
+
camera.setPosition(...camera_options.position)
|
|
233
|
+
camera.setViewAngle(camera_options.view_angle)
|
|
234
|
+
camera.setClippingRange(...camera_options.clipping_range)
|
|
235
|
+
|
|
236
|
+
genericRenderWindow.value.getRenderWindow().render()
|
|
237
|
+
|
|
238
|
+
const payload = {
|
|
239
|
+
camera_options: {
|
|
240
|
+
focal_point: camera_options.focal_point,
|
|
241
|
+
view_up: camera_options.view_up,
|
|
242
|
+
position: camera_options.position,
|
|
243
|
+
view_angle: camera_options.view_angle,
|
|
244
|
+
clipping_range: camera_options.clipping_range,
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
return viewer_call(
|
|
248
|
+
{
|
|
249
|
+
schema: viewer_schemas.opengeodeweb_viewer.viewer.update_camera,
|
|
250
|
+
params: payload,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
response_function: () => {
|
|
254
|
+
remoteRender()
|
|
255
|
+
Object.assign(camera_options, payload.camera_options)
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (typeof z_scale === "number") {
|
|
262
|
+
return setZScaling(z_scale).then(() => applyCamera())
|
|
263
|
+
}
|
|
264
|
+
return applyCamera()
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const clear = () => {
|
|
268
|
+
const renderer = genericRenderWindow.value.getRenderer()
|
|
269
|
+
const actors = renderer.getActors()
|
|
270
|
+
for (const actor of actors) {
|
|
271
|
+
renderer.removeActor(actor)
|
|
272
|
+
}
|
|
273
|
+
genericRenderWindow.value.getRenderWindow().render()
|
|
274
|
+
for (const id of Object.keys(db)) {
|
|
275
|
+
delete db[id]
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
201
279
|
return {
|
|
202
280
|
db,
|
|
203
281
|
genericRenderWindow,
|
|
@@ -210,5 +288,8 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => {
|
|
|
210
288
|
resize,
|
|
211
289
|
setContainer,
|
|
212
290
|
zScale,
|
|
291
|
+
clear,
|
|
292
|
+
exportStores,
|
|
293
|
+
importStores,
|
|
213
294
|
}
|
|
214
295
|
})
|
package/stores/treeview.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
export const useTreeviewStore = defineStore("treeview", () => {
|
|
2
|
-
const dataStyleStore = useDataStyleStore()
|
|
3
|
-
const dataBaseStore = useDataBaseStore()
|
|
4
|
-
|
|
5
2
|
const items = ref([])
|
|
6
3
|
const selection = ref([])
|
|
7
4
|
const components_selection = ref([])
|
|
@@ -10,6 +7,8 @@ export const useTreeviewStore = defineStore("treeview", () => {
|
|
|
10
7
|
const model_id = ref("")
|
|
11
8
|
const isTreeCollection = ref(false)
|
|
12
9
|
const selectedTree = ref(null)
|
|
10
|
+
const isImporting = ref(false)
|
|
11
|
+
const pendingSelectionIds = ref([])
|
|
13
12
|
|
|
14
13
|
// /** Functions **/
|
|
15
14
|
function addItem(geodeObject, displayed_name, id, object_type) {
|
|
@@ -53,6 +52,62 @@ export const useTreeviewStore = defineStore("treeview", () => {
|
|
|
53
52
|
panelWidth.value = width
|
|
54
53
|
}
|
|
55
54
|
|
|
55
|
+
function exportStores() {
|
|
56
|
+
return {
|
|
57
|
+
isAdditionnalTreeDisplayed: isAdditionnalTreeDisplayed.value,
|
|
58
|
+
panelWidth: panelWidth.value,
|
|
59
|
+
model_id: model_id.value,
|
|
60
|
+
isTreeCollection: isTreeCollection.value,
|
|
61
|
+
selectedTree: selectedTree.value,
|
|
62
|
+
selectionIds: selection.value.map((c) => c.id),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function importStores(snapshot) {
|
|
67
|
+
isAdditionnalTreeDisplayed.value =
|
|
68
|
+
snapshot?.isAdditionnalTreeDisplayed || false
|
|
69
|
+
panelWidth.value = snapshot?.panelWidth || 300
|
|
70
|
+
model_id.value = snapshot?.model_id || ""
|
|
71
|
+
isTreeCollection.value = snapshot?.isTreeCollection || false
|
|
72
|
+
selectedTree.value = snapshot?.selectedTree || null
|
|
73
|
+
|
|
74
|
+
pendingSelectionIds.value =
|
|
75
|
+
snapshot?.selectionIds ||
|
|
76
|
+
(snapshot?.selection || []).map((c) => c.id) ||
|
|
77
|
+
[]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function finalizeImportSelection() {
|
|
81
|
+
const ids = pendingSelectionIds.value || []
|
|
82
|
+
const rebuilt = []
|
|
83
|
+
if (!ids.length) {
|
|
84
|
+
for (const group of items.value) {
|
|
85
|
+
for (const child of group.children) {
|
|
86
|
+
rebuilt.push(child)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
for (const group of items.value) {
|
|
91
|
+
for (const child of group.children) {
|
|
92
|
+
if (ids.includes(child.id)) {
|
|
93
|
+
rebuilt.push(child)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
selection.value = rebuilt
|
|
99
|
+
pendingSelectionIds.value = []
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const clear = () => {
|
|
103
|
+
items.value = []
|
|
104
|
+
selection.value = []
|
|
105
|
+
components_selection.value = []
|
|
106
|
+
pendingSelectionIds.value = []
|
|
107
|
+
model_id.value = ""
|
|
108
|
+
selectedTree.value = undefined
|
|
109
|
+
}
|
|
110
|
+
|
|
56
111
|
return {
|
|
57
112
|
items,
|
|
58
113
|
selection,
|
|
@@ -61,10 +116,15 @@ export const useTreeviewStore = defineStore("treeview", () => {
|
|
|
61
116
|
panelWidth,
|
|
62
117
|
model_id,
|
|
63
118
|
selectedTree,
|
|
119
|
+
isImporting,
|
|
64
120
|
addItem,
|
|
65
121
|
displayAdditionalTree,
|
|
66
122
|
displayFileTree,
|
|
67
123
|
toggleTreeView,
|
|
68
124
|
setPanelWidth,
|
|
125
|
+
exportStores,
|
|
126
|
+
importStores,
|
|
127
|
+
finalizeImportSelection,
|
|
128
|
+
clear,
|
|
69
129
|
}
|
|
70
130
|
})
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from "vitest"
|
|
2
|
+
import { setActivePinia } from "pinia"
|
|
3
|
+
import { createTestingPinia } from "@pinia/testing"
|
|
4
|
+
import { useProjectManager } from "@/composables/project_manager.js"
|
|
5
|
+
|
|
6
|
+
// Snapshot
|
|
7
|
+
const snapshotMock = {
|
|
8
|
+
dataBase: {
|
|
9
|
+
db: {
|
|
10
|
+
abc123: {
|
|
11
|
+
object_type: "mesh",
|
|
12
|
+
geode_object: "PointSet2D",
|
|
13
|
+
native_filename: "native.ext",
|
|
14
|
+
viewable_filename: "viewable.ext",
|
|
15
|
+
displayed_name: "My Data",
|
|
16
|
+
vtk_js: { binary_light_viewable: "VGxpZ2h0RGF0YQ==" },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
treeview: {
|
|
21
|
+
isAdditionnalTreeDisplayed: false,
|
|
22
|
+
panelWidth: 320,
|
|
23
|
+
model_id: "",
|
|
24
|
+
isTreeCollection: false,
|
|
25
|
+
selectedTree: null,
|
|
26
|
+
selectionIds: [],
|
|
27
|
+
},
|
|
28
|
+
dataStyle: {
|
|
29
|
+
styles: {
|
|
30
|
+
abc123: {
|
|
31
|
+
points: {
|
|
32
|
+
visibility: true,
|
|
33
|
+
coloring: {
|
|
34
|
+
active: "color",
|
|
35
|
+
color: { r: 255, g: 255, b: 255 },
|
|
36
|
+
vertex: null,
|
|
37
|
+
},
|
|
38
|
+
size: 2,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
hybridViewer: {
|
|
44
|
+
zScale: 1.5,
|
|
45
|
+
camera_options: {
|
|
46
|
+
focal_point: [1, 2, 3],
|
|
47
|
+
view_up: [0, 1, 0],
|
|
48
|
+
position: [10, 11, 12],
|
|
49
|
+
view_angle: 30,
|
|
50
|
+
clipping_range: [0.1, 1000],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const geodeStoreMock = {
|
|
56
|
+
start_request: vi.fn(),
|
|
57
|
+
stop_request: vi.fn(),
|
|
58
|
+
base_url: "",
|
|
59
|
+
$reset: vi.fn(),
|
|
60
|
+
}
|
|
61
|
+
const infraStoreMock = { create_connection: vi.fn(() => Promise.resolve()) }
|
|
62
|
+
const viewerStoreMock = { ws_connect: vi.fn(() => Promise.resolve()) }
|
|
63
|
+
const treeviewStoreMock = {
|
|
64
|
+
clear: vi.fn(),
|
|
65
|
+
importStores: vi.fn(() => Promise.resolve()),
|
|
66
|
+
finalizeImportSelection: vi.fn(),
|
|
67
|
+
addItem: vi.fn(() => Promise.resolve()),
|
|
68
|
+
}
|
|
69
|
+
const dataBaseStoreMock = {
|
|
70
|
+
clear: vi.fn(),
|
|
71
|
+
registerObject: vi.fn(() => Promise.resolve()),
|
|
72
|
+
addItem: vi.fn(() => Promise.resolve()),
|
|
73
|
+
}
|
|
74
|
+
const dataStyleStoreMock = {
|
|
75
|
+
importStores: vi.fn(() => Promise.resolve()),
|
|
76
|
+
applyAllStylesFromState: vi.fn(() => Promise.resolve()),
|
|
77
|
+
addDataStyle: vi.fn(() => Promise.resolve()),
|
|
78
|
+
applyDefaultStyle: vi.fn(() => Promise.resolve()),
|
|
79
|
+
}
|
|
80
|
+
const hybridViewerStoreMock = {
|
|
81
|
+
clear: vi.fn(),
|
|
82
|
+
initHybridViewer: vi.fn(() => Promise.resolve()),
|
|
83
|
+
importStores: vi.fn(async (snapshot) => {
|
|
84
|
+
if (snapshot?.zScale != null)
|
|
85
|
+
hybridViewerStoreMock.setZScaling(snapshot.zScale)
|
|
86
|
+
if (snapshot?.camera_options) {
|
|
87
|
+
const { viewer_call } = await import("@/composables/viewer_call.js")
|
|
88
|
+
viewer_call({
|
|
89
|
+
schema: { $id: "opengeodeweb_viewer/viewer.update_camera" },
|
|
90
|
+
params: { camera_options: snapshot.camera_options },
|
|
91
|
+
})
|
|
92
|
+
hybridViewerStoreMock.remoteRender()
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
addItem: vi.fn(() => Promise.resolve()),
|
|
96
|
+
remoteRender: vi.fn(),
|
|
97
|
+
setZScaling: vi.fn(),
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Mocks
|
|
101
|
+
vi.stubGlobal(
|
|
102
|
+
"$fetch",
|
|
103
|
+
vi.fn(async () => ({ snapshot: snapshotMock })),
|
|
104
|
+
)
|
|
105
|
+
vi.mock("@/composables/viewer_call.js", () => ({
|
|
106
|
+
viewer_call: vi.fn(() => Promise.resolve()),
|
|
107
|
+
}))
|
|
108
|
+
vi.mock("@/composables/api_fetch.js", () => ({
|
|
109
|
+
api_fetch: vi.fn(async (_req, options = {}) => {
|
|
110
|
+
const response = {
|
|
111
|
+
_data: new Blob(["zipcontent"], { type: "application/zip" }),
|
|
112
|
+
headers: {
|
|
113
|
+
get: (k) => (k === "new-file-name" ? "project_123.vease" : null),
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
if (options.response_function) await options.response_function(response)
|
|
117
|
+
return response
|
|
118
|
+
}),
|
|
119
|
+
}))
|
|
120
|
+
vi.mock("js-file-download", () => ({ default: vi.fn() }))
|
|
121
|
+
vi.mock("@/stores/infra.js", () => ({ useInfraStore: () => infraStoreMock }))
|
|
122
|
+
vi.mock("@/stores/viewer.js", () => ({ useViewerStore: () => viewerStoreMock }))
|
|
123
|
+
vi.mock("@/stores/treeview.js", () => ({
|
|
124
|
+
useTreeviewStore: () => treeviewStoreMock,
|
|
125
|
+
}))
|
|
126
|
+
vi.mock("@/stores/data_base.js", () => ({
|
|
127
|
+
useDataBaseStore: () => dataBaseStoreMock,
|
|
128
|
+
}))
|
|
129
|
+
vi.mock("@/stores/data_style.js", () => ({
|
|
130
|
+
useDataStyleStore: () => dataStyleStoreMock,
|
|
131
|
+
}))
|
|
132
|
+
vi.mock("@/stores/hybrid_viewer.js", () => ({
|
|
133
|
+
useHybridViewerStore: () => hybridViewerStoreMock,
|
|
134
|
+
}))
|
|
135
|
+
vi.mock("@/stores/geode.js", () => ({ useGeodeStore: () => geodeStoreMock }))
|
|
136
|
+
vi.mock("@/stores/app.js", () => ({
|
|
137
|
+
useAppStore: () => ({
|
|
138
|
+
exportStores: vi.fn(() => ({ projectName: "mockedProject" })),
|
|
139
|
+
}),
|
|
140
|
+
}))
|
|
141
|
+
|
|
142
|
+
vi.stubGlobal("useAppStore", () => ({
|
|
143
|
+
exportStores: vi.fn(() => ({ projectName: "mockedProject" })),
|
|
144
|
+
}))
|
|
145
|
+
|
|
146
|
+
describe("ProjectManager composable (compact)", () => {
|
|
147
|
+
beforeEach(async () => {
|
|
148
|
+
const pinia = createTestingPinia({ stubActions: false, createSpy: vi.fn })
|
|
149
|
+
setActivePinia(pinia)
|
|
150
|
+
|
|
151
|
+
// reset spies
|
|
152
|
+
for (const store of [
|
|
153
|
+
infraStoreMock,
|
|
154
|
+
viewerStoreMock,
|
|
155
|
+
treeviewStoreMock,
|
|
156
|
+
dataBaseStoreMock,
|
|
157
|
+
dataStyleStoreMock,
|
|
158
|
+
hybridViewerStoreMock,
|
|
159
|
+
]) {
|
|
160
|
+
Object.values(store).forEach(
|
|
161
|
+
(v) => typeof v === "function" && v.mockClear && v.mockClear(),
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
const { viewer_call } = await import("@/composables/viewer_call.js")
|
|
165
|
+
viewer_call.mockClear()
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test("exportProject", async () => {
|
|
169
|
+
const { exportProject } = useProjectManager()
|
|
170
|
+
const { default: fileDownload } = await import("js-file-download")
|
|
171
|
+
|
|
172
|
+
await exportProject()
|
|
173
|
+
|
|
174
|
+
expect(infraStoreMock.create_connection).toHaveBeenCalled()
|
|
175
|
+
expect(fileDownload).toHaveBeenCalled()
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test("importProjectFile with snapshot", async () => {
|
|
179
|
+
const { importProjectFile } = useProjectManager()
|
|
180
|
+
const file = new Blob(['{"dataBase":{"db":{}}}'], {
|
|
181
|
+
type: "application/json",
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
await importProjectFile(file)
|
|
185
|
+
|
|
186
|
+
const { viewer_call } = await import("@/composables/viewer_call.js")
|
|
187
|
+
|
|
188
|
+
expect(infraStoreMock.create_connection).toHaveBeenCalled()
|
|
189
|
+
expect(viewerStoreMock.ws_connect).toHaveBeenCalled()
|
|
190
|
+
expect(viewer_call).toHaveBeenCalledTimes(3)
|
|
191
|
+
|
|
192
|
+
expect(treeviewStoreMock.importStores).toHaveBeenCalledWith(
|
|
193
|
+
snapshotMock.treeview,
|
|
194
|
+
)
|
|
195
|
+
expect(hybridViewerStoreMock.initHybridViewer).toHaveBeenCalled()
|
|
196
|
+
expect(hybridViewerStoreMock.importStores).toHaveBeenCalledWith(
|
|
197
|
+
snapshotMock.hybridViewer,
|
|
198
|
+
)
|
|
199
|
+
expect(hybridViewerStoreMock.setZScaling).toHaveBeenCalledWith(1.5)
|
|
200
|
+
|
|
201
|
+
expect(dataStyleStoreMock.importStores).toHaveBeenCalledWith(
|
|
202
|
+
snapshotMock.dataStyle,
|
|
203
|
+
)
|
|
204
|
+
expect(dataStyleStoreMock.applyAllStylesFromState).toHaveBeenCalled()
|
|
205
|
+
|
|
206
|
+
expect(dataBaseStoreMock.registerObject).toHaveBeenCalledWith("abc123")
|
|
207
|
+
expect(dataBaseStoreMock.addItem).toHaveBeenCalledWith(
|
|
208
|
+
"abc123",
|
|
209
|
+
expect.objectContaining({
|
|
210
|
+
object_type: "mesh",
|
|
211
|
+
geode_object: "PointSet2D",
|
|
212
|
+
displayed_name: "My Data",
|
|
213
|
+
}),
|
|
214
|
+
)
|
|
215
|
+
expect(treeviewStoreMock.addItem).toHaveBeenCalledWith(
|
|
216
|
+
"PointSet2D",
|
|
217
|
+
"My Data",
|
|
218
|
+
"abc123",
|
|
219
|
+
"mesh",
|
|
220
|
+
)
|
|
221
|
+
expect(hybridViewerStoreMock.addItem).toHaveBeenCalledWith("abc123")
|
|
222
|
+
expect(dataStyleStoreMock.addDataStyle).toHaveBeenCalledWith(
|
|
223
|
+
"abc123",
|
|
224
|
+
"PointSet2D",
|
|
225
|
+
)
|
|
226
|
+
expect(dataStyleStoreMock.applyDefaultStyle).toHaveBeenCalledWith("abc123")
|
|
227
|
+
|
|
228
|
+
expect(hybridViewerStoreMock.remoteRender).toHaveBeenCalled()
|
|
229
|
+
})
|
|
230
|
+
})
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from "vitest"
|
|
2
|
+
import { createTestingPinia } from "@pinia/testing"
|
|
3
|
+
import { setActivePinia } from "pinia"
|
|
4
|
+
|
|
5
|
+
vi.mock("@/composables/viewer_call.js", () => ({
|
|
6
|
+
default: vi.fn(() => Promise.resolve({})),
|
|
7
|
+
viewer_call: vi.fn(() => Promise.resolve({})),
|
|
8
|
+
}))
|
|
9
|
+
vi.mock("@/stores/hybrid_viewer.js", () => ({
|
|
10
|
+
useHybridViewerStore: () => ({
|
|
11
|
+
$id: "hybridViewer",
|
|
12
|
+
initHybridViewer: vi.fn(),
|
|
13
|
+
clear: vi.fn(),
|
|
14
|
+
addItem: vi.fn(),
|
|
15
|
+
setZScaling: vi.fn(),
|
|
16
|
+
save: vi.fn(),
|
|
17
|
+
load: vi.fn(),
|
|
18
|
+
}),
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
setActivePinia(
|
|
23
|
+
createTestingPinia({
|
|
24
|
+
stubActions: false,
|
|
25
|
+
createSpy: vi.fn,
|
|
26
|
+
}),
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe("Project import", () => {
|
|
31
|
+
test("app.importStores restores stores", async () => {
|
|
32
|
+
const stores = {
|
|
33
|
+
app: useAppStore(),
|
|
34
|
+
dataBase: useDataBaseStore(),
|
|
35
|
+
treeview: useTreeviewStore(),
|
|
36
|
+
dataStyle: useDataStyleStore(),
|
|
37
|
+
hybrid: useHybridViewerStore(),
|
|
38
|
+
}
|
|
39
|
+
Object.values(stores)
|
|
40
|
+
.slice(1)
|
|
41
|
+
.forEach((store) => stores.app.registerStore(store))
|
|
42
|
+
|
|
43
|
+
vi.spyOn(stores.dataBase, "importStores").mockImplementation(
|
|
44
|
+
async (snapshot) => {
|
|
45
|
+
for (const [id, item] of Object.entries(snapshot?.db || {})) {
|
|
46
|
+
stores.dataBase.db[id] = item
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const snapshot = {
|
|
52
|
+
dataBase: {
|
|
53
|
+
db: {
|
|
54
|
+
abc123: {
|
|
55
|
+
object_type: "mesh",
|
|
56
|
+
geode_object: "PointSet2D",
|
|
57
|
+
native_filename: "native.ext",
|
|
58
|
+
viewable_filename: "viewable.ext",
|
|
59
|
+
displayed_name: "My Data",
|
|
60
|
+
vtk_js: { binary_light_viewable: "VGxpZ2h0RGF0YQ==" },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
treeview: {
|
|
65
|
+
items: [{ title: "PointSet2D", children: [] }],
|
|
66
|
+
selection: [],
|
|
67
|
+
components_selection: [],
|
|
68
|
+
isAdditionnalTreeDisplayed: false,
|
|
69
|
+
panelWidth: 320,
|
|
70
|
+
model_id: "",
|
|
71
|
+
isTreeCollection: false,
|
|
72
|
+
selectedTree: null,
|
|
73
|
+
},
|
|
74
|
+
dataStyle: { styles: { abc123: { some: "style" } } },
|
|
75
|
+
hybridViewer: { zScale: 1.5 },
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await stores.app.importStores(snapshot)
|
|
79
|
+
|
|
80
|
+
expect(stores.dataBase.db.abc123).toBeDefined()
|
|
81
|
+
expect(stores.dataStyle.styles.abc123).toBeDefined()
|
|
82
|
+
})
|
|
83
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, expectTypeOf, test, vi } from "vitest"
|
|
2
2
|
import { createTestingPinia } from "@pinia/testing"
|
|
3
|
-
import { useAppStore } from "@/stores/
|
|
3
|
+
import { useAppStore } from "@/stores/app.js"
|
|
4
4
|
import { setActivePinia } from "pinia"
|
|
5
5
|
|
|
6
6
|
beforeEach(async () => {
|
|
@@ -16,8 +16,8 @@ describe("App Store", () => {
|
|
|
16
16
|
test("initial state", () => {
|
|
17
17
|
const app_store = useAppStore()
|
|
18
18
|
expectTypeOf(app_store.stores).toBeArray()
|
|
19
|
-
expectTypeOf(app_store.
|
|
20
|
-
expectTypeOf(app_store.
|
|
19
|
+
expectTypeOf(app_store.exportStores).toBeFunction()
|
|
20
|
+
expectTypeOf(app_store.importStores).toBeFunction()
|
|
21
21
|
expectTypeOf(app_store.registerStore).toBeFunction()
|
|
22
22
|
})
|
|
23
23
|
})
|
|
@@ -46,7 +46,7 @@ describe("App Store", () => {
|
|
|
46
46
|
load: vi.fn().mockImplementation(() => {}),
|
|
47
47
|
}
|
|
48
48
|
const mock_store_2 = {
|
|
49
|
-
$id: "
|
|
49
|
+
$id: "geodeStore",
|
|
50
50
|
save: vi.fn().mockImplementation(() => {}),
|
|
51
51
|
load: vi.fn().mockImplementation(() => {}),
|
|
52
52
|
}
|
|
@@ -56,58 +56,60 @@ describe("App Store", () => {
|
|
|
56
56
|
|
|
57
57
|
expect(app_store.stores.length).toBe(2)
|
|
58
58
|
expect(app_store.stores[0].$id).toBe("userStore")
|
|
59
|
-
expect(app_store.stores[1].$id).toBe("
|
|
59
|
+
expect(app_store.stores[1].$id).toBe("geodeStore")
|
|
60
60
|
})
|
|
61
61
|
})
|
|
62
62
|
|
|
63
|
-
describe("
|
|
64
|
-
test("
|
|
63
|
+
describe("Export", () => {
|
|
64
|
+
test("export stores with exportStores method", () => {
|
|
65
65
|
const app_store = useAppStore()
|
|
66
66
|
const mock_store_1 = {
|
|
67
67
|
$id: "userStore",
|
|
68
|
-
|
|
68
|
+
exportStores: vi.fn().mockImplementation(() => ({
|
|
69
69
|
name: "toto",
|
|
70
70
|
email: "toto@titi.com",
|
|
71
71
|
})),
|
|
72
|
-
|
|
72
|
+
importStores: vi.fn().mockImplementation(() => {}),
|
|
73
73
|
}
|
|
74
74
|
const mock_store_2 = {
|
|
75
|
-
$id: "
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
$id: "geodeStore",
|
|
76
|
+
exportStores: vi
|
|
77
|
+
.fn()
|
|
78
|
+
.mockImplementation(() => ({ items: [], total: 0 })),
|
|
79
|
+
importStores: vi.fn().mockImplementation(() => {}),
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
app_store.registerStore(mock_store_1)
|
|
81
83
|
app_store.registerStore(mock_store_2)
|
|
82
84
|
|
|
83
|
-
const snapshot = app_store.
|
|
85
|
+
const snapshot = app_store.exportStores()
|
|
84
86
|
|
|
85
|
-
expect(mock_store_1.
|
|
86
|
-
expect(mock_store_2.
|
|
87
|
+
expect(mock_store_1.exportStores).toHaveBeenCalledTimes(1)
|
|
88
|
+
expect(mock_store_2.exportStores).toHaveBeenCalledTimes(1)
|
|
87
89
|
expect(snapshot).toEqual({
|
|
88
90
|
userStore: { name: "toto", email: "toto@titi.com" },
|
|
89
|
-
|
|
91
|
+
geodeStore: { items: [], total: 0 },
|
|
90
92
|
})
|
|
91
93
|
})
|
|
92
94
|
|
|
93
|
-
test("skip stores without
|
|
95
|
+
test("skip stores without exportSave method", () => {
|
|
94
96
|
const app_store = useAppStore()
|
|
95
97
|
const mock_store_1 = {
|
|
96
98
|
$id: "withSave",
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
exportStores: vi.fn().mockImplementation(() => ({ data: "test" })),
|
|
100
|
+
importStores: vi.fn().mockImplementation(() => {}),
|
|
99
101
|
}
|
|
100
102
|
const mock_store_2 = {
|
|
101
103
|
$id: "withoutSave",
|
|
102
|
-
|
|
104
|
+
importStores: vi.fn().mockImplementation(() => {}),
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
app_store.registerStore(mock_store_1)
|
|
106
108
|
app_store.registerStore(mock_store_2)
|
|
107
109
|
|
|
108
|
-
const snapshot = app_store.
|
|
110
|
+
const snapshot = app_store.exportStores()
|
|
109
111
|
|
|
110
|
-
expect(mock_store_1.
|
|
112
|
+
expect(mock_store_1.exportStores).toHaveBeenCalledTimes(1)
|
|
111
113
|
expect(snapshot).toEqual({
|
|
112
114
|
withSave: { data: "test" },
|
|
113
115
|
})
|
|
@@ -116,71 +118,53 @@ describe("App Store", () => {
|
|
|
116
118
|
|
|
117
119
|
test("return empty snapshot when no stores registered", () => {
|
|
118
120
|
const app_store = useAppStore()
|
|
119
|
-
const snapshot = app_store.
|
|
121
|
+
const snapshot = app_store.exportStores()
|
|
120
122
|
expect(snapshot).toEqual({})
|
|
121
123
|
})
|
|
122
124
|
})
|
|
123
125
|
|
|
124
126
|
describe("load", () => {
|
|
125
|
-
test("
|
|
126
|
-
const
|
|
127
|
-
const
|
|
127
|
+
test("App Store > actions > importStores > import stores with importStores method", async () => {
|
|
128
|
+
const appStore = useAppStore()
|
|
129
|
+
const userStore = {
|
|
128
130
|
$id: "userStore",
|
|
129
|
-
|
|
130
|
-
load: vi.fn().mockImplementation(() => {}),
|
|
131
|
+
importStores: vi.fn().mockResolvedValue(),
|
|
131
132
|
}
|
|
132
|
-
const
|
|
133
|
-
$id: "
|
|
134
|
-
|
|
135
|
-
load: vi.fn().mockImplementation(() => {}),
|
|
133
|
+
const geodeStore = {
|
|
134
|
+
$id: "geodeStore",
|
|
135
|
+
importStores: vi.fn().mockResolvedValue(),
|
|
136
136
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
app_store.registerStore(mock_store_2)
|
|
140
|
-
|
|
137
|
+
appStore.registerStore(userStore)
|
|
138
|
+
appStore.registerStore(geodeStore)
|
|
141
139
|
const snapshot = {
|
|
142
|
-
userStore: {
|
|
143
|
-
|
|
140
|
+
userStore: { some: "data" },
|
|
141
|
+
geodeStore: { other: "data" },
|
|
144
142
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
expect(mock_store_1.load).toHaveBeenCalledTimes(1)
|
|
149
|
-
expect(mock_store_1.load).toHaveBeenCalledWith({
|
|
150
|
-
name: "tata",
|
|
151
|
-
email: "tata@tutu.com",
|
|
152
|
-
})
|
|
153
|
-
expect(mock_store_2.load).toHaveBeenCalledTimes(1)
|
|
154
|
-
expect(mock_store_2.load).toHaveBeenCalledWith({
|
|
155
|
-
items: [{ id: 1 }],
|
|
156
|
-
total: 50,
|
|
157
|
-
})
|
|
143
|
+
await appStore.importStores(snapshot)
|
|
144
|
+
expect(userStore.importStores).toHaveBeenCalledTimes(1)
|
|
145
|
+
expect(geodeStore.importStores).toHaveBeenCalledTimes(1)
|
|
158
146
|
})
|
|
159
147
|
|
|
160
|
-
test("skip stores without
|
|
148
|
+
test("skip stores without importStores method", () => {
|
|
161
149
|
const app_store = useAppStore()
|
|
162
150
|
const mock_store_1 = {
|
|
163
|
-
$id: "
|
|
151
|
+
$id: "withImport",
|
|
164
152
|
save: vi.fn().mockImplementation(() => {}),
|
|
165
|
-
|
|
153
|
+
importStores: vi.fn().mockImplementation(() => {}),
|
|
166
154
|
}
|
|
167
155
|
const mock_store_2 = {
|
|
168
|
-
$id: "
|
|
156
|
+
$id: "withoutImport",
|
|
169
157
|
save: vi.fn().mockImplementation(() => {}),
|
|
170
158
|
}
|
|
171
|
-
|
|
172
159
|
app_store.registerStore(mock_store_1)
|
|
173
160
|
app_store.registerStore(mock_store_2)
|
|
174
|
-
|
|
175
161
|
const snapshot = {
|
|
176
|
-
|
|
177
|
-
|
|
162
|
+
withImport: { data: "test" },
|
|
163
|
+
withoutImport: { data: "ignored" },
|
|
178
164
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
expect(mock_store_1.load).toHaveBeenCalledTimes(1)
|
|
183
|
-
expect(mock_store_2.load).toBeUndefined()
|
|
165
|
+
app_store.importStores(snapshot)
|
|
166
|
+
expect(mock_store_1.importStores).toHaveBeenCalledTimes(1)
|
|
167
|
+
expect(mock_store_2.importStores).toBeUndefined()
|
|
184
168
|
})
|
|
185
169
|
|
|
186
170
|
test("warn when store not found in snapshot", () => {
|
|
@@ -190,12 +174,10 @@ describe("App Store", () => {
|
|
|
190
174
|
.mockImplementation(() => {})
|
|
191
175
|
const mock_store = {
|
|
192
176
|
$id: "testStore",
|
|
193
|
-
|
|
177
|
+
importStores: vi.fn().mockImplementation(() => {}),
|
|
194
178
|
}
|
|
195
|
-
|
|
196
179
|
app_store.registerStore(mock_store)
|
|
197
|
-
app_store.
|
|
198
|
-
|
|
180
|
+
app_store.importStores({})
|
|
199
181
|
expect(console_warn_spy).toHaveBeenCalledWith(
|
|
200
182
|
expect.stringContaining("Stores not found in snapshot: testStore"),
|
|
201
183
|
)
|
|
@@ -4,7 +4,7 @@ import { useHybridViewerStore } from "../stores/hybrid_viewer"
|
|
|
4
4
|
|
|
5
5
|
// Local imports
|
|
6
6
|
|
|
7
|
-
function importWorkflow(files) {
|
|
7
|
+
async function importWorkflow(files) {
|
|
8
8
|
console.log("importWorkflow", { files })
|
|
9
9
|
const promise_array = []
|
|
10
10
|
for (const file of files) {
|
|
@@ -15,64 +15,95 @@ function importWorkflow(files) {
|
|
|
15
15
|
return Promise.all(promise_array)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
function buildImportItemFromPayloadApi(value, geode_object) {
|
|
19
|
+
return {
|
|
20
|
+
id: value.id,
|
|
21
|
+
object_type: value.object_type,
|
|
22
|
+
geode_object,
|
|
23
|
+
native_filename: value.native_file_name,
|
|
24
|
+
viewable_filename: value.viewable_file_name,
|
|
25
|
+
displayed_name: value.name,
|
|
26
|
+
vtk_js: { binary_light_viewable: value.binary_light_viewable },
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function importItem(item) {
|
|
19
31
|
const dataBaseStore = useDataBaseStore()
|
|
20
32
|
const dataStyleStore = useDataStyleStore()
|
|
21
33
|
const hybridViewerStore = useHybridViewerStore()
|
|
22
34
|
const treeviewStore = useTreeviewStore()
|
|
35
|
+
await dataBaseStore.registerObject(item.id)
|
|
36
|
+
await dataBaseStore.addItem(item.id, {
|
|
37
|
+
object_type: item.object_type,
|
|
38
|
+
geode_object: item.geode_object,
|
|
39
|
+
native_filename: item.native_filename,
|
|
40
|
+
viewable_filename: item.viewable_filename,
|
|
41
|
+
displayed_name: item.displayed_name,
|
|
42
|
+
vtk_js: item.vtk_js,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
await treeviewStore.addItem(
|
|
46
|
+
item.geode_object,
|
|
47
|
+
item.displayed_name,
|
|
48
|
+
item.id,
|
|
49
|
+
item.object_type,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
await hybridViewerStore.addItem(item.id)
|
|
53
|
+
await dataStyleStore.addDataStyle(item.id, item.geode_object)
|
|
54
|
+
|
|
55
|
+
if (item.object_type === "model") {
|
|
56
|
+
await Promise.all([
|
|
57
|
+
dataBaseStore.fetchMeshComponents(item.id),
|
|
58
|
+
dataBaseStore.fetchUuidToFlatIndexDict(item.id),
|
|
59
|
+
])
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await dataStyleStore.applyDefaultStyle(item.id)
|
|
63
|
+
hybridViewerStore.remoteRender()
|
|
64
|
+
return item.id
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function importFile(filename, geode_object) {
|
|
23
68
|
const { data } = await api_fetch({
|
|
24
69
|
schema: back_schemas.opengeodeweb_back.save_viewable_file,
|
|
25
70
|
params: {
|
|
26
71
|
input_geode_object: geode_object,
|
|
27
|
-
filename,
|
|
72
|
+
filename: filename,
|
|
28
73
|
},
|
|
29
74
|
})
|
|
30
75
|
|
|
31
76
|
console.log("data.value", data.value)
|
|
32
77
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
viewable_file_name,
|
|
37
|
-
name,
|
|
38
|
-
object_type,
|
|
39
|
-
binary_light_viewable,
|
|
40
|
-
} = data.value
|
|
41
|
-
|
|
42
|
-
await dataBaseStore.registerObject(id)
|
|
43
|
-
console.log("after dataBaseStore.registerObject")
|
|
44
|
-
await dataBaseStore.addItem(id, {
|
|
45
|
-
object_type: object_type,
|
|
46
|
-
geode_object: geode_object,
|
|
47
|
-
native_filename: native_file_name,
|
|
48
|
-
viewable_filename: viewable_file_name,
|
|
49
|
-
displayed_name: name,
|
|
50
|
-
vtk_js: {
|
|
51
|
-
binary_light_viewable,
|
|
52
|
-
},
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
await treeviewStore.addItem(geode_object, name, id, object_type)
|
|
78
|
+
const item = buildImportItemFromPayloadApi(data._value, geode_object)
|
|
79
|
+
return importItem(item)
|
|
80
|
+
}
|
|
56
81
|
|
|
57
|
-
|
|
82
|
+
async function importItemFromSnapshot(item) {
|
|
83
|
+
return importItem(item)
|
|
84
|
+
}
|
|
58
85
|
|
|
59
|
-
|
|
60
|
-
console.log("
|
|
86
|
+
async function importWorkflowFromSnapshot(items) {
|
|
87
|
+
console.log("[importWorkflowFromSnapshot] start", { count: items?.length })
|
|
88
|
+
const dataBaseStore = useDataBaseStore()
|
|
89
|
+
const treeviewStore = useTreeviewStore()
|
|
90
|
+
const dataStyleStore = useDataStyleStore()
|
|
91
|
+
const hybridViewerStore = useHybridViewerStore()
|
|
61
92
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
dataBaseStore
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
93
|
+
const ids = []
|
|
94
|
+
for (const item of items) {
|
|
95
|
+
const id = await importItemFromSnapshot(
|
|
96
|
+
item,
|
|
97
|
+
dataBaseStore,
|
|
98
|
+
treeviewStore,
|
|
99
|
+
dataStyleStore,
|
|
100
|
+
hybridViewerStore,
|
|
101
|
+
)
|
|
102
|
+
ids.push(id)
|
|
71
103
|
}
|
|
72
|
-
await dataStyleStore.applyDefaultStyle(id)
|
|
73
|
-
console.log("after dataStyleStore.applyDefaultStyle")
|
|
74
104
|
hybridViewerStore.remoteRender()
|
|
75
|
-
|
|
105
|
+
console.log("[importWorkflowFromSnapshot] done", { ids })
|
|
106
|
+
return ids
|
|
76
107
|
}
|
|
77
108
|
|
|
78
|
-
export { importFile, importWorkflow }
|
|
109
|
+
export { importFile, importWorkflow, importWorkflowFromSnapshot }
|
|
File without changes
|