@owdproject/core 3.1.4 → 3.2.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/README.md +6 -6
- package/bin/cli.js +395 -0
- package/bin/desktop.js +5 -0
- package/bin/lib/catalog.js +293 -0
- package/bin/lib/config.js +109 -0
- package/bin/lib/desktopConfig.js +56 -0
- package/bin/lib/generateTemplate.js +389 -0
- package/bin/lib/install.js +408 -0
- package/bin/lib/migrateModulePlayground.config.mjs +164 -0
- package/bin/lib/migrateModulePlayground.mjs +313 -0
- package/bin/lib/npmVersions.js +124 -0
- package/bin/lib/playgroundContext.js +188 -0
- package/bin/lib/scaffold.js +197 -0
- package/bin/lib/status.js +289 -0
- package/bin/lib/validateModule.js +683 -0
- package/bin/lib/workspace.js +182 -0
- package/bin/owd.js +2 -25
- package/bin/tui.js +1379 -0
- package/dist/module.d.mts +13 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +219 -0
- package/dist/types.d.mts +7 -0
- package/module.ts +38 -12
- package/package.json +78 -22
- package/runtime/components/Core/Application/CoreApplicationRender.vue +3 -1
- package/runtime/components/Core/Application/CoreApplicationWindowsRender.vue +28 -7
- package/runtime/components/Core/Window/CoreWindow.vue +42 -8
- package/runtime/components/Core/Window/CoreWindowNav.vue +26 -8
- package/runtime/composables/useApplicationManager.ts +18 -13
- package/runtime/composables/useDesktopManager.ts +31 -7
- package/runtime/composables/useOwdDialogs.ts +13 -0
- package/runtime/composables/useWorkspaceOverview.ts +41 -0
- package/runtime/core/controllers/ApplicationController.ts +114 -24
- package/runtime/core/controllers/WindowController.ts +4 -9
- package/runtime/dialogs/owdDialogProvider.ts +81 -0
- package/runtime/plugins/01.owd-dialogs-fallback.ts +15 -0
- package/runtime/stores/storeApplicationMeta.ts +56 -12
- package/runtime/stores/storeDesktop.ts +79 -2
- package/runtime/stores/storeDesktopDefaultApps.ts +2 -2
- package/runtime/stores/storeDesktopExplorer.ts +74 -0
- package/runtime/stores/storeDesktopWorkspace.ts +14 -2
- package/runtime/utils/captureElementToCanvas.ts +21 -0
- package/runtime/utils/explorerEntryPath.ts +10 -0
- package/runtime/utils/resolveDesktopConfigPath.ts +52 -0
- package/runtime/utils/validateOwdUserConfig.ts +44 -0
- package/types/index.d.ts +64 -12
- package/CHANGELOG.md +0 -25
- package/project.json +0 -4
- package/runtime/stores/storeApplication.ts +0 -236
- package/test/basic.test.ts +0 -15
- package/test/fixtures/basic/app.vue +0 -5
- package/test/fixtures/basic/nuxt.config.ts +0 -5
- package/test/fixtures/basic/package.json +0 -5
- package/tsconfig.app.json +0 -31
- package/tsconfig.json +0 -12
- package/tsconfig.spec.json +0 -37
- package/vitest.config.ts +0 -24
|
@@ -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
|
|
187
|
+
const windows = new Map<string, IWindowController>()
|
|
188
188
|
|
|
189
189
|
for (const applicationController of apps.values()) {
|
|
190
|
-
if (
|
|
191
|
-
|
|
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
|
-
|
|
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
|
|
215
|
-
|
|
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 {
|
|
1
|
+
import { computed } from 'vue'
|
|
2
2
|
import { useAppConfig } from 'nuxt/app'
|
|
3
|
+
import { deepMerge } from '../utils/utilCommon'
|
|
4
|
+
import { useDesktopStore } from '../stores/storeDesktop'
|
|
5
|
+
import { useDesktopDefaultAppsStore } from '../stores/storeDesktopDefaultApps'
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
|
-
* Desktop Manager
|
|
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
20
|
appConfig.desktop = deepMerge(
|
|
17
21
|
appConfig.desktop,
|
|
18
22
|
newConfig,
|
|
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,13 @@
|
|
|
1
|
+
import { inject } from 'vue'
|
|
2
|
+
import {
|
|
3
|
+
OWD_DIALOG_PROVIDER_KEY,
|
|
4
|
+
createBrowserFallbackDialogProvider,
|
|
5
|
+
type OwdDialogProvider,
|
|
6
|
+
} from '../dialogs/owdDialogProvider'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns the active desktop dialog provider (theme) or a documented browser fallback.
|
|
10
|
+
*/
|
|
11
|
+
export function useOwdDialogs(): OwdDialogProvider {
|
|
12
|
+
return inject(OWD_DIALOG_PROVIDER_KEY, null) ?? createBrowserFallbackDialogProvider()
|
|
13
|
+
}
|
|
@@ -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 overview (move windows between desktops).
|
|
7
|
+
*/
|
|
8
|
+
export function useWorkspaceOverview() {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
204
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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,81 @@
|
|
|
1
|
+
import type { InjectionKey } from 'vue'
|
|
2
|
+
|
|
3
|
+
/** Options for a yes/no confirmation (theme renders labels and chrome). */
|
|
4
|
+
export interface OwdConfirmDialogOptions {
|
|
5
|
+
title?: string
|
|
6
|
+
message: string
|
|
7
|
+
danger?: boolean
|
|
8
|
+
acceptLabel?: string
|
|
9
|
+
rejectLabel?: string
|
|
10
|
+
/**
|
|
11
|
+
* Theme-specific payload (e.g. Win95 delete dialog passes `toTrash` for slot templates).
|
|
12
|
+
*/
|
|
13
|
+
extras?: Record<string, unknown>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Cross-theme dialog API. Themes implement with PrimeVue, GNOME dialogs, etc.
|
|
18
|
+
* Callers pass user-facing strings (usually from i18n); implementations only render.
|
|
19
|
+
*/
|
|
20
|
+
export interface OwdDialogProvider {
|
|
21
|
+
confirm(options: OwdConfirmDialogOptions): Promise<boolean>
|
|
22
|
+
alert(message: string, options?: { title?: string }): Promise<void>
|
|
23
|
+
prompt(
|
|
24
|
+
message: string,
|
|
25
|
+
defaultValue?: string,
|
|
26
|
+
): Promise<string | null>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const OWD_DIALOG_PROVIDER_KEY: InjectionKey<OwdDialogProvider> =
|
|
30
|
+
Symbol('owd-dialog-provider')
|
|
31
|
+
|
|
32
|
+
function browserGlobals(): {
|
|
33
|
+
confirm: (message?: string) => boolean
|
|
34
|
+
alert: (message?: string) => void
|
|
35
|
+
prompt: (message?: string, defaultText?: string) => string | null
|
|
36
|
+
} | null {
|
|
37
|
+
const g = globalThis as typeof globalThis & {
|
|
38
|
+
confirm?: (message?: string) => boolean
|
|
39
|
+
alert?: (message?: string) => void
|
|
40
|
+
prompt?: (message?: string, defaultText?: string) => string | null
|
|
41
|
+
}
|
|
42
|
+
if (typeof g.confirm !== 'function') return null
|
|
43
|
+
return {
|
|
44
|
+
confirm: g.confirm.bind(g),
|
|
45
|
+
alert:
|
|
46
|
+
typeof g.alert === 'function'
|
|
47
|
+
? g.alert.bind(g)
|
|
48
|
+
: (m?: string) => {
|
|
49
|
+
void m
|
|
50
|
+
},
|
|
51
|
+
prompt:
|
|
52
|
+
typeof g.prompt === 'function'
|
|
53
|
+
? g.prompt.bind(g)
|
|
54
|
+
: () => null,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createBrowserFallbackDialogProvider(): OwdDialogProvider {
|
|
59
|
+
return {
|
|
60
|
+
async confirm(opts) {
|
|
61
|
+
if (import.meta.server) return false
|
|
62
|
+
const w = browserGlobals()
|
|
63
|
+
if (!w) return false
|
|
64
|
+
const text = [opts.title, opts.message].filter(Boolean).join('\n\n')
|
|
65
|
+
return w.confirm(text)
|
|
66
|
+
},
|
|
67
|
+
async alert(message, opts) {
|
|
68
|
+
if (import.meta.server) return
|
|
69
|
+
const w = browserGlobals()
|
|
70
|
+
if (!w) return
|
|
71
|
+
w.alert([opts?.title, message].filter(Boolean).join('\n\n'))
|
|
72
|
+
},
|
|
73
|
+
async prompt(message, defaultValue = '') {
|
|
74
|
+
if (import.meta.server) return null
|
|
75
|
+
const w = browserGlobals()
|
|
76
|
+
if (!w) return null
|
|
77
|
+
const v = w.prompt(message, defaultValue)
|
|
78
|
+
return v === null ? null : v
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from 'nuxt/app'
|
|
2
|
+
import {
|
|
3
|
+
OWD_DIALOG_PROVIDER_KEY,
|
|
4
|
+
createBrowserFallbackDialogProvider,
|
|
5
|
+
} from '../dialogs/owdDialogProvider'
|
|
6
|
+
|
|
7
|
+
export default defineNuxtPlugin({
|
|
8
|
+
name: 'owd-dialogs-fallback',
|
|
9
|
+
setup(nuxtApp) {
|
|
10
|
+
nuxtApp.vueApp.provide(
|
|
11
|
+
OWD_DIALOG_PROVIDER_KEY,
|
|
12
|
+
createBrowserFallbackDialogProvider(),
|
|
13
|
+
)
|
|
14
|
+
},
|
|
15
|
+
})
|
|
@@ -1,15 +1,59 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref } from 'vue'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
}
|