@owdproject/core 3.3.0 → 3.4.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 (92) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/DESKTOP_KERNEL.md +232 -0
  3. package/MIGRATION_3.3.2.md +23 -0
  4. package/MIGRATION_3.4.md +126 -0
  5. package/PLAYGROUND.md +91 -0
  6. package/README.md +13 -80
  7. package/dist/module.d.mts +2 -2
  8. package/dist/module.json +3 -3
  9. package/dist/module.mjs +86 -99
  10. package/index.ts +6 -3
  11. package/kit/authoring.ts +340 -0
  12. package/kit/defineDesktopApp.ts +56 -0
  13. package/module.ts +76 -108
  14. package/package.json +70 -40
  15. package/runtime/components/{Desktop/Application/DesktopApplicationRender.vue → Application/DesktopCoreApplicationRender.vue} +2 -2
  16. package/runtime/components/{Desktop/Application/DesktopApplicationWindowsRender.vue → Application/DesktopCoreApplicationWindowsRender.vue} +1 -1
  17. package/runtime/components/core/DesktopCore.vue +43 -0
  18. package/runtime/components/{Desktop/Window/DesktopWindow.vue → window/DesktopCoreWindow.vue} +17 -6
  19. package/runtime/components/window/DesktopCoreWindowSnapHintsBase.vue +74 -0
  20. package/runtime/components/workspace/DesktopCoreWorkspaceEdgeHintsBase.vue +84 -0
  21. package/runtime/composables/useBlockNonInputContextMenu.ts +30 -0
  22. package/runtime/composables/useDesktopConfig.ts +8 -0
  23. package/runtime/composables/useDesktopDialogs.ts +16 -0
  24. package/runtime/composables/useDesktopExtension.ts +8 -0
  25. package/runtime/composables/useDesktopManager.ts +2 -0
  26. package/runtime/composables/useDesktopManifest.ts +17 -0
  27. package/runtime/composables/useDesktopSession.ts +27 -0
  28. package/runtime/composables/useDesktopShellIdentity.ts +99 -0
  29. package/runtime/composables/useDesktopShellOptions.ts +36 -0
  30. package/runtime/composables/useDesktopShellReady.ts +7 -0
  31. package/runtime/composables/useDesktopWorkArea.ts +93 -0
  32. package/runtime/composables/useTerminalManager.ts +102 -9
  33. package/runtime/composables/useToggleWindowMaximize.ts +35 -0
  34. package/runtime/composables/useWindowDragHandlers.ts +47 -0
  35. package/runtime/composables/useWindowSnapDrop.ts +112 -0
  36. package/runtime/composables/useWorkspaceEdgeDrop.ts +176 -0
  37. package/runtime/composables/useWorkspaceManager.ts +26 -0
  38. package/runtime/composables/useWorkspaceOverviewCapture.ts +92 -0
  39. package/runtime/composables/useWorkspaceOverviewLiveScale.ts +151 -0
  40. package/runtime/constants/desktopShellKeys.ts +12 -0
  41. package/runtime/constants/shellLayout.ts +5 -0
  42. package/runtime/dialogs/desktopDialogProvider.ts +73 -0
  43. package/runtime/internal/controllers/ApplicationController.ts +8 -2
  44. package/runtime/internal/controllers/WindowController.ts +93 -8
  45. package/runtime/plugins/01.desktop-shell-init.client.ts +28 -0
  46. package/runtime/plugins/02.desktop-register-desktop-apps.client.ts +2 -6
  47. package/runtime/stores/storeApplicationMeta.ts +2 -1
  48. package/runtime/stores/storeApplicationWindows.ts +2 -1
  49. package/runtime/stores/storeDesktop.ts +17 -57
  50. package/runtime/stores/storeDesktopDefaultApps.ts +2 -1
  51. package/runtime/stores/storeDesktopVolume.ts +2 -1
  52. package/runtime/stores/storeDesktopWindow.ts +20 -2
  53. package/runtime/stores/storeDesktopWorkspace.ts +31 -1
  54. package/runtime/stores/storeIds.ts +35 -0
  55. package/runtime/types/desktopMenu.ts +10 -0
  56. package/runtime/utils/initDesktopShell.ts +80 -0
  57. package/runtime/utils/migratePersistedStoreIds.ts +57 -0
  58. package/runtime/utils/utilApp.ts +0 -12
  59. package/runtime/utils/{captureElementToCanvas.ts → utilCaptureElementToCanvas.ts} +1 -1
  60. package/runtime/utils/utilDetectSnapZone.ts +57 -0
  61. package/runtime/utils/utilWindowLayout.ts +82 -0
  62. package/runtime/utils/utilWindowMaximizeLayout.ts +44 -0
  63. package/runtime/utils/utilWorkspaceWindows.ts +33 -0
  64. package/types/index.d.ts +125 -14
  65. package/bin/cli.js +0 -395
  66. package/bin/desktop.js +0 -5
  67. package/bin/lib/catalog.js +0 -293
  68. package/bin/lib/config.js +0 -109
  69. package/bin/lib/desktopConfig.js +0 -56
  70. package/bin/lib/generateTemplate.js +0 -389
  71. package/bin/lib/install.js +0 -408
  72. package/bin/lib/migrateModulePlayground.config.mjs +0 -164
  73. package/bin/lib/migrateModulePlayground.mjs +0 -313
  74. package/bin/lib/npmVersions.js +0 -124
  75. package/bin/lib/playgroundContext.js +0 -188
  76. package/bin/lib/scaffold.js +0 -197
  77. package/bin/lib/status.js +0 -289
  78. package/bin/lib/validateModule.js +0 -697
  79. package/bin/lib/workspace.js +0 -182
  80. package/bin/owd.js +0 -5
  81. package/bin/tui.js +0 -1379
  82. package/runtime/components/Desktop/Core/DesktopCore.vue +0 -90
  83. package/runtime/utils/explorerEntryPath.ts +0 -10
  84. package/runtime/utils/resolveDesktopConfigPath.ts +0 -52
  85. package/runtime/utils/splitDesktopUserConfig.ts +0 -85
  86. package/runtime/utils/utilDesktop.ts +0 -35
  87. package/runtime/utils/validateDesktopUserConfig.ts +0 -44
  88. /package/runtime/components/{Desktop/Background/DesktopBackground.vue → background/DesktopCoreBackground.vue} +0 -0
  89. /package/runtime/components/{Desktop/Time/DesktopTime.vue → time/DesktopCoreTime.vue} +0 -0
  90. /package/runtime/components/{Desktop/Window/DesktopWindowContent.vue → window/DesktopCoreWindowContent.vue} +0 -0
  91. /package/runtime/components/{Desktop/Window/DesktopWindowNav.vue → window/DesktopCoreWindowNav.vue} +0 -0
  92. /package/runtime/utils/{utilWindow.ts → utilWindowControllerAdapter.ts} +0 -0
