@geode/opengeodeweb-front 9.13.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.
@@ -81,6 +81,7 @@
81
81
  {
82
82
  response_function: () => {
83
83
  grid_scale.value = !grid_scale.value
84
+ hybridViewerStore.remoteRender()
84
85
  },
85
86
  },
86
87
  )
@@ -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
@@ -18,8 +18,9 @@ export function viewer_call(
18
18
  const client = viewer_store.client
19
19
 
20
20
  return new Promise((resolve, reject) => {
21
- if (!client) {
22
- reject()
21
+ if (!client.getConnection) {
22
+ resolve()
23
+ return
23
24
  }
24
25
  viewer_store.start_request()
25
26
  client
package/package.json CHANGED
@@ -39,11 +39,11 @@
39
39
  },
40
40
  "description": "OpenSource Vue/Nuxt/Pinia/Vuetify framework for web applications",
41
41
  "type": "module",
42
- "version": "9.13.1",
42
+ "version": "9.14.0-rc.1",
43
43
  "main": "./nuxt.config.js",
44
44
  "dependencies": {
45
- "@geode/opengeodeweb-back": "latest",
46
- "@geode/opengeodeweb-viewer": "latest",
45
+ "@geode/opengeodeweb-back": "next",
46
+ "@geode/opengeodeweb-viewer": "next",
47
47
  "@kitware/vtk.js": "33.3.0",
48
48
  "@mdi/font": "7.4.47",
49
49
  "@pinia/nuxt": "0.11.3",
@@ -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 save() {
18
+ function exportStores() {
21
19
  const snapshot = {}
22
- let savedCount = 0
20
+ let exportCount = 0
23
21
 
24
22
  for (const store of stores) {
25
- if (!store.save) {
26
- continue
27
- }
23
+ if (!store.exportStores) continue
28
24
  const storeId = store.$id
29
25
  try {
30
- snapshot[storeId] = store.save()
31
- savedCount++
26
+ snapshot[storeId] = store.exportStores()
27
+ exportCount++
32
28
  } catch (error) {
33
- console.error(`[AppStore] Error saving store "${storeId}":`, error)
29
+ console.error(`[AppStore] Error exporting store "${storeId}":`, error)
34
30
  }
35
31
  }
36
-
37
- console.log(`[AppStore] Saved ${savedCount} stores`)
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 load(snapshot) {
39
+ async function importStores(snapshot) {
42
40
  if (!snapshot) {
43
- console.warn("[AppStore] load called with invalid snapshot")
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 loadedCount = 0
46
+ let importedCount = 0
48
47
  const notFoundStores = []
49
-
50
48
  for (const store of stores) {
51
- if (!store.load) {
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.load(snapshot[storeId])
64
- loadedCount++
56
+ await store.importStores(snapshot[storeId])
57
+ importedCount++
65
58
  } catch (error) {
66
- console.error(`[AppStore] Error loading store "${storeId}":`, 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
- save,
83
- load,
73
+ exportStores,
74
+ importStores,
84
75
  }
85
76
  })
@@ -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
  })
@@ -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
  })
@@ -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
  })
@@ -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
  })
@@ -5,4 +5,4 @@
5
5
  # pip-compile --output-file=tests/integration/microservices/back/requirements.txt tests/integration/microservices/back/requirements.in
6
6
  #
7
7
 
8
- opengeodeweb-back==5.*,>=5.12.0
8
+ opengeodeweb-back==5.*,>=5.13.0rc1
@@ -5,4 +5,4 @@
5
5
  # pip-compile --output-file=tests/integration/microservices/viewer/requirements.txt tests/integration/microservices/viewer/requirements.in
6
6
  #
7
7
 
8
- opengeodeweb-viewer==1.*,>=1.11.9
8
+ opengeodeweb-viewer==1.*,>=1.12.0rc1
@@ -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/app_store.js"
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.save).toBeFunction()
20
- expectTypeOf(app_store.load).toBeFunction()
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: "cartStore",
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("cartStore")
59
+ expect(app_store.stores[1].$id).toBe("geodeStore")
60
60
  })
61
61
  })
62
62
 
