@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.
- package/CHANGELOG.md +98 -0
- package/DESKTOP_KERNEL.md +232 -0
- package/MIGRATION_3.3.2.md +23 -0
- package/MIGRATION_3.4.md +126 -0
- package/PLAYGROUND.md +91 -0
- package/README.md +13 -80
- package/dist/module.d.mts +2 -2
- package/dist/module.json +3 -3
- package/dist/module.mjs +86 -99
- package/index.ts +6 -3
- package/kit/authoring.ts +340 -0
- package/kit/defineDesktopApp.ts +56 -0
- package/module.ts +76 -108
- package/package.json +70 -40
- package/runtime/components/{Desktop/Application/DesktopApplicationRender.vue → Application/DesktopCoreApplicationRender.vue} +2 -2
- package/runtime/components/{Desktop/Application/DesktopApplicationWindowsRender.vue → Application/DesktopCoreApplicationWindowsRender.vue} +1 -1
- package/runtime/components/core/DesktopCore.vue +43 -0
- package/runtime/components/{Desktop/Window/DesktopWindow.vue → window/DesktopCoreWindow.vue} +17 -6
- package/runtime/components/window/DesktopCoreWindowSnapHintsBase.vue +74 -0
- package/runtime/components/workspace/DesktopCoreWorkspaceEdgeHintsBase.vue +84 -0
- package/runtime/composables/useBlockNonInputContextMenu.ts +30 -0
- package/runtime/composables/useDesktopConfig.ts +8 -0
- package/runtime/composables/useDesktopDialogs.ts +16 -0
- package/runtime/composables/useDesktopExtension.ts +8 -0
- package/runtime/composables/useDesktopManager.ts +2 -0
- package/runtime/composables/useDesktopManifest.ts +17 -0
- package/runtime/composables/useDesktopSession.ts +27 -0
- package/runtime/composables/useDesktopShellIdentity.ts +99 -0
- package/runtime/composables/useDesktopShellOptions.ts +36 -0
- package/runtime/composables/useDesktopShellReady.ts +7 -0
- package/runtime/composables/useDesktopWorkArea.ts +93 -0
- package/runtime/composables/useTerminalManager.ts +102 -9
- package/runtime/composables/useToggleWindowMaximize.ts +35 -0
- package/runtime/composables/useWindowDragHandlers.ts +47 -0
- package/runtime/composables/useWindowSnapDrop.ts +112 -0
- package/runtime/composables/useWorkspaceEdgeDrop.ts +176 -0
- package/runtime/composables/useWorkspaceManager.ts +26 -0
- package/runtime/composables/useWorkspaceOverviewCapture.ts +92 -0
- package/runtime/composables/useWorkspaceOverviewLiveScale.ts +151 -0
- package/runtime/constants/desktopShellKeys.ts +12 -0
- package/runtime/constants/shellLayout.ts +5 -0
- package/runtime/dialogs/desktopDialogProvider.ts +73 -0
- package/runtime/internal/controllers/ApplicationController.ts +8 -2
- package/runtime/internal/controllers/WindowController.ts +93 -8
- package/runtime/plugins/01.desktop-shell-init.client.ts +28 -0
- package/runtime/plugins/02.desktop-register-desktop-apps.client.ts +2 -6
- package/runtime/stores/storeApplicationMeta.ts +2 -1
- package/runtime/stores/storeApplicationWindows.ts +2 -1
- package/runtime/stores/storeDesktop.ts +17 -57
- package/runtime/stores/storeDesktopDefaultApps.ts +2 -1
- package/runtime/stores/storeDesktopVolume.ts +2 -1
- package/runtime/stores/storeDesktopWindow.ts +20 -2
- package/runtime/stores/storeDesktopWorkspace.ts +31 -1
- package/runtime/stores/storeIds.ts +35 -0
- package/runtime/types/desktopMenu.ts +10 -0
- package/runtime/utils/initDesktopShell.ts +80 -0
- package/runtime/utils/migratePersistedStoreIds.ts +57 -0
- package/runtime/utils/utilApp.ts +0 -12
- package/runtime/utils/{captureElementToCanvas.ts → utilCaptureElementToCanvas.ts} +1 -1
- package/runtime/utils/utilDetectSnapZone.ts +57 -0
- package/runtime/utils/utilWindowLayout.ts +82 -0
- package/runtime/utils/utilWindowMaximizeLayout.ts +44 -0
- package/runtime/utils/utilWorkspaceWindows.ts +33 -0
- package/types/index.d.ts +125 -14
- package/bin/cli.js +0 -395
- package/bin/desktop.js +0 -5
- package/bin/lib/catalog.js +0 -293
- package/bin/lib/config.js +0 -109
- package/bin/lib/desktopConfig.js +0 -56
- package/bin/lib/generateTemplate.js +0 -389
- package/bin/lib/install.js +0 -408
- package/bin/lib/migrateModulePlayground.config.mjs +0 -164
- package/bin/lib/migrateModulePlayground.mjs +0 -313
- package/bin/lib/npmVersions.js +0 -124
- package/bin/lib/playgroundContext.js +0 -188
- package/bin/lib/scaffold.js +0 -197
- package/bin/lib/status.js +0 -289
- package/bin/lib/validateModule.js +0 -697
- package/bin/lib/workspace.js +0 -182
- package/bin/owd.js +0 -5
- package/bin/tui.js +0 -1379
- package/runtime/components/Desktop/Core/DesktopCore.vue +0 -90
- package/runtime/utils/explorerEntryPath.ts +0 -10
- package/runtime/utils/resolveDesktopConfigPath.ts +0 -52
- package/runtime/utils/splitDesktopUserConfig.ts +0 -85
- package/runtime/utils/utilDesktop.ts +0 -35
- package/runtime/utils/validateDesktopUserConfig.ts +0 -44
- /package/runtime/components/{Desktop/Background/DesktopBackground.vue → background/DesktopCoreBackground.vue} +0 -0
- /package/runtime/components/{Desktop/Time/DesktopTime.vue → time/DesktopCoreTime.vue} +0 -0
- /package/runtime/components/{Desktop/Window/DesktopWindowContent.vue → window/DesktopCoreWindowContent.vue} +0 -0
- /package/runtime/components/{Desktop/Window/DesktopWindowNav.vue → window/DesktopCoreWindowNav.vue} +0 -0
- /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,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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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 '
|
|
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 =
|
|
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
|
-
|
|
7
|
+
applicationWindowsStoreId(appId),
|
|
7
8
|
() => {
|
|
8
9
|
const windows: Ref<{ [windowId: string]: WindowStoredState }> = ref({})
|
|
9
10
|
|