@@ -0,0 +1,92 @@
1
+ import { useDebounceFn } from '@vueuse/core'
2
+ import { nextTick, ref, shallowRef, watch } from 'vue'
3
+ import { useDesktopWorkspaceStore } from '@owdproject/core/runtime/stores/storeDesktopWorkspace'
4
+ import { captureElementToCanvas } from '../utils/utilCaptureElementToCanvas'
5
+
6
+ export type WorkspaceRootResolver = (
7
+ workspaceId: string,
8
+ ) => HTMLElement | null | undefined
9
+
10
+ export type UseWorkspaceOverviewCaptureOptions = {
11
+ /** Delay before capturing after overview opens (ms). Default 150. */
12
+ debounceMs?: number
13
+ /** JPEG quality for thumbnail data URLs (0–1). Default 0.7. */
14
+ jpegQuality?: number
15
+ /** Passed through to html2canvas. */
16
+ captureOptions?: Record<string, unknown>
17
+ }
18
+
19
+ /**
20
+ * Captures workspace desktop roots as JPEG data URLs while workspace overview is open.
21
+ * Themes supply a resolver that maps each workspace id to its DOM root (e.g. a layer wrapping
22
+ * {@link DesktopApplicationRender} with `workspace-filter`).
23
+ */
24
+ export function useWorkspaceOverviewCapture(
25
+ resolveWorkspaceRoot: WorkspaceRootResolver,
26
+ options: UseWorkspaceOverviewCaptureOptions = {},
27
+ ) {
28
+ const desktopWorkspaceStore = useDesktopWorkspaceStore()
29
+ const thumbnails = shallowRef(new Map<string, string>())
30
+ const isCapturing = ref(false)
31
+
32
+ const debounceMs = options.debounceMs ?? 150
33
+ const jpegQuality = options.jpegQuality ?? 0.7
34
+
35
+ async function captureWorkspace(workspaceId: string): Promise<string | null> {
36
+ const element = resolveWorkspaceRoot(workspaceId)
37
+ const canvas = await captureElementToCanvas(element, options.captureOptions)
38
+ if (!canvas) return null
39
+ return canvas.toDataURL('image/jpeg', jpegQuality)
40
+ }
41
+
42
+ async function refreshThumbnails() {
43
+ if (!desktopWorkspaceStore.overview) return
44
+
45
+ isCapturing.value = true
46
+ const next = new Map<string, string>()
47
+
48
+ try {
49
+ await nextTick()
50
+ const ids = [...desktopWorkspaceStore.list]
51
+ await Promise.all(
52
+ ids.map(async (id) => {
53
+ const dataUrl = await captureWorkspace(id)
54
+ if (dataUrl) next.set(id, dataUrl)
55
+ }),
56
+ )
57
+ thumbnails.value = next
58
+ } finally {
59
+ isCapturing.value = false
60
+ }
61
+ }
62
+
63
+ const scheduleRefresh = useDebounceFn(() => {
64
+ void refreshThumbnails()
65
+ }, debounceMs)
66
+
67
+ watch(
68
+ () => desktopWorkspaceStore.overview,
69
+ (open) => {
70
+ if (open) scheduleRefresh()
71
+ else thumbnails.value = new Map()
72
+ },
73
+ )
74
+
75
+ watch(
76
+ () => desktopWorkspaceStore.list.join('\0'),
77
+ () => {
78
+ if (desktopWorkspaceStore.overview) scheduleRefresh()
79
+ },
80
+ )
81
+
82
+ function thumbnailFor(workspaceId: string): string | undefined {
83
+ return thumbnails.value.get(workspaceId)
84
+ }
85
+
86
+ return {
87
+ thumbnails,
88
+ isCapturing,
89
+ refreshThumbnails,
90
+ thumbnailFor,
91
+ }
92
+ }
@@ -0,0 +1,151 @@
1
+ import type { CSSProperties, Ref } from 'vue'
2
+ import {
3
+ nextTick,
4
+ onScopeDispose,
5
+ readonly,
6
+ shallowRef,
7
+ watch,
8
+ } from 'vue'
9
+ import { useDesktopWorkspaceStore } from '@owdproject/core/runtime/stores/storeDesktopWorkspace'
10
+
11
+ export type WorkspaceStageSize = {
12
+ width: number
13
+ height: number
14
+ }
15
+
16
+ export type WorkspaceOverviewLiveScaleOptions = {
17
+ /** Upper bound for scale (default 1). */
18
+ maxScale?: number
19
+ }
20
+
21
+ export type WorkspaceOverviewInnerStyleOptions =
22
+ WorkspaceOverviewLiveScaleOptions & {
23
+ transformOrigin?: string
24
+ }
25
+
26
+ /**
27
+ * Fit factor to show a 1:1 shell stage inside a panel viewport without clipping.
28
+ */
29
+ export function computeScaleToFit(
30
+ panelWidth: number,
31
+ panelHeight: number,
32
+ stageWidth: number,
33
+ stageHeight: number,
34
+ options?: WorkspaceOverviewLiveScaleOptions,
35
+ ): number {
36
+ if (
37
+ panelWidth <= 0 ||
38
+ panelHeight <= 0 ||
39
+ stageWidth <= 0 ||
40
+ stageHeight <= 0
41
+ ) {
42
+ return 1
43
+ }
44
+
45
+ const scale = Math.min(panelWidth / stageWidth, panelHeight / stageHeight)
46
+ const maxScale = options?.maxScale ?? 1
47
+ return Math.min(scale, maxScale)
48
+ }
49
+
50
+ /**
51
+ * Live DOM workspace overview: measure shell stage, scale desktop roots to fit cards.
52
+ */
53
+ export function useWorkspaceOverviewLiveScale(
54
+ shellStageRef: Ref<HTMLElement | null | undefined>,
55
+ ) {
56
+ const desktopWorkspaceStore = useDesktopWorkspaceStore()
57
+ const stageSize = shallowRef<WorkspaceStageSize>({ width: 0, height: 0 })
58
+
59
+ let resizeObserver: ResizeObserver | null = null
60
+
61
+ function readStageSize() {
62
+ const el = shellStageRef.value
63
+ if (!el) {
64
+ stageSize.value = { width: 0, height: 0 }
65
+ return
66
+ }
67
+ stageSize.value = {
68
+ width: el.clientWidth,
69
+ height: el.clientHeight,
70
+ }
71
+ }
72
+
73
+ function stopObserving() {
74
+ resizeObserver?.disconnect()
75
+ resizeObserver = null
76
+ }
77
+
78
+ function startObserving() {
79
+ stopObserving()
80
+ const el = shellStageRef.value
81
+ if (!el || typeof ResizeObserver === 'undefined') return
82
+
83
+ readStageSize()
84
+ resizeObserver = new ResizeObserver(() => readStageSize())
85
+ resizeObserver.observe(el)
86
+ }
87
+
88
+ watch(
89
+ () => desktopWorkspaceStore.overview,
90
+ (open) => {
91
+ if (open) {
92
+ void nextTick(() => startObserving())
93
+ } else {
94
+ stopObserving()
95
+ stageSize.value = { width: 0, height: 0 }
96
+ }
97
+ },
98
+ )
99
+
100
+ watch(shellStageRef, (el) => {
101
+ if (desktopWorkspaceStore.overview && el) {
102
+ void nextTick(() => startObserving())
103
+ }
104
+ })
105
+
106
+ onScopeDispose(stopObserving)
107
+
108
+ function scaleToFit(
109
+ panelEl: HTMLElement | null | undefined,
110
+ options?: WorkspaceOverviewLiveScaleOptions,
111
+ ): number {
112
+ if (!panelEl) return 1
113
+ const { width: stageWidth, height: stageHeight } = stageSize.value
114
+ if (stageWidth <= 0 || stageHeight <= 0) return 1
115
+
116
+ return computeScaleToFit(
117
+ panelEl.clientWidth,
118
+ panelEl.clientHeight,
119
+ stageWidth,
120
+ stageHeight,
121
+ options,
122
+ )
123
+ }
124
+
125
+ function innerOverviewStyle(
126
+ panelEl: HTMLElement | null | undefined,
127
+ options?: WorkspaceOverviewInnerStyleOptions,
128
+ ): CSSProperties | undefined {
129
+ if (!desktopWorkspaceStore.overview) return undefined
130
+
131
+ const { width: stageWidth, height: stageHeight } = stageSize.value
132
+ if (stageWidth <= 0 || stageHeight <= 0) return undefined
133
+
134
+ const scale = scaleToFit(panelEl, options)
135
+ const transformOrigin = options?.transformOrigin ?? 'top left'
136
+
137
+ return {
138
+ width: `${stageWidth}px`,
139
+ height: `${stageHeight}px`,
140
+ transform: `scale(${scale})`,
141
+ transformOrigin,
142
+ }
143
+ }
144
+
145
+ return {
146
+ stageSize: readonly(stageSize),
147
+ scaleToFit,
148
+ innerOverviewStyle,
149
+ refreshStageSize: readStageSize,
150
+ }
151
+ }
@@ -0,0 +1,12 @@
1
+ import type { InjectionKey } from 'vue'
2
+ import type { DesktopDialogProvider } from '../dialogs/desktopDialogProvider'
3
+
4
+ export type DesktopToggleWindowMaximizeFn = (
5
+ win: IWindowController | undefined,
6
+ ) => boolean
7
+
8
+ export const DESKTOP_DIALOG_PROVIDER_KEY: InjectionKey<DesktopDialogProvider> =
9
+ Symbol('desktop-dialog-provider')
10
+
11
+ export const DESKTOP_TOGGLE_WINDOW_MAXIMIZE_KEY: InjectionKey<DesktopToggleWindowMaximizeFn> =
12
+ Symbol('desktop-toggle-window-maximize')
@@ -0,0 +1,5 @@
1
+ /** Viewport band (px) where a window drag targets the adjacent workspace. */
2
+ export const WORKSPACE_EDGE_DROP_PX = 72
3
+
4
+ /** Viewport band (px) where a window drag targets a snap zone. */
5
+ export const WINDOW_SNAP_EDGE_PX = 14
@@ -0,0 +1,73 @@
1
+ /** Options for a yes/no confirmation (theme renders labels and chrome). */
2
+ export interface DesktopConfirmDialogOptions {
3
+ title?: string
4
+ message: string
5
+ danger?: boolean
6
+ acceptLabel?: string
7
+ rejectLabel?: string
8
+ /**
9
+ * Theme-specific payload (e.g. delete dialog passes `toTrash` for slot templates).
10
+ */
11
+ extras?: Record<string, unknown>
12
+ }
13
+
14
+ /**
15
+ * Cross-theme dialog API. Themes implement with PrimeVue, native dialogs, etc.
16
+ * Callers pass user-facing strings (usually from i18n); implementations only render.
17
+ */
18
+ export interface DesktopDialogProvider {
19
+ confirm(options: DesktopConfirmDialogOptions): Promise<boolean>
20
+ alert(message: string, options?: { title?: string }): Promise<void>
21
+ prompt(message: string, defaultValue?: string): Promise<string | null>
22
+ }
23
+
24
+ function browserGlobals(): {
25
+ confirm: (message?: string) => boolean
26
+ alert: (message?: string) => void
27
+ prompt: (message?: string, defaultText?: string) => string | null
28
+ } | null {
29
+ const g = globalThis as typeof globalThis & {
30
+ confirm?: (message?: string) => boolean
31
+ alert?: (message?: string) => void
32
+ prompt?: (message?: string, defaultText?: string) => string | null
33
+ }
34
+ if (typeof g.confirm !== 'function') return null
35
+ return {
36
+ confirm: g.confirm.bind(g),
37
+ alert:
38
+ typeof g.alert === 'function'
39
+ ? g.alert.bind(g)
40
+ : (m?: string) => {
41
+ void m
42
+ },
43
+ prompt:
44
+ typeof g.prompt === 'function'
45
+ ? g.prompt.bind(g)
46
+ : () => null,
47
+ }
48
+ }
49
+
50
+ export function createBrowserFallbackDialogProvider(): DesktopDialogProvider {
51
+ return {
52
+ async confirm(opts) {
53
+ if (import.meta.server) return false
54
+ const w = browserGlobals()
55
+ if (!w) return false
56
+ const text = [opts.title, opts.message].filter(Boolean).join('\n\n')
57
+ return w.confirm(text)
58
+ },
59
+ async alert(message, opts) {
60
+ if (import.meta.server) return
61
+ const w = browserGlobals()
62
+ if (!w) return
63
+ w.alert([opts?.title, message].filter(Boolean).join('\n\n'))
64
+ },
65
+ async prompt(message, defaultValue = '') {
66
+ if (import.meta.server) return null
67
+ const w = browserGlobals()
68
+ if (!w) return null
69
+ const v = w.prompt(message, defaultValue)
70
+ return v === null ? null : v
71
+ },
72
+ }
73
+ }
@@ -82,9 +82,11 @@ export class ApplicationController implements IApplicationController {
82
82
 
83
83
  if (this.config.commands) {
84
84
  for (const commandKey of Object.keys(this.config.commands)) {
85
+ const meta = this.config.terminal?.[commandKey] ?? {}
85
86
  this.terminalManager.addCommand({
86
87
  applicationId: this.id,
87
88
  name: commandKey,
89
+ ...meta,
88
90
  })
89
91
  }
90
92
  }
@@ -155,14 +157,18 @@ export class ApplicationController implements IApplicationController {
155
157
 
156
158
  /** Ensure a single focused window after restore or stale persisted state. */
157
159
  private normalizeWindowFocus() {
158
- if (this.windows.size === 0) {
160
+ const allWindows = this.applicationManager?.windowsOpened?.value
161
+ ? Array.from(this.applicationManager.windowsOpened.value.values())
162
+ : Array.from(this.windows.values())
163
+
164
+ if (allWindows.length === 0) {
159
165
  return
160
166
  }
161
167
 
162
168
  let topWindow: IWindowController | undefined
163
169
  let topZ = -Infinity
164
170
 
165
- for (const window of this.windows.values()) {
171
+ for (const window of allWindows) {
166
172
  window.setFocus(false)
167
173
  const z = window.state.position?.z ?? 0
168
174
  if (z >= topZ) {
@@ -1,7 +1,7 @@
1
1
  import { useApplicationManager } from '../../composables/useApplicationManager'
2
2
  import { useDesktopWindowStore } from '../../stores/storeDesktopWindow'
3
3
  import { deepClone } from '../../utils/utilCommon'
4
- import { markRaw } from '@vue/reactivity'
4
+ import { markRaw, reactive } from '@vue/reactivity'
5
5
  import { defineAsyncComponent } from 'vue'
6
6
 
7
7
  export class WindowController implements IWindowController {
@@ -46,7 +46,7 @@ export class WindowController implements IWindowController {
46
46
  resizable: true,
47
47
  }
48
48
 
49
- public override: WindowOverride = {}
49
+ public override: WindowOverride = reactive({})
50
50
 
51
51
  private storedState: WindowStoredState
52
52
 
@@ -226,6 +226,14 @@ export class WindowController implements IWindowController {
226
226
  return this.application.config.title
227
227
  }
228
228
 
229
+ get windowTitle(): string {
230
+ if (typeof this.override.windowTitle !== 'undefined') {
231
+ return this.override.windowTitle
232
+ }
233
+
234
+ return this.title
235
+ }
236
+
229
237
  get icon() {
230
238
  if (typeof this.override.icon !== 'undefined') {
231
239
  return this.override.icon
@@ -331,6 +339,10 @@ export class WindowController implements IWindowController {
331
339
  }
332
340
 
333
341
  get isMaximized() {
342
+ if (this.state.layout === 'maximize') {
343
+ return true
344
+ }
345
+
334
346
  if (this.config.overridable?.maximized) {
335
347
  return !!this.state.maximized
336
348
  }
@@ -338,22 +350,81 @@ export class WindowController implements IWindowController {
338
350
  return !!this.config.maximized
339
351
  }
340
352
 
341
- public toggleMaximize() {
353
+ private parseSizeValue(value: WindowSizeValue | undefined, fallback: number): number {
354
+ if (typeof value === 'number') {
355
+ return value
356
+ }
357
+ if (typeof value === 'string') {
358
+ const parsed = Number.parseFloat(value)
359
+ if (!Number.isNaN(parsed)) {
360
+ return parsed
361
+ }
362
+ }
363
+ return fallback
364
+ }
365
+
366
+ public saveBoundsBeforeLayout() {
367
+ if (this.state.layout && this.state.layout !== 'normal') {
368
+ return
369
+ }
370
+
371
+ const pos = this.position
372
+ const size = this.size
373
+
374
+ this.state.boundsBeforeLayout = {
375
+ x: pos?.x ?? 0,
376
+ y: pos?.y ?? 0,
377
+ width: this.parseSizeValue(size.width, 400),
378
+ height: this.parseSizeValue(size.height, 300),
379
+ }
380
+ }
381
+
382
+ public setLayout(layout: WindowLayout) {
383
+ if (layout === 'normal') {
384
+ return this.clearLayout()
385
+ }
386
+
342
387
  if (!this.isMaximizable) {
343
388
  return false
344
389
  }
345
390
 
346
- this.state.maximized = !this.state.maximized
391
+ this.saveBoundsBeforeLayout()
392
+ this.state.layout = layout
393
+ this.state.maximized = layout === 'maximize'
347
394
  return true
348
395
  }
349
396
 
397
+ public clearLayout() {
398
+ const bounds = this.state.boundsBeforeLayout
399
+ if (bounds) {
400
+ this.setPosition({ x: bounds.x, y: bounds.y })
401
+ this.setSize({ width: bounds.width, height: bounds.height })
402
+ delete this.state.boundsBeforeLayout
403
+ }
404
+
405
+ this.state.layout = 'normal'
406
+ this.state.maximized = false
407
+ return true
408
+ }
409
+
410
+ public toggleMaximize() {
411
+ if (!this.isMaximizable) {
412
+ return false
413
+ }
414
+
415
+ if (this.isMaximized) {
416
+ return this.clearLayout()
417
+ }
418
+
419
+ return this.setLayout('maximize')
420
+ }
421
+
350
422
  public maximize() {
351
423
  if (!this.isMaximizable) {
352
424
  return false
353
425
  }
354
426
 
355
- this.state.maximized = true
356
- return true
427
+ return this.setLayout('maximize')
357
428
  }
358
429
 
359
430
  public unmaximize() {
@@ -361,8 +432,7 @@ export class WindowController implements IWindowController {
361
432
  return false
362
433
  }
363
434
 
364
- this.state.maximized = false
365
- return true
435
+ return this.clearLayout()
366
436
  }
367
437
 
368
438
  // destroy
@@ -418,6 +488,14 @@ export class WindowController implements IWindowController {
418
488
  this.override.title = undefined
419
489
  }
420
490
 
491
+ public setWindowTitleOverride(value: undefined | string) {
492
+ this.override.windowTitle = value
493
+ }
494
+
495
+ public resetWindowTitleOverride() {
496
+ this.override.windowTitle = undefined
497
+ }
498
+
421
499
  // menu
422
500
 
423
501
  public menu: any[] = []
@@ -448,6 +526,11 @@ export class WindowController implements IWindowController {
448
526
  maximize: this.maximize.bind(this),
449
527
  unmaximize: this.unmaximize.bind(this),
450
528
 
529
+ // layout
530
+ setLayout: this.setLayout.bind(this),
531
+ clearLayout: this.clearLayout.bind(this),
532
+ saveBoundsBeforeLayout: this.saveBoundsBeforeLayout.bind(this),
533
+
451
534
  // destroy
452
535
  destroy: this.destroy.bind(this),
453
536
 
@@ -457,6 +540,8 @@ export class WindowController implements IWindowController {
457
540
  // override
458
541
  setTitleOverride: this.setTitleOverride.bind(this),
459
542
  resetTitleOverride: this.resetTitleOverride.bind(this),
543
+ setWindowTitleOverride: this.setWindowTitleOverride.bind(this),
544
+ resetWindowTitleOverride: this.resetWindowTitleOverride.bind(this),
460
545
  }
461
546
  }
462
547
  }
@@ -0,0 +1,28 @@
1
+ import { defineNuxtPlugin } from 'nuxt/app'
2
+ import {
3
+ bindDesktopPinia,
4
+ initDesktopShell,
5
+ syncDesktopShellAppConfig,
6
+ } from '../utils/initDesktopShell'
7
+ import { migratePersistedStoreIds } from '../utils/migratePersistedStoreIds'
8
+
9
+ export default defineNuxtPlugin({
10
+ name: 'desktop-shell-init',
11
+ dependsOn: ['pinia'],
12
+ enforce: 'pre',
13
+ setup(nuxtApp) {
14
+ migratePersistedStoreIds()
15
+ bindDesktopPinia(nuxtApp.$pinia)
16
+ initDesktopShell()
17
+
18
+ nuxtApp.hook('app:created', () => {
19
+ syncDesktopShellAppConfig()
20
+ })
21
+
22
+ return {
23
+ provide: {
24
+ desktopShellReady: true,
25
+ },
26
+ }
27
+ },
28
+ })
@@ -1,16 +1,12 @@
1
- import { setActivePinia } from 'pinia'
2
1
  import { defineNuxtPlugin } from 'nuxt/app'
3
- import { flushPendingDesktopApps } from '../utils/utilDesktop'
2
+ import { flushPendingDesktopApps } from '../../kit/defineDesktopApp'
4
3
 
5
4
  export default defineNuxtPlugin({
6
5
  name: 'desktop-register-desktop-apps',
7
- dependsOn: ['pinia'],
6
+ dependsOn: ['pinia', 'desktop-shell-init'],
8
7
  enforce: 'post',
9
8
  setup(nuxtApp) {
10
9
  nuxtApp.hook('app:created', async () => {
11
- if (nuxtApp.$pinia) {
12
- setActivePinia(nuxtApp.$pinia)
13
- }
14
10
  await flushPendingDesktopApps()
15
11
  })
16
12
  },
@@ -1,5 +1,6 @@
1
1
  import { defineStore } from 'pinia'
2
2
  import { ref } from 'vue'
3
+ import { applicationMetaStoreId } from './storeIds'
3
4
 
4
5
  const metaStoreRegistry = new Map<string, ReturnType<typeof defineStore>>()
5
6
  /** Store ids created by the generic fallback (only `meta`), replaceable when an app registers real state. */
@@ -20,7 +21,7 @@ export const useApplicationMetaStore = (
20
21
  appId: string,
21
22
  states?: MetaStoreSetup,
22
23
  ) => {
23
- const id = `owd/application/${appId}/meta`
24
+ const id = applicationMetaStoreId(appId)
24
25
 
25
26
  if (
26
27
  states !== undefined &&
@@ -1,9 +1,10 @@
1
1
  import { defineStore } from 'pinia'
2
2
  import { ref } from 'vue'
3
+ import { applicationWindowsStoreId } from './storeIds'
3
4
 
4
5
  export const useApplicationWindowsStore = (appId: string) => {
5
6
  return defineStore(
6
- `owd/application/${appId}/windows`,
7
+ applicationWindowsStoreId(appId),
7
8
  () => {
8
9
  const windows: Ref<{ [windowId: string]: WindowStoredState }> = ref({})
9
10