@owdproject/core 3.1.4 → 3.3.0

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.
Files changed (64) hide show
  1. package/README.md +6 -6
  2. package/bin/cli.js +395 -0
  3. package/bin/desktop.js +5 -0
  4. package/bin/lib/catalog.js +293 -0
  5. package/bin/lib/config.js +109 -0
  6. package/bin/lib/desktopConfig.js +56 -0
  7. package/bin/lib/generateTemplate.js +389 -0
  8. package/bin/lib/install.js +408 -0
  9. package/bin/lib/migrateModulePlayground.config.mjs +164 -0
  10. package/bin/lib/migrateModulePlayground.mjs +313 -0
  11. package/bin/lib/npmVersions.js +124 -0
  12. package/bin/lib/playgroundContext.js +188 -0
  13. package/bin/lib/scaffold.js +197 -0
  14. package/bin/lib/status.js +289 -0
  15. package/bin/lib/validateModule.js +697 -0
  16. package/bin/lib/workspace.js +182 -0
  17. package/bin/owd.js +2 -25
  18. package/bin/tui.js +1379 -0
  19. package/dist/module.d.mts +13 -0
  20. package/dist/module.json +9 -0
  21. package/dist/module.mjs +267 -0
  22. package/dist/types.d.mts +7 -0
  23. package/module.ts +69 -32
  24. package/package.json +81 -22
  25. package/runtime/components/{Core/Application/CoreApplicationRender.vue → Desktop/Application/DesktopApplicationRender.vue} +4 -2
  26. package/runtime/components/Desktop/Application/DesktopApplicationWindowsRender.vue +38 -0
  27. package/runtime/components/{Core/Window/CoreWindow.vue → Desktop/Window/DesktopWindow.vue} +42 -8
  28. package/runtime/components/{Core/Window/CoreWindowNav.vue → Desktop/Window/DesktopWindowNav.vue} +26 -8
  29. package/runtime/composables/useApplicationManager.ts +19 -14
  30. package/runtime/composables/useDesktopManager.ts +33 -9
  31. package/runtime/composables/useWorkspaceManager.ts +41 -0
  32. package/runtime/{core → internal}/controllers/ApplicationController.ts +114 -24
  33. package/runtime/{core → internal}/controllers/WindowController.ts +4 -9
  34. package/runtime/plugins/02.desktop-register-desktop-apps.client.ts +17 -0
  35. package/runtime/stores/storeApplicationMeta.ts +56 -12
  36. package/runtime/stores/storeDesktop.ts +68 -2
  37. package/runtime/stores/storeDesktopDefaultApps.ts +2 -2
  38. package/runtime/stores/storeDesktopWorkspace.ts +14 -2
  39. package/runtime/utils/captureElementToCanvas.ts +21 -0
  40. package/runtime/utils/explorerEntryPath.ts +10 -0
  41. package/runtime/utils/resolveDesktopConfigPath.ts +52 -0
  42. package/runtime/utils/splitDesktopUserConfig.ts +85 -0
  43. package/runtime/utils/utilCommon.ts +0 -32
  44. package/runtime/utils/utilDesktop.ts +27 -2
  45. package/runtime/utils/validateDesktopUserConfig.ts +44 -0
  46. package/types/index.d.ts +45 -12
  47. package/CHANGELOG.md +0 -25
  48. package/project.json +0 -4
  49. package/runtime/components/Core/Application/CoreApplicationWindowsRender.vue +0 -19
  50. package/runtime/components/Core/Explorer/CoreExplorerFile.vue +0 -76
  51. package/runtime/components/Core/Explorer/CoreExplorerFolder.vue +0 -10
  52. package/runtime/stores/storeApplication.ts +0 -236
  53. package/test/basic.test.ts +0 -15
  54. package/test/fixtures/basic/app.vue +0 -5
  55. package/test/fixtures/basic/nuxt.config.ts +0 -5
  56. package/test/fixtures/basic/package.json +0 -5
  57. package/tsconfig.app.json +0 -31
  58. package/tsconfig.json +0 -12
  59. package/tsconfig.spec.json +0 -37
  60. package/vitest.config.ts +0 -24
  61. /package/runtime/components/{Core/Background/CoreBackground.vue → Desktop/Background/DesktopBackground.vue} +0 -0
  62. /package/runtime/components/{Core/Desktop/CoreDesktop.vue → Desktop/Core/DesktopCore.vue} +0 -0
  63. /package/runtime/components/{Core/Time/CoreTime.vue → Desktop/Time/DesktopTime.vue} +0 -0
  64. /package/runtime/components/{Core/Window/CoreWindowContent.vue → Desktop/Window/DesktopWindowContent.vue} +0 -0
