Skip to content

Commit be6b88b

Browse files
authored
Merge pull request #249 from Geode-solutions/feat/save_and_load
feat(save_and_load): add import/export methods
2 parents b41144d + 941b3eb commit be6b88b

File tree

15 files changed

+824
-147
lines changed

15 files changed

+824
-147
lines changed

components/VeaseViewToolbar.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
{
8282
response_function: () => {
8383
grid_scale.value = !grid_scale.value
84+
hybridViewerStore.remoteRender()
8485
},
8586
},
8687
)

composables/project_manager.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

composables/viewer_call.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ export function viewer_call(
1818
const client = viewer_store.client
1919

2020
return new Promise((resolve, reject) => {
21-
if (!client) {
22-
reject()
21+
if (!client.getConnection) {
22+
resolve()
23+
return
2324
}
2425
viewer_store.start_request()
2526
client
File renamed without changes.

stores/app_store.js renamed to stores/app.js

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,81 +5,72 @@ export const useAppStore = defineStore("app", () => {
55
const isAlreadyRegistered = stores.some(
66
(registeredStore) => registeredStore.$id === store.$id,
77
)
8-
98
if (isAlreadyRegistered) {
109
console.log(
1110
`[AppStore] Store "${store.$id}" already registered, skipping`,
1211
)
1312
return
1413
}
15-
1614
console.log("[AppStore] Registering store", store.$id)
1715
stores.push(store)
1816
}
1917

20-
function save() {
18+
function exportStores() {
2119
const snapshot = {}
22-
let savedCount = 0
20+
let exportCount = 0
2321

2422
for (const store of stores) {
25-
if (!store.save) {
26-
continue
27-
}
23+
if (!store.exportStores) continue
2824
const storeId = store.$id
2925
try {
30-
snapshot[storeId] = store.save()
31-
savedCount++
26+
snapshot[storeId] = store.exportStores()
27+
exportCount++
3228
} catch (error) {
33-
console.error(`[AppStore] Error saving store "${storeId}":`, error)
29+
console.error(`[AppStore] Error exporting store "${storeId}":`, error)
3430
}
3531
}
36-
37-
console.log(`[AppStore] Saved ${savedCount} stores`)
32+
console.log(
33+
`[AppStore] Exported ${exportCount} stores; snapshot keys:`,
34+
Object.keys(snapshot),
35+
)
3836
return snapshot
3937
}
4038

41-
function load(snapshot) {
39+
async function importStores(snapshot) {
4240
if (!snapshot) {
43-
console.warn("[AppStore] load called with invalid snapshot")
41+
console.warn("[AppStore] import called with invalid snapshot")
4442
return
4543
}
44+
console.log("[AppStore] Import snapshot keys:", Object.keys(snapshot || {}))
4645

47-
let loadedCount = 0
46+
let importedCount = 0
4847
const notFoundStores = []
49-
5048
for (const store of stores) {
51-
if (!store.load) {
52-
continue
53-
}
54-
49+
if (!store.importStores) continue
5550
const storeId = store.$id
56-
5751
if (!snapshot[storeId]) {
5852
notFoundStores.push(storeId)
5953
continue
6054
}
61-
6255
try {
63-
store.load(snapshot[storeId])
64-
loadedCount++
56+
await store.importStores(snapshot[storeId])
57+
importedCount++
6558
} catch (error) {
66-
console.error(`[AppStore] Error loading store "${storeId}":`, error)
59+
console.error(`[AppStore] Error importing store "${storeId}":`, error)
6760
}
6861
}
69-
7062
if (notFoundStores.length > 0) {
7163
console.warn(
7264
`[AppStore] Stores not found in snapshot: ${notFoundStores.join(", ")}`,
7365
)
7466
}
75-
76-
console.log(`[AppStore] Loaded ${loadedCount} stores`)
67+
console.log(`[AppStore] Imported ${importedCount} stores`)
7768
}
7869

7970
return {
8071
stores,
8172
registerStore,
82-
save,
83-
load,
73+
exportStores,
74+
importStores,
8475
}
8576
})

stores/data_base.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,43 @@ export const useDataBaseStore = defineStore("dataBase", () => {
120120
return flat_indexes
121121
}
122122

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+
123160
return {
124161
db,
125162
itemMetaDatas,
@@ -134,5 +171,8 @@ export const useDataBaseStore = defineStore("dataBase", () => {
134171
getSurfacesUuids,
135172
getBlocksUuids,
136173
getFlatIndexes,
174+
exportStores,
175+
importStores,
176+
clear,
137177
}
138178
})

stores/data_style.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getDefaultStyle } from "../utils/default_styles.js"
12
import useDataStyleState from "../internal_stores/data_style_state.js"
23
import useMeshStyle from "../internal_stores/mesh/index.js"
34
import useModelStyle from "../internal_stores/model/index.js"
@@ -7,6 +8,7 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
78
const meshStyleStore = useMeshStyle()
89
const modelStyleStore = useModelStyle()
910
const dataBaseStore = useDataBaseStore()
11+
const hybridViewerStore = useHybridViewerStore()
1012

1113
function addDataStyle(id, geode_object) {
1214
dataStyleState.styles[id] = getDefaultStyle(geode_object)
@@ -37,12 +39,45 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
3739
}
3840
}
3941

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+
4072
return {
4173
...dataStyleState,
4274
...meshStyleStore,
4375
...modelStyleStore,
4476
addDataStyle,
4577
applyDefaultStyle,
4678
setVisibility,
79+
exportStores,
80+
importStores,
81+
applyAllStylesFromState,
4782
}
4883
})

0 commit comments

Comments
 (0)