@@ -39,11 +39,12 @@ import {
39
39
onMounted ,
40
40
ref ,
41
41
} from ' vue'
42
- import { useStore } from ' vuex'
43
- import { startCase , uniqueId } from ' lodash'
42
+ import { startCase , uniqueId } from ' lodash-es'
44
43
import WidgetComponent from ' @/components/cylc/workspace/Widget.vue'
45
- import LuminoWidget from ' @/components/cylc/workspace/lumino-widget '
44
+ import { LuminoWidget } from ' @/components/cylc/workspace/luminoWidget '
46
45
import { BoxPanel , DockPanel , Widget } from ' @lumino/widgets'
46
+ import { watchWithControl } from ' @/utils/reactivity'
47
+ import { replacer , reviver } from ' @/utils/json'
47
48
import { useDefaultView } from ' @/views/views'
48
49
import { eventBus } from ' @/services/eventBus'
49
50
@@ -66,8 +67,6 @@ import '@lumino/default-theme/style'
66
67
* @property {Record<string,*>} initialOptions - prop passed to the view component
67
68
*/
68
69
69
- const $store = useStore ()
70
-
71
70
const props = defineProps ({
72
71
workflowName: {
73
72
type: String ,
@@ -114,21 +113,35 @@ const resizeObserver = new ResizeObserver(() => {
114
113
boxPanel .update ()
115
114
})
116
115
117
- onMounted (() => {
116
+ const layoutsCache = window .caches .open (' workspace-layouts' )
117
+ const layoutWatcher = watchWithControl (views, saveLayout, { deep: true })
118
+
119
+ onMounted (async () => {
120
+ // Store any add-view events that occur before the layout is ready
121
+ // (e.g. when opening log view from command menu):
122
+ const bufferedAddViewEvents = []
123
+ eventBus .on (' add-view' , (e ) => bufferedAddViewEvents .push (e))
124
+
118
125
// Attach box panel to DOM:
119
126
Widget .attach (boxPanel, mainDiv .value )
120
127
// Watch for resize of the main element to trigger relayout:
121
128
resizeObserver .observe (mainDiv .value )
129
+
130
+ await getLayout ()
131
+ dockPanel .layoutModified .connect (() => layoutWatcher .trigger ())
132
+
133
+ eventBus .off (' add-view' )
122
134
eventBus .on (' add-view' , addView)
135
+ bufferedAddViewEvents .forEach ((e ) => addView (e))
123
136
eventBus .on (' lumino:deleted' , onWidgetDeleted)
124
- getLayout ( props . workflowName )
137
+ eventBus . on ( ' reset-workspace-layout ' , resetToDefault )
125
138
})
126
139
127
140
onBeforeUnmount (() => {
128
141
resizeObserver .disconnect ()
129
142
eventBus .off (' add-view' , addView)
130
143
eventBus .off (' lumino:deleted' , onWidgetDeleted)
131
- saveLayout ( )
144
+ eventBus . off ( ' reset-workspace-layout ' , resetToDefault )
132
145
// Register with Lumino that the dock panel is no longer used,
133
146
// otherwise uncaught errors can occur when restoring layout
134
147
dockPanel .dispose ()
@@ -140,71 +153,104 @@ onBeforeUnmount(() => {
140
153
* @param {AddViewEvent} event
141
154
* @param {boolean} onTop
142
155
*/
143
- const addView = ({ name, initialOptions = {} }, onTop = true ) => {
156
+ async function addView ({ name, initialOptions = {} }, onTop = true ) {
157
+ layoutWatcher .pause ()
144
158
const id = uniqueId (' widget' )
145
159
const luminoWidget = new LuminoWidget (id, startCase (name), /* closable */ true )
146
160
dockPanel .addWidget (luminoWidget, { mode: ' tab-after' })
161
+ if (onTop) {
162
+ dockPanel .selectWidget (luminoWidget)
163
+ }
147
164
// give time for Lumino's widget DOM element to be created
148
- nextTick (() => {
149
- views .value .set (id, { name, initialOptions })
150
- if (onTop) {
151
- dockPanel .selectWidget (luminoWidget)
152
- }
153
- })
165
+ await nextTick ()
166
+ views .value .set (id, { name, initialOptions })
167
+ layoutWatcher .resume ()
168
+ layoutWatcher .trigger ()
154
169
}
155
170
156
171
/**
157
172
* Remove all the widgets present in the DockPanel.
158
173
*/
159
- // (This is likely to be used in the future)
160
- // eslint-disable-next-line no-unused-vars
161
- const closeAllViews = () => {
174
+ async function closeAllViews () {
162
175
for (const widget of Array .from (dockPanel .widgets ())) {
163
176
widget .close ()
164
177
}
178
+ await nextTick ()
165
179
}
166
180
167
181
/**
168
- * Get the saved layout (if there is one) for the given workflow,
182
+ * Get the saved layout (if there is one) for the current workflow,
169
183
* else add the default view.
170
- *
171
- * @param {string} workflowName
172
184
*/
173
- const getLayout = ( workflowName ) => {
174
- restoreLayout (workflowName ) || addView ({ name: defaultView .value })
185
+ async function getLayout () {
186
+ await restoreLayout () || await addView ({ name: defaultView .value })
175
187
}
176
188
177
189
/**
178
- * Save the current layout/views to the store .
190
+ * Save the current layout/views to cache storage .
179
191
*/
180
- const saveLayout = () => {
181
- $store . commit ( ' app/saveLayout ' , {
182
- workflowName : props . workflowName ,
192
+ async function saveLayout () {
193
+ // Serialize layout first to synchronously capture the current state
194
+ const serializedLayout = JSON . stringify ({
183
195
layout: dockPanel .saveLayout (),
184
- views: new Map (views .value ),
185
- })
196
+ views: views .value ,
197
+ }, replacer)
198
+ const cache = await layoutsCache
199
+ // Overrides on FIFO basis:
200
+ await cache .put (props .workflowName , new Response (
201
+ serializedLayout,
202
+ { headers: { ' Content-Type' : ' application/json' } }
203
+ ))
204
+ const keys = await cache .keys ()
205
+ if (keys .length > 100 ) {
206
+ await cache .delete (keys[0 ])
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Return the saved layout for this workflow from cache storage, if it was saved.
212
+ */
213
+ async function getStoredLayout () {
214
+ const cache = await layoutsCache
215
+ const stored = await cache .match (props .workflowName ).then ((r ) => r? .text ())
216
+ if (stored) {
217
+ return JSON .parse (
218
+ stored,
219
+ (key , value ) => reviver (key, LuminoWidget .layoutReviver (key, value))
220
+ )
221
+ }
186
222
}
187
223
188
224
/**
189
- * Restore the layout for this workflow from the store , if it was saved.
225
+ * Restore the layout for this workflow, if it was saved.
190
226
*
191
- * @param {string} workflowName
192
- * @returns {boolean} true if the layout was restored, false otherwise
227
+ * @returns true if the layout was restored, false otherwise
193
228
*/
194
- const restoreLayout = ( workflowName ) => {
195
- const stored = $store . state . app . workspaceLayouts . get (workflowName )
229
+ async function restoreLayout () {
230
+ const stored = await getStoredLayout ( )
196
231
if (stored) {
232
+ layoutWatcher .pause ()
197
233
dockPanel .restoreLayout (stored .layout )
198
234
// Wait for next tick so that Lumino has created the widget divs that the
199
235
// views will be teleported into
200
- nextTick (() => {
201
- views .value = stored .views
202
- } )
236
+ await nextTick ()
237
+ views .value = stored .views
238
+ layoutWatcher . resume ( )
203
239
return true
204
240
}
205
241
return false
206
242
}
207
243
244
+ /**
245
+ * Reset the workspace layout to a single tab with the default view.
246
+ */
247
+ async function resetToDefault () {
248
+ layoutWatcher .pause ()
249
+ await closeAllViews ()
250
+ await addView ({ name: defaultView .value })
251
+ // (addView resumes the layout watcher)
252
+ }
253
+
208
254
/**
209
255
* React to a deleted event.
210
256
*
0 commit comments