63
- describe("save", () => {
64
- test("save stores with save method", () => {
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
- save: vi.fn().mockImplementation(() => ({
68
+ exportStores: vi.fn().mockImplementation(() => ({
69
69
  name: "toto",
70
70
  email: "toto@titi.com",
71
71
  })),
72
- load: vi.fn().mockImplementation(() => {}),
72
+ importStores: vi.fn().mockImplementation(() => {}),
73
73
  }
74
74
  const mock_store_2 = {
75
- $id: "cartStore",
76
- save: vi.fn().mockImplementation(() => ({ items: [], total: 0 })),
77
- load: vi.fn().mockImplementation(() => {}),
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.save()
85
+ const snapshot = app_store.exportStores()
84
86
 
85
- expect(mock_store_1.save).toHaveBeenCalledTimes(1)
86
- expect(mock_store_2.save).toHaveBeenCalledTimes(1)
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
- cartStore: { items: [], total: 0 },
91
+ geodeStore: { items: [], total: 0 },
90
92
  })
91
93
  })
92
94
 
93
- test("skip stores without save method", () => {
95
+ test("skip stores without exportSave method", () => {
94
96
  const app_store = useAppStore()
95
97
  const mock_store_1 = {
96
98
  $id: "withSave",
97
- save: vi.fn().mockImplementation(() => ({ data: "test" })),
98
- load: vi.fn().mockImplementation(() => {}),
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
- load: vi.fn().mockImplementation(() => {}),
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.save()
110
+ const snapshot = app_store.exportStores()
109
111
 
110
- expect(mock_store_1.save).toHaveBeenCalledTimes(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.save()
121
+ const snapshot = app_store.exportStores()
120
122
  expect(snapshot).toEqual({})
121
123
  })
122
124
  })
123
125
 
124
126
  describe("load", () => {
125
- test("load stores with load method", () => {
126
- const app_store = useAppStore()
127
- const mock_store_1 = {
127
+ test("App Store > actions > importStores > import stores with importStores method", async () => {
128
+ const appStore = useAppStore()
129
+ const userStore = {
128
130
  $id: "userStore",
129
- save: vi.fn().mockImplementation(() => {}),
130
- load: vi.fn().mockImplementation(() => {}),
131
+ importStores: vi.fn().mockResolvedValue(),
131
132
  }
132
- const mock_store_2 = {
133
- $id: "cartStore",
134
- save: vi.fn().mockImplementation(() => {}),
135
- load: vi.fn().mockImplementation(() => {}),
133
+ const geodeStore = {
134
+ $id: "geodeStore",
135
+ importStores: vi.fn().mockResolvedValue(),
136
136
  }
137
-
138
- app_store.registerStore(mock_store_1)
139
- app_store.registerStore(mock_store_2)
140
-
137
+ appStore.registerStore(userStore)
138
+ appStore.registerStore(geodeStore)
141
139
  const snapshot = {
142
- userStore: { name: "tata", email: "tata@tutu.com" },
143
- cartStore: { items: [{ id: 1 }], total: 50 },
140
+ userStore: { some: "data" },
141
+ geodeStore: { other: "data" },
144
142
  }
145
-
146
- app_store.load(snapshot)
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 load method", () => {
148
+ test("skip stores without importStores method", () => {
161
149
  const app_store = useAppStore()
162
150
  const mock_store_1 = {
163
- $id: "withLoad",
151
+ $id: "withImport",
164
152
  save: vi.fn().mockImplementation(() => {}),
165
- load: vi.fn().mockImplementation(() => {}),
153
+ importStores: vi.fn().mockImplementation(() => {}),
166
154
  }
167
155
  const mock_store_2 = {
168
- $id: "withoutLoad",
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
- withLoad: { data: "test" },
177
- withoutLoad: { data: "ignored" },
162
+ withImport: { data: "test" },
163
+ withoutImport: { data: "ignored" },
178
164
  }
179
-
180
- app_store.load(snapshot)
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
- load: vi.fn().mockImplementation(() => {}),
177
+ importStores: vi.fn().mockImplementation(() => {}),
194
178
  }
195
-
196
179
  app_store.registerStore(mock_store)
197
- app_store.load({})
198
-
180
+ app_store.importStores({})
199
181
  expect(console_warn_spy).toHaveBeenCalledWith(
200
182
  expect.stringContaining("Stores not found in snapshot: testStore"),
201
183
  )
@@ -16,9 +16,9 @@ const corners_defaultColor = { r: 20, g: 20, b: 20 }
16
16
  const lines_defaultVisibility = true
17
17
  const lines_defaultColor = { r: 20, g: 20, b: 20 }
18
18
  const surfaces_defaultVisibility = true
19
- const surfaces_defaultColor = { r: 20, g: 20, b: 20 }
19
+ const surfaces_defaultColor = { r: 255, g: 255, b: 255 }
20
20
  const blocks_defaultVisibility = true
21
- const blocks_defaultColor = { r: 20, g: 20, b: 20 }
21
+ const blocks_defaultColor = { r: 255, g: 255, b: 255 }
22
22
 
23
23
  // Mesh functions
24
24
  const meshPointsDefaultStyle = (
@@ -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
- async function importFile(filename, geode_object) {
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
- id,
35
- native_file_name,
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
- console.log("after treeviewStore.addItem")
82
+ async function importItemFromSnapshot(item) {
83
+ return importItem(item)
84
+ }
58
85
 
59
- await hybridViewerStore.addItem(id)
60
- console.log("after dataBaseStore.addItem")
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
- await dataStyleStore.addDataStyle(id, geode_object, object_type)
63
- console.log("after dataStyleStore.addDataStyle")
64
- if (object_type === "model") {
65
- await Promise.all([
66
- dataBaseStore.fetchMeshComponents(id),
67
- dataBaseStore.fetchUuidToFlatIndexDict(id),
68
- ])
69
- console.log("after dataBaseStore.fetchMeshComponents")
70
- console.log("after dataBaseStore.fetchUuidToFlatIndexDict")
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
- return id
105
+ console.log("[importWorkflowFromSnapshot] done", { ids })
106
+ return ids
76
107
  }
77
108
 
78
- export { importFile, importWorkflow }
109
+ export { importFile, importWorkflow, importWorkflowFromSnapshot }