@owdproject/core 3.3.1 → 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 +74 -88
- package/index.ts +6 -3
- package/kit/authoring.ts +340 -0
- package/kit/defineDesktopApp.ts +56 -0
- package/module.ts +64 -84
- package/package.json +70 -41
- 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
|
@@ -1,43 +1,9 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
2
|
import { ref } from 'vue'
|
|
3
|
-
|
|
4
|
-
export type DesktopWindowSurface = 'acrylic' | 'solid'
|
|
5
|
-
|
|
6
|
-
export type DesktopAppearance = 'dark' | 'light'
|
|
7
|
-
|
|
8
|
-
export type DesktopPersonalization = {
|
|
9
|
-
windowSurface: DesktopWindowSurface
|
|
10
|
-
/** Used when windowSurface is solid (CSS color string). */
|
|
11
|
-
windowTint: string
|
|
12
|
-
appearance: DesktopAppearance
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const defaultDesktopPersonalization: DesktopPersonalization = {
|
|
16
|
-
windowSurface: 'acrylic',
|
|
17
|
-
windowTint: '#2d2d30',
|
|
18
|
-
appearance: 'dark',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function mergePersonalization(
|
|
22
|
-
raw: Partial<DesktopPersonalization> | undefined | null,
|
|
23
|
-
): DesktopPersonalization {
|
|
24
|
-
const windowSurface =
|
|
25
|
-
raw?.windowSurface === 'solid' || raw?.windowSurface === 'acrylic'
|
|
26
|
-
? raw.windowSurface
|
|
27
|
-
: defaultDesktopPersonalization.windowSurface
|
|
28
|
-
const appearance =
|
|
29
|
-
raw?.appearance === 'light' || raw?.appearance === 'dark'
|
|
30
|
-
? raw.appearance
|
|
31
|
-
: defaultDesktopPersonalization.appearance
|
|
32
|
-
const tint =
|
|
33
|
-
typeof raw?.windowTint === 'string' && raw.windowTint.trim()
|
|
34
|
-
? raw.windowTint.trim()
|
|
35
|
-
: defaultDesktopPersonalization.windowTint
|
|
36
|
-
return { windowSurface, windowTint: tint, appearance }
|
|
37
|
-
}
|
|
3
|
+
import { DESKTOP_STORE_ID } from './storeIds'
|
|
38
4
|
|
|
39
5
|
export const useDesktopStore = defineStore(
|
|
40
|
-
|
|
6
|
+
DESKTOP_STORE_ID,
|
|
41
7
|
() => {
|
|
42
8
|
const state = ref<{
|
|
43
9
|
workspace: {
|
|
@@ -52,7 +18,11 @@ export const useDesktopStore = defineStore(
|
|
|
52
18
|
positionZ: number
|
|
53
19
|
}
|
|
54
20
|
defaultApps: Record<string, { applicationId: string; entry: string }>
|
|
55
|
-
personalization:
|
|
21
|
+
personalization: {
|
|
22
|
+
windowSurface: 'acrylic' | 'solid'
|
|
23
|
+
windowTint: string
|
|
24
|
+
appearance: 'dark' | 'light'
|
|
25
|
+
}
|
|
56
26
|
}>({
|
|
57
27
|
workspace: {
|
|
58
28
|
overview: false,
|
|
@@ -66,33 +36,23 @@ export const useDesktopStore = defineStore(
|
|
|
66
36
|
positionZ: 0,
|
|
67
37
|
},
|
|
68
38
|
defaultApps: {},
|
|
69
|
-
personalization: {
|
|
39
|
+
personalization: {
|
|
40
|
+
windowSurface: 'acrylic',
|
|
41
|
+
windowTint: '#2d2d30',
|
|
42
|
+
appearance: 'dark',
|
|
43
|
+
},
|
|
70
44
|
})
|
|
71
45
|
|
|
72
|
-
function setWindowSurface(value:
|
|
73
|
-
|
|
74
|
-
state.value.personalization = mergePersonalization({
|
|
75
|
-
...base,
|
|
76
|
-
windowSurface: value,
|
|
77
|
-
})
|
|
46
|
+
function setWindowSurface(value: 'acrylic' | 'solid') {
|
|
47
|
+
state.value.personalization.windowSurface = value
|
|
78
48
|
}
|
|
79
49
|
|
|
80
50
|
function setWindowTint(value: string) {
|
|
81
|
-
|
|
82
|
-
if (!v) return
|
|
83
|
-
const base = mergePersonalization(state.value.personalization)
|
|
84
|
-
state.value.personalization = mergePersonalization({
|
|
85
|
-
...base,
|
|
86
|
-
windowTint: v,
|
|
87
|
-
})
|
|
51
|
+
state.value.personalization.windowTint = value
|
|
88
52
|
}
|
|
89
53
|
|
|
90
|
-
function setAppearance(value:
|
|
91
|
-
|
|
92
|
-
state.value.personalization = mergePersonalization({
|
|
93
|
-
...base,
|
|
94
|
-
appearance: value,
|
|
95
|
-
})
|
|
54
|
+
function setAppearance(value: 'dark' | 'light') {
|
|
55
|
+
state.value.personalization.appearance = value
|
|
96
56
|
}
|
|
97
57
|
|
|
98
58
|
return {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
2
|
import { useDesktopStore } from './storeDesktop'
|
|
3
|
+
import { DESKTOP_DEFAULT_APPS_STORE_ID } from './storeIds'
|
|
3
4
|
|
|
4
5
|
export const useDesktopDefaultAppsStore = defineStore(
|
|
5
|
-
|
|
6
|
+
DESKTOP_DEFAULT_APPS_STORE_ID,
|
|
6
7
|
() => {
|
|
7
8
|
const desktopStore = useDesktopStore()
|
|
8
9
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import { useDesktopStore } from './storeDesktop'
|
|
4
|
+
import { DESKTOP_VOLUME_STORE_ID } from './storeIds'
|
|
4
5
|
|
|
5
|
-
export const useDesktopVolumeStore = defineStore(
|
|
6
|
+
export const useDesktopVolumeStore = defineStore(DESKTOP_VOLUME_STORE_ID, () => {
|
|
6
7
|
const desktopStore = useDesktopStore()
|
|
7
8
|
|
|
8
9
|
const master = computed(() => desktopStore.state.volume.master)
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
+
import type { DesktopWorkAreaRect } from '@owdproject/core'
|
|
1
2
|
import { defineStore } from 'pinia'
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
+
import { computed, readonly, shallowRef } from 'vue'
|
|
3
4
|
import { useDesktopStore } from './storeDesktop'
|
|
5
|
+
import { DESKTOP_WINDOW_STORE_ID } from './storeIds'
|
|
4
6
|
|
|
5
|
-
export const
|
|
7
|
+
export const EMPTY_DESKTOP_WORK_AREA: DesktopWorkAreaRect = {
|
|
8
|
+
x: 0,
|
|
9
|
+
y: 0,
|
|
10
|
+
width: 0,
|
|
11
|
+
height: 0,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const useDesktopWindowStore = defineStore(DESKTOP_WINDOW_STORE_ID, () => {
|
|
6
15
|
const desktopStore = useDesktopStore()
|
|
16
|
+
const workArea = shallowRef<DesktopWorkAreaRect>({
|
|
17
|
+
...EMPTY_DESKTOP_WORK_AREA,
|
|
18
|
+
})
|
|
7
19
|
|
|
8
20
|
const positionZ = computed(() => desktopStore.state.window.positionZ)
|
|
9
21
|
|
|
@@ -13,8 +25,14 @@ export const useDesktopWindowStore = defineStore('owd/desktop/window', () => {
|
|
|
13
25
|
return desktopStore.state.window.positionZ
|
|
14
26
|
}
|
|
15
27
|
|
|
28
|
+
function setWorkArea(rect: DesktopWorkAreaRect) {
|
|
29
|
+
workArea.value = { ...rect }
|
|
30
|
+
}
|
|
31
|
+
|
|
16
32
|
return {
|
|
17
33
|
positionZ,
|
|
18
34
|
incrementPositionZ,
|
|
35
|
+
workArea: readonly(workArea),
|
|
36
|
+
setWorkArea,
|
|
19
37
|
}
|
|
20
38
|
})
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import { useDesktopStore } from './storeDesktop'
|
|
4
|
+
import { DESKTOP_WORKSPACE_STORE_ID } from './storeIds'
|
|
4
5
|
import { nanoid } from 'nanoid'
|
|
5
6
|
|
|
6
7
|
export const useDesktopWorkspaceStore = defineStore(
|
|
7
|
-
|
|
8
|
+
DESKTOP_WORKSPACE_STORE_ID,
|
|
8
9
|
() => {
|
|
9
10
|
const desktopStore = useDesktopStore()
|
|
10
11
|
|
|
@@ -54,6 +55,33 @@ export const useDesktopWorkspaceStore = defineStore(
|
|
|
54
55
|
desktopStore.state.workspace.list.push(nanoid(8))
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/** Last workspace in list excluding the given id (window migration target). */
|
|
59
|
+
function resolveWorkspaceFallback(workspaceId: string): string | null {
|
|
60
|
+
const remaining = desktopStore.state.workspace.list.filter(
|
|
61
|
+
(id) => id !== workspaceId,
|
|
62
|
+
)
|
|
63
|
+
return remaining.at(-1) ?? null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function removeWorkspace(workspaceId: string): boolean {
|
|
67
|
+
const list = desktopStore.state.workspace.list
|
|
68
|
+
if (list.length <= 2) return false
|
|
69
|
+
|
|
70
|
+
const index = list.indexOf(workspaceId)
|
|
71
|
+
if (index === -1) return false
|
|
72
|
+
|
|
73
|
+
const fallbackId = resolveWorkspaceFallback(workspaceId)
|
|
74
|
+
if (!fallbackId) return false
|
|
75
|
+
|
|
76
|
+
list.splice(index, 1)
|
|
77
|
+
|
|
78
|
+
if (desktopStore.state.workspace.active === workspaceId) {
|
|
79
|
+
desktopStore.state.workspace.active = fallbackId
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
|
|
57
85
|
const workspaceActiveIndex = computed(() => {
|
|
58
86
|
return desktopStore.state.workspace.list.findIndex(
|
|
59
87
|
(workspace) => workspace === desktopStore.state.workspace.active,
|
|
@@ -70,6 +98,8 @@ export const useDesktopWorkspaceStore = defineStore(
|
|
|
70
98
|
setOverview,
|
|
71
99
|
setWorkspace,
|
|
72
100
|
createWorkspace,
|
|
101
|
+
resolveWorkspaceFallback,
|
|
102
|
+
removeWorkspace,
|
|
73
103
|
}
|
|
74
104
|
},
|
|
75
105
|
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const DESKTOP_STORE_ID = 'desktop'
|
|
2
|
+
export const DESKTOP_WORKSPACE_STORE_ID = 'desktop/workspace'
|
|
3
|
+
export const DESKTOP_WINDOW_STORE_ID = 'desktop/window'
|
|
4
|
+
export const DESKTOP_VOLUME_STORE_ID = 'desktop/volume'
|
|
5
|
+
export const DESKTOP_DEFAULT_APPS_STORE_ID = 'desktop/defaultApps'
|
|
6
|
+
|
|
7
|
+
export const applicationWindowsStoreId = (appId: string) =>
|
|
8
|
+
`desktop/application/${appId}/windows`
|
|
9
|
+
|
|
10
|
+
export const applicationMetaStoreId = (appId: string) =>
|
|
11
|
+
`desktop/application/${appId}/meta`
|
|
12
|
+
|
|
13
|
+
export const STORE_ID_MIGRATION_FLAG = 'desktop/store-id-migration/v1'
|
|
14
|
+
|
|
15
|
+
/** Static legacy Pinia store ids mapped to their 3.4 replacements. */
|
|
16
|
+
export const LEGACY_STORE_ID_MAP: Record<string, string> = {
|
|
17
|
+
'owd/desktop': DESKTOP_STORE_ID,
|
|
18
|
+
'owd/desktop/workspace': DESKTOP_WORKSPACE_STORE_ID,
|
|
19
|
+
'owd/desktop/window': DESKTOP_WINDOW_STORE_ID,
|
|
20
|
+
'owd/desktop/volume': DESKTOP_VOLUME_STORE_ID,
|
|
21
|
+
'owd/desktop/defaultApps': DESKTOP_DEFAULT_APPS_STORE_ID,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const LEGACY_APPLICATION_STORE_PATTERN =
|
|
25
|
+
/^owd\/application\/([^/]+)\/(windows|meta)$/
|
|
26
|
+
|
|
27
|
+
export function legacyApplicationStoreIdToNew(
|
|
28
|
+
legacyId: string,
|
|
29
|
+
): string | null {
|
|
30
|
+
const match = legacyId.match(LEGACY_APPLICATION_STORE_PATTERN)
|
|
31
|
+
if (!match) return null
|
|
32
|
+
|
|
33
|
+
const [, appId, kind] = match
|
|
34
|
+
return `desktop/application/${appId}/${kind}`
|
|
35
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Neutral menu model for window chrome (Menubar-compatible shape). */
|
|
2
|
+
export interface DesktopMenuItem {
|
|
3
|
+
label?: string
|
|
4
|
+
icon?: string
|
|
5
|
+
command?: () => void
|
|
6
|
+
separator?: boolean
|
|
7
|
+
disabled?: boolean
|
|
8
|
+
items?: DesktopMenuItem[]
|
|
9
|
+
[key: string]: unknown
|
|
10
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { setActivePinia } from 'pinia'
|
|
2
|
+
import type { Pinia } from 'pinia'
|
|
3
|
+
import { useAppConfig, useRuntimeConfig } from 'nuxt/app'
|
|
4
|
+
import { toRaw } from 'vue'
|
|
5
|
+
import { useDesktopManager } from '../composables/useDesktopManager'
|
|
6
|
+
import { useDesktopStore } from '../stores/storeDesktop'
|
|
7
|
+
import { useDesktopWorkspaceStore } from '../stores/storeDesktopWorkspace'
|
|
8
|
+
import { useDesktopDefaultAppsStore } from '../stores/storeDesktopDefaultApps'
|
|
9
|
+
import { registerDesktopPluginPinia } from '../../kit/defineDesktopApp'
|
|
10
|
+
|
|
11
|
+
/** Bind Nuxt's Pinia instance for store composables outside component setup timing. */
|
|
12
|
+
export function bindDesktopPinia(pinia: Pinia | undefined) {
|
|
13
|
+
if (pinia) {
|
|
14
|
+
setActivePinia(pinia)
|
|
15
|
+
registerDesktopPluginPinia(pinia)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Sync `appConfig.desktop` from runtime when the composable is not ready yet (early client plugins). */
|
|
20
|
+
export function ensureAppConfigDesktop() {
|
|
21
|
+
const runtimeConfig = useRuntimeConfig()
|
|
22
|
+
const publicDesktop = runtimeConfig.public?.desktop
|
|
23
|
+
if (!publicDesktop || typeof publicDesktop !== 'object') return undefined
|
|
24
|
+
|
|
25
|
+
let appConfig: ReturnType<typeof useAppConfig> | undefined
|
|
26
|
+
try {
|
|
27
|
+
appConfig = useAppConfig()
|
|
28
|
+
} catch {
|
|
29
|
+
return publicDesktop as DesktopConfig
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!appConfig) return publicDesktop as DesktopConfig
|
|
33
|
+
|
|
34
|
+
if (!appConfig.desktop) {
|
|
35
|
+
appConfig.desktop = publicDesktop as DesktopConfig
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return appConfig.desktop as DesktopConfig
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function mergeShellKeysOntoAppConfig() {
|
|
42
|
+
if (import.meta.server) return
|
|
43
|
+
|
|
44
|
+
const desktopManager = useDesktopManager()
|
|
45
|
+
const desktop = ensureAppConfigDesktop()
|
|
46
|
+
if (!desktop) return
|
|
47
|
+
|
|
48
|
+
desktopManager.setConfig({
|
|
49
|
+
windows: desktop.windows ? toRaw(desktop.windows) : undefined,
|
|
50
|
+
systemBar: desktop.systemBar ? toRaw(desktop.systemBar) : undefined,
|
|
51
|
+
dockBar: desktop.dockBar ? toRaw(desktop.dockBar) : undefined,
|
|
52
|
+
workspaces: desktop.workspaces ? toRaw(desktop.workspaces) : undefined,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Re-run appConfig shell merge when `useAppConfig()` was not ready during early plugins. */
|
|
57
|
+
export function syncDesktopShellAppConfig() {
|
|
58
|
+
mergeShellKeysOntoAppConfig()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Kernel shell bootstrap: workspaces, default apps, merged shell config on appConfig. */
|
|
62
|
+
export function initDesktopShell() {
|
|
63
|
+
if (import.meta.server) return
|
|
64
|
+
|
|
65
|
+
const desktopStore = useDesktopStore()
|
|
66
|
+
const desktopWorkspaceStore = useDesktopWorkspaceStore()
|
|
67
|
+
const runtimeConfig = useRuntimeConfig()
|
|
68
|
+
const desktopDefaultAppsStore = useDesktopDefaultAppsStore()
|
|
69
|
+
|
|
70
|
+
if (desktopStore.$persistedState) {
|
|
71
|
+
desktopStore.$persistedState.isReady().then(() => {
|
|
72
|
+
desktopWorkspaceStore.setupWorkspaces()
|
|
73
|
+
})
|
|
74
|
+
} else {
|
|
75
|
+
desktopWorkspaceStore.setupWorkspaces()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
desktopDefaultAppsStore.loadDefaultAppsFromConfig(runtimeConfig)
|
|
79
|
+
mergeShellKeysOntoAppConfig()
|
|
80
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LEGACY_STORE_ID_MAP,
|
|
3
|
+
STORE_ID_MIGRATION_FLAG,
|
|
4
|
+
legacyApplicationStoreIdToNew,
|
|
5
|
+
} from '../stores/storeIds'
|
|
6
|
+
|
|
7
|
+
function migrateStorageKey(
|
|
8
|
+
storage: Storage,
|
|
9
|
+
legacyId: string,
|
|
10
|
+
newId: string,
|
|
11
|
+
) {
|
|
12
|
+
const legacyValue = storage.getItem(legacyId)
|
|
13
|
+
if (legacyValue === null) return
|
|
14
|
+
|
|
15
|
+
if (storage.getItem(newId) !== null) {
|
|
16
|
+
storage.removeItem(legacyId)
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
storage.setItem(newId, legacyValue)
|
|
21
|
+
storage.removeItem(legacyId)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function collectLegacyApplicationKeys(storage: Storage) {
|
|
25
|
+
const keys: string[] = []
|
|
26
|
+
|
|
27
|
+
for (let index = 0; index < storage.length; index++) {
|
|
28
|
+
const key = storage.key(index)
|
|
29
|
+
if (key && legacyApplicationStoreIdToNew(key)) {
|
|
30
|
+
keys.push(key)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return keys
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* One-shot migration of persisted Pinia state from legacy `owd/*` store ids
|
|
39
|
+
* to `desktop/*`. Runs before shell init so module-persistence hydrates new keys.
|
|
40
|
+
*/
|
|
41
|
+
export function migratePersistedStoreIds(storage: Storage = localStorage) {
|
|
42
|
+
if (typeof storage?.getItem !== 'function') return
|
|
43
|
+
if (storage.getItem(STORE_ID_MIGRATION_FLAG) === 'done') return
|
|
44
|
+
|
|
45
|
+
for (const [legacyId, newId] of Object.entries(LEGACY_STORE_ID_MAP)) {
|
|
46
|
+
migrateStorageKey(storage, legacyId, newId)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const legacyId of collectLegacyApplicationKeys(storage)) {
|
|
50
|
+
const newId = legacyApplicationStoreIdToNew(legacyId)
|
|
51
|
+
if (newId) {
|
|
52
|
+
migrateStorageKey(storage, legacyId, newId)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
storage.setItem(STORE_ID_MIGRATION_FLAG, 'done')
|
|
57
|
+
}
|
package/runtime/utils/utilApp.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { Nuxt } from '@nuxt/schema'
|
|
2
|
-
|
|
3
1
|
export function normalizeApplicationConfig(
|
|
4
2
|
config: ApplicationConfig,
|
|
5
3
|
): ApplicationConfig {
|
|
@@ -25,13 +23,3 @@ export function normalizeApplicationConfig(
|
|
|
25
23
|
entries: normalizedEntries,
|
|
26
24
|
}
|
|
27
25
|
}
|
|
28
|
-
|
|
29
|
-
export function registerTailwindPath(nuxt: Nuxt, path: string) {
|
|
30
|
-
nuxt.options.runtimeConfig.app.owd = nuxt.options.runtimeConfig.app.owd || {}
|
|
31
|
-
nuxt.options.runtimeConfig.app.owd.tailwindPaths =
|
|
32
|
-
nuxt.options.runtimeConfig.app.owd.tailwindPaths || []
|
|
33
|
-
|
|
34
|
-
if (!nuxt.options.runtimeConfig.app.owd.tailwindPaths.includes(path)) {
|
|
35
|
-
nuxt.options.runtimeConfig.app.owd.tailwindPaths.push(path)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Renders a DOM subtree to a canvas via html2canvas (optional dependency).
|
|
3
|
-
* Used for workspace thumbnails
|
|
3
|
+
* Used for workspace overview thumbnails; returns null if capture fails or html2canvas is missing.
|
|
4
4
|
*/
|
|
5
5
|
export async function captureElementToCanvas(
|
|
6
6
|
element: HTMLElement | null | undefined,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { DesktopWorkAreaRect, WindowLayout } from '@owdproject/core'
|
|
2
|
+
import {
|
|
3
|
+
WINDOW_SNAP_EDGE_PX,
|
|
4
|
+
WORKSPACE_EDGE_DROP_PX,
|
|
5
|
+
} from '../constants/shellLayout'
|
|
6
|
+
import { workAreaBottom, workAreaRight } from './utilWindowLayout'
|
|
7
|
+
|
|
8
|
+
export type SnapZoneHint = Exclude<WindowLayout, 'normal'>
|
|
9
|
+
|
|
10
|
+
export type DetectSnapZoneOptions = {
|
|
11
|
+
edgePx?: number
|
|
12
|
+
/** When true, left/right half snap is suppressed in the outer workspace-edge band. */
|
|
13
|
+
deferSideSnapForWorkspaceEdge?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolve snap zone from pointer position and shell work area.
|
|
18
|
+
* Returns null when the pointer is not in a snap band.
|
|
19
|
+
*/
|
|
20
|
+
export function detectSnapZone(
|
|
21
|
+
clientX: number,
|
|
22
|
+
clientY: number,
|
|
23
|
+
workArea: DesktopWorkAreaRect,
|
|
24
|
+
options?: DetectSnapZoneOptions,
|
|
25
|
+
): SnapZoneHint | null {
|
|
26
|
+
const edge = options?.edgePx ?? WINDOW_SNAP_EDGE_PX
|
|
27
|
+
const { x, y, width, height } = workArea
|
|
28
|
+
|
|
29
|
+
if (width <= 0 || height <= 0) {
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const bottom = workAreaBottom(workArea)
|
|
34
|
+
const right = workAreaRight(workArea)
|
|
35
|
+
|
|
36
|
+
const nearTop = clientY <= y + edge
|
|
37
|
+
const nearBottom = clientY >= bottom - edge
|
|
38
|
+
const nearLeft = clientX <= x + edge
|
|
39
|
+
const nearRight = clientX >= right - edge
|
|
40
|
+
|
|
41
|
+
const inWorkspaceEdgeBand =
|
|
42
|
+
options?.deferSideSnapForWorkspaceEdge === true &&
|
|
43
|
+
typeof globalThis !== 'undefined' &&
|
|
44
|
+
(clientX <= WORKSPACE_EDGE_DROP_PX ||
|
|
45
|
+
clientX >= globalThis.innerWidth - WORKSPACE_EDGE_DROP_PX)
|
|
46
|
+
|
|
47
|
+
if (nearTop && nearLeft) return 'top-left'
|
|
48
|
+
if (nearTop && nearRight) return 'top-right'
|
|
49
|
+
if (nearBottom && nearLeft) return 'bottom-left'
|
|
50
|
+
if (nearBottom && nearRight) return 'bottom-right'
|
|
51
|
+
if (nearTop) return 'maximize'
|
|
52
|
+
if (nearBottom) return 'bottom-half'
|
|
53
|
+
if (nearLeft && !inWorkspaceEdgeBand) return 'left-half'
|
|
54
|
+
if (nearRight && !inWorkspaceEdgeBand) return 'right-half'
|
|
55
|
+
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DesktopWorkAreaRect,
|
|
3
|
+
IWindowController,
|
|
4
|
+
WindowBounds,
|
|
5
|
+
WindowLayout,
|
|
6
|
+
} from '@owdproject/core'
|
|
7
|
+
|
|
8
|
+
export type { WindowLayout, WindowBounds, DesktopWorkAreaRect }
|
|
9
|
+
|
|
10
|
+
export function workAreaBottom(workArea: DesktopWorkAreaRect): number {
|
|
11
|
+
return workArea.y + workArea.height
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function workAreaRight(workArea: DesktopWorkAreaRect): number {
|
|
15
|
+
return workArea.x + workArea.width
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Viewport rectangle for a snap zone inside the shell work area.
|
|
20
|
+
*/
|
|
21
|
+
export function computeSnapRect(
|
|
22
|
+
zone: Exclude<WindowLayout, 'normal'>,
|
|
23
|
+
workArea: DesktopWorkAreaRect,
|
|
24
|
+
): WindowBounds {
|
|
25
|
+
const { x, y, width, height } = workArea
|
|
26
|
+
const halfW = Math.floor(width / 2)
|
|
27
|
+
const halfH = Math.floor(height / 2)
|
|
28
|
+
|
|
29
|
+
switch (zone) {
|
|
30
|
+
case 'maximize':
|
|
31
|
+
return { x, y, width, height }
|
|
32
|
+
case 'left-half':
|
|
33
|
+
return { x, y, width: halfW, height }
|
|
34
|
+
case 'right-half':
|
|
35
|
+
return { x: x + halfW, y, width: width - halfW, height }
|
|
36
|
+
case 'bottom-half':
|
|
37
|
+
return { x, y: y + halfH, width, height: height - halfH }
|
|
38
|
+
case 'top-left':
|
|
39
|
+
return { x, y, width: halfW, height: halfH }
|
|
40
|
+
case 'top-right':
|
|
41
|
+
return { x: x + halfW, y, width: width - halfW, height: halfH }
|
|
42
|
+
case 'bottom-left':
|
|
43
|
+
return { x, y: y + halfH, width: halfW, height: height - halfH }
|
|
44
|
+
case 'bottom-right':
|
|
45
|
+
return {
|
|
46
|
+
x: x + halfW,
|
|
47
|
+
y: y + halfH,
|
|
48
|
+
width: width - halfW,
|
|
49
|
+
height: height - halfH,
|
|
50
|
+
}
|
|
51
|
+
default: {
|
|
52
|
+
const _exhaustive: never = zone
|
|
53
|
+
return _exhaustive
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function applyWindowBounds(
|
|
59
|
+
win: IWindowController,
|
|
60
|
+
bounds: WindowBounds,
|
|
61
|
+
): void {
|
|
62
|
+
win.actions.setPosition({ x: bounds.x, y: bounds.y })
|
|
63
|
+
win.actions.setSize({ width: bounds.width, height: bounds.height })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function applySnapToWindow(
|
|
67
|
+
win: IWindowController,
|
|
68
|
+
zone: Exclude<WindowLayout, 'normal'>,
|
|
69
|
+
workArea: DesktopWorkAreaRect,
|
|
70
|
+
): boolean {
|
|
71
|
+
if (!win.isMaximizable) {
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
win.actions.setLayout(zone)
|
|
76
|
+
applyWindowBounds(win, computeSnapRect(zone, workArea))
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function restoreWindowBounds(win: IWindowController): boolean {
|
|
81
|
+
return win.actions.clearLayout()
|
|
82
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function computeMaximizeRect(workArea: DesktopWorkAreaRect): WindowBounds {
|
|
2
|
+
const { x, y, width, height } = workArea
|
|
3
|
+
return { x, y, width, height }
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function applyWindowBounds(
|
|
7
|
+
win: IWindowController,
|
|
8
|
+
bounds: WindowBounds,
|
|
9
|
+
): void {
|
|
10
|
+
win.actions.setPosition({ x: bounds.x, y: bounds.y })
|
|
11
|
+
win.actions.setSize({ width: bounds.width, height: bounds.height })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function applyMaximizeToWindow(
|
|
15
|
+
win: IWindowController,
|
|
16
|
+
workArea: DesktopWorkAreaRect,
|
|
17
|
+
): boolean {
|
|
18
|
+
if (!win.isMaximizable) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
win.actions.setLayout('maximize')
|
|
23
|
+
applyWindowBounds(win, computeMaximizeRect(workArea))
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function toggleWindowMaximizeLayout(
|
|
28
|
+
win: IWindowController,
|
|
29
|
+
workArea: DesktopWorkAreaRect,
|
|
30
|
+
): boolean {
|
|
31
|
+
if (!win.isMaximizable) {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (win.isMaximized || (win.state.layout && win.state.layout !== 'normal')) {
|
|
36
|
+
return win.actions.clearLayout()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (workArea.width <= 0 || workArea.height <= 0) {
|
|
40
|
+
return win.actions.setLayout('maximize')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return applyMaximizeToWindow(win, workArea)
|
|
44
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collect open windows that belong to a workspace (explicit id or implicit active desktop).
|
|
3
|
+
*/
|
|
4
|
+
export function collectWindowsOnWorkspace(
|
|
5
|
+
apps: Iterable<IApplicationController>,
|
|
6
|
+
workspaceId: string,
|
|
7
|
+
activeWorkspaceId: string,
|
|
8
|
+
): IWindowController[] {
|
|
9
|
+
const result: IWindowController[] = []
|
|
10
|
+
|
|
11
|
+
for (const app of apps) {
|
|
12
|
+
for (const win of app.windows.values()) {
|
|
13
|
+
const ws = win.state.workspace
|
|
14
|
+
if (ws === workspaceId) {
|
|
15
|
+
result.push(win)
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
if (!ws && workspaceId === activeWorkspaceId) {
|
|
19
|
+
result.push(win)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return result
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function countWindowsOnWorkspace(
|
|
28
|
+
apps: Iterable<IApplicationController>,
|
|
29
|
+
workspaceId: string,
|
|
30
|
+
activeWorkspaceId: string,
|
|
31
|
+
): number {
|
|
32
|
+
return collectWindowsOnWorkspace(apps, workspaceId, activeWorkspaceId).length
|
|
33
|
+
}
|