@@ -2,14 +2,16 @@
2
2
  import { ref, provide } from 'vue'
3
3
  import { computed } from '@vue/reactivity'
4
4
  import { useAppConfig } from 'nuxt/app'
5
- import { useDesktopManager } from '@owdproject/core/runtime/composables/useDesktopManager'
6
5
  import { handleWindowControllerProps } from '@owdproject/core/runtime/utils/utilWindow'
6
+ import { useDesktopWorkspaceStore } from '../../../stores/storeDesktopWorkspace'
7
7
 
8
8
  const props = defineProps<{
9
9
  window?: IWindowController
10
10
  content?: WindowContent
11
11
  }>()
12
12
 
13
+ const desktopWorkspaceStore = useDesktopWorkspaceStore()
14
+
13
15
  const emit = defineEmits([
14
16
  'resize:start',
15
17
  'resize:move',
@@ -29,7 +31,6 @@ const emit = defineEmits([
29
31
  ])
30
32
 
31
33
  const appConfig = useAppConfig()
32
- const desktopManager = useDesktopManager() // used in <style v-bind>
33
34
  const windowController: IWindowController = handleWindowControllerProps(
34
35
  props.window,
35
36
  )
@@ -117,6 +118,36 @@ const classes = computed(() => {
117
118
 
118
119
  return list
119
120
  })
121
+
122
+ /** While workspace overview is open, disable vue-resizable chrome drag and use native HTML5 drag to move windows between desktops. */
123
+ const workspaceChromeDragSelector = computed(() => {
124
+ if (desktopWorkspaceStore.overview) {
125
+ return '.owd-window-overview-drag-none'
126
+ }
127
+ return windowController?.isDraggable
128
+ ? '.owd-window-nav__draggable'
129
+ : '.owd-window-nav__draggable-none'
130
+ })
131
+
132
+ function onOverviewNativeDragStart(e: DragEvent) {
133
+ if (!desktopWorkspaceStore.overview || !windowController) return
134
+ e.stopPropagation()
135
+ e.dataTransfer?.setData('text/plain', windowController.state.id)
136
+ e.dataTransfer!.effectAllowed = 'move'
137
+ try {
138
+ const img = new Image()
139
+ img.src =
140
+ 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
141
+ e.dataTransfer?.setDragImage(img, 0, 0)
142
+ } catch {
143
+ /* noop */
144
+ }
145
+ }
146
+
147
+ /** `fit-parent` clamps drag/resize to the parent box — wrong for viewport-fixed shells (Win11, GNOME). */
148
+ const shouldFitParent = computed(
149
+ () => appConfig.desktop.windows.position !== 'fixed',
150
+ )
120
151
  </script>
121
152
 
122
153
  <template>
@@ -132,7 +163,7 @@ const classes = computed(() => {
132
163
  :left="windowController?.position?.x"
133
164
  :top="windowController?.position?.y"
134
165
  :active="windowController?.isResizable ? undefined : []"
135
- fit-parent
166
+ :fit-parent="shouldFitParent"
136
167
  @drag:start="onWindowDragStart"
137
168
  @drag:move="onWindowDragMove"
138
169
  @drag:end="onWindowDragEnd"
@@ -140,18 +171,21 @@ const classes = computed(() => {
140
171
  @resize:move="onWindowResizeMove"
141
172
  @resize:end="onWindowResizeEnd"
142
173
  :style="{ zIndex: windowController?.position?.z }"
143
- :drag-selector="
144
- windowController?.isDraggable
145
- ? '.owd-window-nav__draggable'
146
- : '.owd-window-nav__draggable-none'
147
- "
174
+ :drag-selector="workspaceChromeDragSelector"
175
+ :draggable="desktopWorkspaceStore.overview"
148
176
  @pointerdown="onWindowPointerDown"
177
+ @dragstart="onOverviewNativeDragStart"
149
178
  >
150
179
  <slot />
151
180
  </vue-resizable>
152
181
  </template>
153
182
 
154
183
  <style scoped lang="scss">
184
+ /* vue-resizable sets `.resizable-component { position: relative }` — override so desktop geometry stays consistent */
185
+ .owd-window.resizable-component {
186
+ position: v-bind('appConfig.desktop.windows.position') !important;
187
+ }
188
+
155
189
  .owd-window {
156
190
  position: v-bind('appConfig.desktop.windows.position');
157
191
 
@@ -14,10 +14,7 @@ const classes = computed(() => {
14
14
  list.push('owd-window-nav--active')
15
15
  }
16
16
 
17
- if (
18
- typeof windowController?.state.focused === 'undefined' ||
19
- !!windowController?.state.focused
20
- ) {
17
+ if (windowController?.state.focused) {
21
18
  list.push('owd-window-nav--focused')
22
19
  }
23
20
 
@@ -34,15 +31,36 @@ const classes = computed(() => {
34
31
  </template>
35
32
 
36
33
  <style lang="scss">
34
+ /**
35
+ * Title/icon sit above the drag layer in DOM order; use pointer-events so
36
+ * mousedown reaches `.owd-window-nav__draggable` for vue-resizable. Window
37
+ * controls stay clickable.
38
+ */
37
39
  .owd-window-nav {
38
40
  position: relative;
41
+ display: flex;
42
+ align-items: stretch;
43
+ width: 100%;
44
+ min-height: 0;
39
45
 
40
46
  &__draggable {
41
47
  position: absolute;
42
- top: 0;
43
- left: 0;
44
- right: 0;
45
- height: 100%;
48
+ inset: 0;
49
+ z-index: 1;
50
+ }
51
+
52
+ > :not(.owd-window-nav__draggable) {
53
+ position: relative;
54
+ z-index: 2;
55
+ pointer-events: none;
56
+ }
57
+
58
+ > .owd-window-nav__btn-group {
59
+ pointer-events: auto;
60
+ }
61
+
62
+ > .owd-window-nav__interactive {
63
+ pointer-events: auto;
46
64
  }
47
65
  }
48
66
  </style>
@@ -1,5 +1,5 @@
1
1
  import { reactive, markRaw, computed } from 'vue'
2
- import { ApplicationController } from '../core/controllers/ApplicationController'
2
+ import { ApplicationController } from '../internal/controllers/ApplicationController'
3
3
  import { normalizeApplicationConfig } from '../utils/utilApp'
4
4
  import { debugLog } from '../utils/utilDebug'
5
5
  import * as shellwords from 'shellwords'
@@ -29,7 +29,7 @@ export function useApplicationManager() {
29
29
  const applicationController: IApplicationController = new ApplicationController(id, applicationConfig)
30
30
  await applicationController.initApplication()
31
31
 
32
- apps.set(id, applicationController)
32
+ apps.set(id, markRaw(applicationController))
33
33
 
34
34
  return applicationController
35
35
  }
@@ -184,11 +184,18 @@ export function useApplicationManager() {
184
184
  * Array of opened windows for system bars, docks
185
185
  */
186
186
  const windowsOpened = computed(() => {
187
- const windows: Map<string, IWindowController>[] = []
187
+ const windows = new Map<string, IWindowController>()
188
188
 
189
189
  for (const applicationController of apps.values()) {
190
- if (applicationController.isRunning) {
191
- windows.push(...applicationController.windows)
190
+ if (
191
+ !applicationController.isRunning &&
192
+ applicationController.openWindowCount.value === 0
193
+ ) {
194
+ continue
195
+ }
196
+
197
+ for (const [windowId, window] of applicationController.windows) {
198
+ windows.set(windowId, window)
192
199
  }
193
200
  }
194
201
 
@@ -202,7 +209,8 @@ export function useApplicationManager() {
202
209
  const runningApps: IApplicationController[] = []
203
210
 
204
211
  for (const applicationController of apps.values()) {
205
- if (applicationController.isRunning) {
212
+ const openCount = applicationController.openWindowCount.value
213
+ if (applicationController.isRunning || openCount > 0) {
206
214
  runningApps.push(applicationController)
207
215
  }
208
216
  }
@@ -211,13 +219,10 @@ export function useApplicationManager() {
211
219
  })
212
220
 
213
221
  function getWindowOpenedId(windowId: string) {
214
- const mapWindowFound = windowsOpened.value.find((window: any) => {
215
- return window[1].state.id === windowId
216
- })
217
-
218
- if (mapWindowFound) {
219
- // @ts-ignore
220
- return mapWindowFound[1] as IWindowController
222
+ for (const window of windowsOpened.value.values()) {
223
+ if (window.state.id === windowId) {
224
+ return window
225
+ }
221
226
  }
222
227
  }
223
228
 
@@ -262,7 +267,6 @@ export function useApplicationManager() {
262
267
  })
263
268
 
264
269
  return {
265
- apps,
266
270
  defineApp,
267
271
  isAppDefined,
268
272
  getAppById,
@@ -271,6 +275,7 @@ export function useApplicationManager() {
271
275
  execAppCommand,
272
276
  closeApp,
273
277
  getWindowOpenedId,
278
+ apps,
274
279
  appsEntries,
275
280
  appsRunning,
276
281
  windowsOpened,
@@ -1,25 +1,49 @@
1
- import { deepMerge } from '../utils/utilCommon'
1
+ import { computed } from 'vue'
2
2
  import { useAppConfig } from 'nuxt/app'
3
+ import { defu } from 'defu'
4
+ import { useDesktopStore } from '../stores/storeDesktop'
5
+ import { useDesktopDefaultAppsStore } from '../stores/storeDesktopDefaultApps'
3
6
 
4
7
  /**
5
- * Desktop Manager Composable (Singleton)
8
+ * Desktop Manager composable (singleton pattern via Pinia + app config).
9
+ * Agnostic of any theme: merges runtime shell config and exposes default-app routing.
6
10
  */
7
11
  export function useDesktopManager() {
12
+ const appConfig = useAppConfig()
13
+ const desktopStore = useDesktopStore()
14
+ const defaultAppsStore = useDesktopDefaultAppsStore()
15
+
8
16
  /**
9
- * Merges and sets the desktop config at runtime
10
- *
11
- * @param newConfig
17
+ * Merges and sets the desktop config at runtime (shell geometry, feature flags, etc.).
12
18
  */
13
19
  function setConfig(newConfig: DesktopConfig) {
14
- const appConfig = useAppConfig()
15
-
16
- appConfig.desktop = deepMerge(
17
- appConfig.desktop,
20
+ appConfig.desktop = defu(
18
21
  newConfig,
22
+ (appConfig.desktop ?? {}) as DesktopConfig,
19
23
  ) as DesktopConfig
20
24
  }
21
25
 
26
+ /** Map of feature name → default app handler (e.g. terminal, browser). */
27
+ const defaultApps = computed(
28
+ () => desktopStore.state.defaultApps,
29
+ )
30
+
31
+ function getDefaultApp(feature: string) {
32
+ return defaultAppsStore.getDefaultApp(feature)
33
+ }
34
+
35
+ function setDefaultApp(
36
+ feature: string,
37
+ application: IApplicationController,
38
+ entry: string,
39
+ ) {
40
+ defaultAppsStore.setDefaultApp(feature, application, entry)
41
+ }
42
+
22
43
  return {
23
44
  setConfig,
45
+ defaultApps,
46
+ getDefaultApp,
47
+ setDefaultApp,
24
48
  }
25
49
  }
@@ -0,0 +1,41 @@
1
+ import { useEventListener } from '@vueuse/core'
2
+ import { useApplicationManager } from './useApplicationManager'
3
+ import { useDesktopWorkspaceStore } from '../stores/storeDesktopWorkspace'
4
+
5
+ /**
6
+ * Keyboard + HTML5 drop handlers for workspace management (move windows between desktops).
7
+ */
8
+ export function useWorkspaceManager() {
9
+ const applicationManager = useApplicationManager()
10
+ const desktopWorkspaceStore = useDesktopWorkspaceStore()
11
+
12
+ useEventListener(window, 'keydown', (e: KeyboardEvent) => {
13
+ if (e.key !== 'Escape') return
14
+ if (!desktopWorkspaceStore.overview) return
15
+ desktopWorkspaceStore.setOverview(false)
16
+ })
17
+
18
+ function onWorkspaceDragOver(e: DragEvent) {
19
+ if (!desktopWorkspaceStore.overview) return
20
+ e.preventDefault()
21
+ }
22
+
23
+ function onWorkspaceDrop(e: DragEvent, workspaceId: string) {
24
+ e.preventDefault()
25
+ if (!desktopWorkspaceStore.overview) return
26
+
27
+ const raw =
28
+ e.dataTransfer?.getData('text/plain') ||
29
+ e.dataTransfer?.getData('text') ||
30
+ ''
31
+ if (!raw) return
32
+
33
+ const win = applicationManager.getWindowOpenedId(raw)
34
+ win?.actions.setWorkspace(workspaceId)
35
+ }
36
+
37
+ return {
38
+ onWorkspaceDragOver,
39
+ onWorkspaceDrop,
40
+ }
41
+ }
@@ -7,7 +7,9 @@ import { useTerminalManager } from '../../composables/useTerminalManager'
7
7
  import { debugLog, debugError } from '../../utils/utilDebug'
8
8
  import { useDesktopDefaultAppsStore } from '../../stores/storeDesktopDefaultApps'
9
9
  import { useDesktopWorkspaceStore } from '../../stores/storeDesktopWorkspace'
10
- import { reactive } from '@vue/reactivity'
10
+ import { useDesktopWindowStore } from '../../stores/storeDesktopWindow'
11
+ import { shallowRef } from 'vue'
12
+ import { unref } from 'vue'
11
13
 
12
14
  export class ApplicationController implements IApplicationController {
13
15
  private readonly applicationManager: IApplicationManager
@@ -16,9 +18,24 @@ export class ApplicationController implements IApplicationController {
16
18
  public readonly id
17
19
  public readonly config
18
20
  public readonly storeWindows
19
- public readonly storeMeta
20
21
 
21
- public windows = reactive(new Map<string, IWindowController>())
22
+ private readonly _windows = shallowRef(new Map<string, IWindowController>())
23
+
24
+ /** Reactive window count for Vue (class getters are not tracked). */
25
+ public readonly openWindowCount = shallowRef(0)
26
+
27
+ get windows(): Map<string, IWindowController> {
28
+ return this._windows.value
29
+ }
30
+
31
+ private replaceWindows(
32
+ mutator: (next: Map<string, IWindowController>) => void,
33
+ ) {
34
+ const next = new Map(this._windows.value)
35
+ mutator(next)
36
+ this._windows.value = next
37
+ this.openWindowCount.value = next.size
38
+ }
22
39
 
23
40
  public isRunning = false
24
41
 
@@ -29,7 +46,11 @@ export class ApplicationController implements IApplicationController {
29
46
  this.id = id
30
47
  this.config = config
31
48
  this.storeWindows = useApplicationWindowsStore(id)
32
- this.storeMeta = useApplicationMetaStore(id)
49
+ }
50
+
51
+ /** Lazy meta store — app modules should register state via `useApplicationMetaStore` before `defineDesktopApp`. */
52
+ get storeMeta() {
53
+ return useApplicationMetaStore(this.id)()
33
54
  }
34
55
 
35
56
  public async initApplication(): Promise<void> {
@@ -127,9 +148,32 @@ export class ApplicationController implements IApplicationController {
127
148
  }
128
149
  })
129
150
 
151
+ this.normalizeWindowFocus()
152
+
130
153
  debugLog('Windows have been restored', this.windows)
131
154
  }
132
155
 
156
+ /** Ensure a single focused window after restore or stale persisted state. */
157
+ private normalizeWindowFocus() {
158
+ if (this.windows.size === 0) {
159
+ return
160
+ }
161
+
162
+ let topWindow: IWindowController | undefined
163
+ let topZ = -Infinity
164
+
165
+ for (const window of this.windows.values()) {
166
+ window.setFocus(false)
167
+ const z = window.state.position?.z ?? 0
168
+ if (z >= topZ) {
169
+ topZ = z
170
+ topWindow = window
171
+ }
172
+ }
173
+
174
+ topWindow?.setFocus(true)
175
+ }
176
+
133
177
  public openWindow(
134
178
  model: string,
135
179
  windowStoredState: WindowStoredState | undefined,
@@ -142,6 +186,20 @@ export class ApplicationController implements IApplicationController {
142
186
  return
143
187
  }
144
188
 
189
+ if (!windowStoredState && this.config.singleton) {
190
+ const existing = this.getFirstWindowByModel(model)
191
+ if (existing) {
192
+ if (meta && typeof meta === 'object') {
193
+ Object.assign(existing.meta, meta)
194
+ }
195
+ if (!meta?.isRestoring) {
196
+ existing.actions.setActive(true)
197
+ existing.actions.bringToFront()
198
+ }
199
+ return existing
200
+ }
201
+ }
202
+
145
203
  let windowId: string
146
204
 
147
205
  if (!windowStoredState) {
@@ -151,32 +209,54 @@ export class ApplicationController implements IApplicationController {
151
209
  model
152
210
  ] as WindowConfig
153
211
  const screenHeight = window.innerHeight
154
- const centerY = (screenHeight - Number(windowConfig.size.height)) / 2
212
+ const configHeight = windowConfig.size?.height
213
+ const layoutHeight =
214
+ typeof configHeight === 'number'
215
+ ? configHeight
216
+ : typeof configHeight === 'string' && /^\d+$/.test(configHeight)
217
+ ? Number(configHeight)
218
+ : 240
219
+ const centerY = (screenHeight - layoutHeight) / 2
155
220
  const positionY =
156
221
  windowConfig.position?.y !== undefined
157
222
  ? window.scrollY + windowConfig.position.y
158
223
  : window.scrollY + centerY
159
-
160
- this.storeWindows.windows[windowId] = {
161
- model,
162
- state: {
163
- id: windowId,
164
- active: true,
165
- focused: false,
166
- position: {
167
- x: windowConfig.position?.x,
168
- y: positionY,
224
+ const cascadeStep = 40
225
+ const cascadeOffset = this.getWindowsByModel(model).length * cascadeStep
226
+ const baseX = windowConfig.position?.x ?? 100
227
+ const desktopWindowStore = useDesktopWindowStore()
228
+ const initialZ = desktopWindowStore.incrementPositionZ()
229
+
230
+ const nextWindows = {
231
+ ...this.storeWindows.windows,
232
+ [windowId]: {
233
+ model,
234
+ state: {
235
+ id: windowId,
236
+ active: true,
237
+ focused: false,
238
+ position: {
239
+ x: baseX + cascadeOffset,
240
+ y: positionY + cascadeOffset,
241
+ z: initialZ,
242
+ },
243
+ createdAt: +new Date(),
244
+ workspace: desktopWorkspaceStore.resolveActiveWorkspaceId(),
169
245
  },
170
- createdAt: +new Date(),
171
- workspace: desktopWorkspaceStore.active,
246
+ meta,
172
247
  },
173
- meta,
174
248
  }
249
+ this.storeWindows.windows = nextWindows
175
250
 
176
251
  windowStoredState = this.storeWindows.windows[windowId]
177
252
  } else {
178
253
  // restore previous id if state is defined
179
- windowId = windowStoredState.state.id
254
+ windowId =
255
+ windowStoredState.state?.id ??
256
+ Object.keys(this.storeWindows.windows).find(
257
+ (key) => this.storeWindows.windows[key] === windowStoredState,
258
+ ) ??
259
+ `${model}-restored`
180
260
  }
181
261
 
182
262
  const windowConfig = this.config.windows[model] as WindowConfig
@@ -192,7 +272,9 @@ export class ApplicationController implements IApplicationController {
192
272
  windowController.actions.bringToFront()
193
273
  }
194
274
 
195
- this.windows.set(windowId, windowController)
275
+ this.replaceWindows((next) => {
276
+ next.set(windowId, windowController)
277
+ })
196
278
 
197
279
  this.setRunning(true)
198
280
 
@@ -200,17 +282,25 @@ export class ApplicationController implements IApplicationController {
200
282
  }
201
283
 
202
284
  public closeWindow(windowId: string) {
203
- delete this.storeWindows.windows[windowId]
204
- this.windows.delete(windowId)
285
+ if (this.storeWindows.windows[windowId]) {
286
+ const nextWindows = { ...this.storeWindows.windows }
287
+ delete nextWindows[windowId]
288
+ this.storeWindows.windows = nextWindows
289
+ }
205
290
 
206
- if (this.windows.size === 0) {
291
+ this.replaceWindows((next) => {
292
+ next.delete(windowId)
293
+ })
294
+
295
+ if (this.openWindowCount.value === 0) {
207
296
  this.applicationManager.closeApp(this.id)
208
297
  }
209
298
  }
210
299
 
211
300
  public closeAllWindows() {
212
301
  this.storeWindows.windows = {}
213
- this.windows.clear()
302
+ this._windows.value = new Map()
303
+ this.openWindowCount.value = 0
214
304
  }
215
305
 
216
306
  get windowsOpened() {
@@ -202,18 +202,13 @@ export class WindowController implements IWindowController {
202
202
  this.state.position = { x: 0, y: 0, z: 0 }
203
203
  }
204
204
 
205
- // already focused
206
- if (this.state.focused) {
207
- return
208
- }
209
-
210
- // set focus false on all other windows
211
- for (const [windowId, window] of applicationManager.windowsOpened.value) {
212
- window.actions.setFocus(false)
205
+ for (const window of applicationManager.windowsOpened.value.values()) {
206
+ if (window !== this) {
207
+ window.setFocus(false)
208
+ }
213
209
  }
214
210
 
215
211
  this.setFocus(true)
216
-
217
212
  this.state.position.z = desktopWindowStore.incrementPositionZ()
218
213
  }
219
214
 
@@ -0,0 +1,17 @@
1
+ import { setActivePinia } from 'pinia'
2
+ import { defineNuxtPlugin } from 'nuxt/app'
3
+ import { flushPendingDesktopApps } from '../utils/utilDesktop'
4
+
5
+ export default defineNuxtPlugin({
6
+ name: 'desktop-register-desktop-apps',
7
+ dependsOn: ['pinia'],
8
+ enforce: 'post',
9
+ setup(nuxtApp) {
10
+ nuxtApp.hook('app:created', async () => {
11
+ if (nuxtApp.$pinia) {
12
+ setActivePinia(nuxtApp.$pinia)
13
+ }
14
+ await flushPendingDesktopApps()
15
+ })
16
+ },
17
+ })
@@ -1,15 +1,59 @@
1
1
  import { defineStore } from 'pinia'
2
+ import { ref } from 'vue'
2
3
 
3
- export const useApplicationMetaStore = (appId: string, states: any = {}) => {
4
- return defineStore(
5
- `owd/application/${appId}/meta`,
6
- () => {
7
- return states
8
- },
9
- {
10
- persistedState: {
11
- persist: true,
12
- },
13
- },
14
- )
4
+ const metaStoreRegistry = new Map<string, ReturnType<typeof defineStore>>()
5
+ /** Store ids created by the generic fallback (only `meta`), replaceable when an app registers real state. */
6
+ const fallbackMetaStoreIds = new Set<string>()
7
+
8
+ type MetaStoreSetup = (() => Record<string, unknown>) | Record<string, unknown>
9
+
10
+ function resolveSetup(states: MetaStoreSetup) {
11
+ return typeof states === 'function' ? states : () => states
12
+ }
13
+
14
+ /**
15
+ * Per-app Pinia meta store factory. The first registration for an app id wins so
16
+ * app modules should call this from their store module (imported in the app plugin
17
+ * before `defineDesktopApp`).
18
+ */
19
+ export const useApplicationMetaStore = (
20
+ appId: string,
21
+ states?: MetaStoreSetup,
22
+ ) => {
23
+ const id = `owd/application/${appId}/meta`
24
+
25
+ if (
26
+ states !== undefined &&
27
+ (!metaStoreRegistry.has(id) || fallbackMetaStoreIds.has(id))
28
+ ) {
29
+ fallbackMetaStoreIds.delete(id)
30
+ metaStoreRegistry.set(
31
+ id,
32
+ defineStore(id, resolveSetup(states), {
33
+ persistedState: {
34
+ persist: true,
35
+ },
36
+ }),
37
+ )
38
+ }
39
+
40
+ if (!metaStoreRegistry.has(id)) {
41
+ fallbackMetaStoreIds.add(id)
42
+ metaStoreRegistry.set(
43
+ id,
44
+ defineStore(
45
+ id,
46
+ () => ({
47
+ meta: ref<Record<string, unknown>>({}),
48
+ }),
49
+ {
50
+ persistedState: {
51
+ persist: true,
52
+ },
53
+ },
54
+ ),
55
+ )
56
+ }
57
+
58
+ return metaStoreRegistry.get(id)!
15
59
  }