@owdproject/core 3.1.4-beta.0 → 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.
Files changed (64) hide show
  1. package/README.md +6 -6
  2. package/bin/cli.js +395 -0
  3. package/bin/desktop.js +5 -0
  4. package/bin/lib/catalog.js +293 -0
  5. package/bin/lib/config.js +109 -0
  6. package/bin/lib/desktopConfig.js +56 -0
  7. package/bin/lib/generateTemplate.js +389 -0
  8. package/bin/lib/install.js +408 -0
  9. package/bin/lib/migrateModulePlayground.config.mjs +164 -0
  10. package/bin/lib/migrateModulePlayground.mjs +313 -0
  11. package/bin/lib/npmVersions.js +124 -0
  12. package/bin/lib/playgroundContext.js +188 -0
  13. package/bin/lib/scaffold.js +197 -0
  14. package/bin/lib/status.js +289 -0
  15. package/bin/lib/validateModule.js +683 -0
  16. package/bin/lib/workspace.js +182 -0
  17. package/bin/owd.js +2 -25
  18. package/bin/tui.js +1379 -0
  19. package/dist/module.d.mts +13 -0
  20. package/dist/module.json +9 -0
  21. package/dist/module.mjs +219 -0
  22. package/dist/types.d.mts +7 -0
  23. package/module.ts +53 -19
  24. package/package.json +78 -22
  25. package/runtime/components/Core/Application/CoreApplicationRender.vue +3 -1
  26. package/runtime/components/Core/Application/CoreApplicationWindowsRender.vue +28 -7
  27. package/runtime/components/Core/Desktop/CoreDesktop.vue +11 -7
  28. package/runtime/components/Core/Window/CoreWindow.vue +45 -9
  29. package/runtime/components/Core/Window/CoreWindowNav.vue +26 -8
  30. package/runtime/composables/useApplicationEntries.ts +1 -1
  31. package/runtime/composables/useApplicationManager.ts +282 -4
  32. package/runtime/composables/useDesktopManager.ts +46 -4
  33. package/runtime/composables/useOwdDialogs.ts +13 -0
  34. package/runtime/composables/useTerminalManager.ts +62 -3
  35. package/runtime/composables/useWorkspaceOverview.ts +41 -0
  36. package/runtime/core/controllers/ApplicationController.ts +120 -30
  37. package/runtime/core/controllers/WindowController.ts +5 -10
  38. package/runtime/dialogs/owdDialogProvider.ts +81 -0
  39. package/runtime/plugins/01.owd-dialogs-fallback.ts +15 -0
  40. package/runtime/stores/storeApplicationMeta.ts +56 -12
  41. package/runtime/stores/storeDesktop.ts +79 -0
  42. package/runtime/stores/storeDesktopDefaultApps.ts +53 -0
  43. package/runtime/stores/storeDesktopExplorer.ts +74 -0
  44. package/runtime/stores/storeDesktopWorkspace.ts +14 -2
  45. package/runtime/utils/captureElementToCanvas.ts +21 -0
  46. package/runtime/utils/explorerEntryPath.ts +10 -0
  47. package/runtime/utils/resolveDesktopConfigPath.ts +52 -0
  48. package/runtime/utils/validateOwdUserConfig.ts +44 -0
  49. package/types/index.d.ts +64 -12
  50. package/CHANGELOG.md +0 -25
  51. package/project.json +0 -4
  52. package/runtime/composables/useClipboardFs.ts +0 -25
  53. package/runtime/core/managers/ApplicationManager.ts +0 -281
  54. package/runtime/core/managers/DesktopManager.ts +0 -47
  55. package/runtime/core/managers/TerminalManager.ts +0 -48
  56. package/runtime/stores/storeApplication.ts +0 -235
  57. package/test/basic.test.ts +0 -15
  58. package/test/fixtures/basic/app.vue +0 -5
  59. package/test/fixtures/basic/nuxt.config.ts +0 -5
  60. package/test/fixtures/basic/package.json +0 -5
  61. package/tsconfig.app.json +0 -31
  62. package/tsconfig.json +0 -12
  63. package/tsconfig.spec.json +0 -37
  64. package/vitest.config.ts +0 -24
@@ -4,51 +4,72 @@ import { useApplicationManager } from '../../composables/useApplicationManager'
4
4
  import { useApplicationWindowsStore } from '../../stores/storeApplicationWindows'
5
5
  import { useApplicationMetaStore } from '../../stores/storeApplicationMeta'
6
6
  import { useTerminalManager } from '../../composables/useTerminalManager'
7
- import { useDesktopManager } from '../../composables/useDesktopManager'
8
7
  import { debugLog, debugError } from '../../utils/utilDebug'
8
+ import { useDesktopDefaultAppsStore } from '../../stores/storeDesktopDefaultApps'
9
9
  import { useDesktopWorkspaceStore } from '../../stores/storeDesktopWorkspace'
10
- import { reactive } from '@vue/reactivity'
10
+ import { useDesktopWindowStore } from '../../stores/storeDesktopWindow'
11
+ import { shallowRef } from 'vue'
12
+ import { unref } from 'vue'
11
13
 
12
14
  export class ApplicationController implements IApplicationController {
13
15
  private readonly applicationManager: IApplicationManager
14
- private readonly desktopManager: IDesktopManager
15
16
  private readonly terminalManager: ITerminalManager
16
17
 
17
18
  public readonly id
18
19
  public readonly config
19
20
  public readonly storeWindows
20
- public readonly storeMeta
21
21
 
22
- public windows = reactive(new Map<string, IWindowController>())
22
+ private readonly _windows = shallowRef(new Map<string, IWindowController>())
23
+
24
+ /** Reactive window count for Vue (class getters are not tracked). */
25
+ public readonly openWindowCount = shallowRef(0)
26
+
27
+ get windows(): Map<string, IWindowController> {
28
+ return this._windows.value
29
+ }
30
+
31
+ private replaceWindows(
32
+ mutator: (next: Map<string, IWindowController>) => void,
33
+ ) {
34
+ const next = new Map(this._windows.value)
35
+ mutator(next)
36
+ this._windows.value = next
37
+ this.openWindowCount.value = next.size
38
+ }
23
39
 
24
40
  public isRunning = false
25
41
 
26
42
  constructor(id: string, config: ApplicationConfig) {
27
43
  this.applicationManager = useApplicationManager()
28
44
  this.terminalManager = useTerminalManager()
29
- this.desktopManager = useDesktopManager()
30
45
 
31
46
  this.id = id
32
47
  this.config = config
33
48
  this.storeWindows = useApplicationWindowsStore(id)
34
- this.storeMeta = useApplicationMetaStore(id)
49
+ }
50
+
51
+ /** Lazy meta store — app modules should register state via `useApplicationMetaStore` before `defineDesktopApp`. */
52
+ get storeMeta() {
53
+ return useApplicationMetaStore(this.id)()
35
54
  }
36
55
 
37
56
  public async initApplication(): Promise<void> {
57
+ const desktopDefaultAppsStore = useDesktopDefaultAppsStore()
58
+
38
59
  // provides
39
60
 
40
61
  // set as default app for specific purposes
41
62
  // todo improve this and move it in a store
42
63
  if (this.config.provides) {
43
- const existingDefault = this.desktopManager.getDefaultApp(
64
+ const existingDefault = desktopDefaultAppsStore.getDefaultApp(
44
65
  this.config.provides.name,
45
66
  )
46
67
 
47
68
  if (!existingDefault) {
48
- this.desktopManager.setDefaultApp(
69
+ desktopDefaultAppsStore.setDefaultApp(
49
70
  this.config.provides.name,
50
71
  this,
51
- this.config.provides.command,
72
+ this.config.provides.entry,
52
73
  )
53
74
 
54
75
  debugLog(
@@ -127,9 +148,32 @@ export class ApplicationController implements IApplicationController {
127
148
  }
128
149
  })
129
150
 
151
+ this.normalizeWindowFocus()
152
+
130
153
  debugLog('Windows have been restored', this.windows)
131
154
  }
132
155
 
156
+ /** Ensure a single focused window after restore or stale persisted state. */
157
+ private normalizeWindowFocus() {
158
+ if (this.windows.size === 0) {
159
+ return
160
+ }
161
+
162
+ let topWindow: IWindowController | undefined
163
+ let topZ = -Infinity
164
+
165
+ for (const window of this.windows.values()) {
166
+ window.setFocus(false)
167
+ const z = window.state.position?.z ?? 0
168
+ if (z >= topZ) {
169
+ topZ = z
170
+ topWindow = window
171
+ }
172
+ }
173
+
174
+ topWindow?.setFocus(true)
175
+ }
176
+
133
177
  public openWindow(
134
178
  model: string,
135
179
  windowStoredState: WindowStoredState | undefined,
@@ -142,6 +186,20 @@ export class ApplicationController implements IApplicationController {
142
186
  return
143
187
  }
144
188
 
189
+ if (!windowStoredState && this.config.singleton) {
190
+ const existing = this.getFirstWindowByModel(model)
191
+ if (existing) {
192
+ if (meta && typeof meta === 'object') {
193
+ Object.assign(existing.meta, meta)
194
+ }
195
+ if (!meta?.isRestoring) {
196
+ existing.actions.setActive(true)
197
+ existing.actions.bringToFront()
198
+ }
199
+ return existing
200
+ }
201
+ }
202
+
145
203
  let windowId: string
146
204
 
147
205
  if (!windowStoredState) {
@@ -151,32 +209,54 @@ export class ApplicationController implements IApplicationController {
151
209
  model
152
210
  ] as WindowConfig
153
211
  const screenHeight = window.innerHeight
154
- const centerY = (screenHeight - Number(windowConfig.size.height)) / 2
212
+ const configHeight = windowConfig.size?.height
213
+ const layoutHeight =
214
+ typeof configHeight === 'number'
215
+ ? configHeight
216
+ : typeof configHeight === 'string' && /^\d+$/.test(configHeight)
217
+ ? Number(configHeight)
218
+ : 240
219
+ const centerY = (screenHeight - layoutHeight) / 2
155
220
  const positionY =
156
221
  windowConfig.position?.y !== undefined
157
222
  ? window.scrollY + windowConfig.position.y
158
223
  : window.scrollY + centerY
159
-
160
- this.storeWindows.windows[windowId] = {
161
- model,
162
- state: {
163
- id: windowId,
164
- active: true,
165
- focused: false,
166
- position: {
167
- x: windowConfig.position?.x,
168
- y: positionY,
224
+ const cascadeStep = 40
225
+ const cascadeOffset = this.getWindowsByModel(model).length * cascadeStep
226
+ const baseX = windowConfig.position?.x ?? 100
227
+ const desktopWindowStore = useDesktopWindowStore()
228
+ const initialZ = desktopWindowStore.incrementPositionZ()
229
+
230
+ const nextWindows = {
231
+ ...this.storeWindows.windows,
232
+ [windowId]: {
233
+ model,
234
+ state: {
235
+ id: windowId,
236
+ active: true,
237
+ focused: false,
238
+ position: {
239
+ x: baseX + cascadeOffset,
240
+ y: positionY + cascadeOffset,
241
+ z: initialZ,
242
+ },
243
+ createdAt: +new Date(),
244
+ workspace: desktopWorkspaceStore.resolveActiveWorkspaceId(),
169
245
  },
170
- createdAt: +new Date(),
171
- workspace: desktopWorkspaceStore.active,
246
+ meta,
172
247
  },
173
- meta,
174
248
  }
249
+ this.storeWindows.windows = nextWindows
175
250
 
176
251
  windowStoredState = this.storeWindows.windows[windowId]
177
252
  } else {
178
253
  // restore previous id if state is defined
179
- windowId = windowStoredState.state.id
254
+ windowId =
255
+ windowStoredState.state?.id ??
256
+ Object.keys(this.storeWindows.windows).find(
257
+ (key) => this.storeWindows.windows[key] === windowStoredState,
258
+ ) ??
259
+ `${model}-restored`
180
260
  }
181
261
 
182
262
  const windowConfig = this.config.windows[model] as WindowConfig
@@ -192,7 +272,9 @@ export class ApplicationController implements IApplicationController {
192
272
  windowController.actions.bringToFront()
193
273
  }
194
274
 
195
- this.windows.set(windowId, windowController)
275
+ this.replaceWindows((next) => {
276
+ next.set(windowId, windowController)
277
+ })
196
278
 
197
279
  this.setRunning(true)
198
280
 
@@ -200,17 +282,25 @@ export class ApplicationController implements IApplicationController {
200
282
  }
201
283
 
202
284
  public closeWindow(windowId: string) {
203
- delete this.storeWindows.windows[windowId]
204
- this.windows.delete(windowId)
285
+ if (this.storeWindows.windows[windowId]) {
286
+ const nextWindows = { ...this.storeWindows.windows }
287
+ delete nextWindows[windowId]
288
+ this.storeWindows.windows = nextWindows
289
+ }
205
290
 
206
- if (this.windows.size === 0) {
291
+ this.replaceWindows((next) => {
292
+ next.delete(windowId)
293
+ })
294
+
295
+ if (this.openWindowCount.value === 0) {
207
296
  this.applicationManager.closeApp(this.id)
208
297
  }
209
298
  }
210
299
 
211
300
  public closeAllWindows() {
212
301
  this.storeWindows.windows = {}
213
- this.windows.clear()
302
+ this._windows.value = new Map()
303
+ this.openWindowCount.value = 0
214
304
  }
215
305
 
216
306
  get windowsOpened() {
@@ -202,18 +202,13 @@ export class WindowController implements IWindowController {
202
202
  this.state.position = { x: 0, y: 0, z: 0 }
203
203
  }
204
204
 
205
- // already focused
206
- if (this.state.focused) {
207
- return
208
- }
209
-
210
- // set focus false on all other windows
211
- for (const [windowId, window] of applicationManager.windowsOpened) {
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
 
@@ -283,7 +278,7 @@ export class WindowController implements IWindowController {
283
278
  maxHeight:
284
279
  typeof stateSize.maxHeight !== 'undefined'
285
280
  ? stateSize.maxHeight
286
- : (configSize.maxHeight ?? 600),
281
+ : configSize.maxHeight,
287
282
  }
288
283
  }
289
284
 
@@ -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
- export const useApplicationMetaStore = (appId: string, states: any = {}) => {
4
- return defineStore(
5
- `owd/application/${appId}/meta`,
6
- () => {
7
- return states
8
- },
9
- {
10
- persistedState: {
11
- persist: true,
12
- },
13
- },
14
- )
4
+ const metaStoreRegistry = new Map<string, ReturnType<typeof defineStore>>()
5
+ /** Store ids created by the generic fallback (only `meta`), replaceable when an app registers real state. */
6
+ const fallbackMetaStoreIds = new Set<string>()
7
+
8
+ type MetaStoreSetup = (() => Record<string, unknown>) | Record<string, unknown>
9
+
10
+ function resolveSetup(states: MetaStoreSetup) {
11
+ return typeof states === 'function' ? states : () => states
12
+ }
13
+
14
+ /**
15
+ * Per-app Pinia meta store factory. The first registration for an app id wins so
16
+ * app modules should call this from their store module (imported in the app plugin
17
+ * before `defineDesktopApp`).
18
+ */
19
+ export const useApplicationMetaStore = (
20
+ appId: string,
21
+ states?: MetaStoreSetup,
22
+ ) => {
23
+ const id = `owd/application/${appId}/meta`
24
+
25
+ if (
26
+ states !== undefined &&
27
+ (!metaStoreRegistry.has(id) || fallbackMetaStoreIds.has(id))
28
+ ) {
29
+ fallbackMetaStoreIds.delete(id)
30
+ metaStoreRegistry.set(
31
+ id,
32
+ defineStore(id, resolveSetup(states), {
33
+ persistedState: {
34
+ persist: true,
35
+ },
36
+ }),
37
+ )
38
+ }
39
+
40
+ if (!metaStoreRegistry.has(id)) {
41
+ fallbackMetaStoreIds.add(id)
42
+ metaStoreRegistry.set(
43
+ id,
44
+ defineStore(
45
+ id,
46
+ () => ({
47
+ meta: ref<Record<string, unknown>>({}),
48
+ }),
49
+ {
50
+ persistedState: {
51
+ persist: true,
52
+ },
53
+ },
54
+ ),
55
+ )
56
+ }
57
+
58
+ return metaStoreRegistry.get(id)!
15
59
  }
@@ -1,6 +1,41 @@
1
1
  import { defineStore } from 'pinia'
2
2
  import { ref } from 'vue'
3
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
+ }
38
+
4
39
  export const useDesktopStore = defineStore(
5
40
  'owd/desktop',
6
41
  () => {
@@ -16,6 +51,12 @@ export const useDesktopStore = defineStore(
16
51
  window: {
17
52
  positionZ: number
18
53
  }
54
+ explorer: {
55
+ navExpandedKeys: string[]
56
+ quickAccessPins: { id: string; label: string; path: string; icon?: string }[]
57
+ }
58
+ defaultApps: Record<string, { applicationId: string; entry: string }>
59
+ personalization: DesktopPersonalization
19
60
  }>({
20
61
  workspace: {
21
62
  overview: false,
@@ -28,10 +69,48 @@ export const useDesktopStore = defineStore(
28
69
  window: {
29
70
  positionZ: 0,
30
71
  },
72
+ explorer: {
73
+ navExpandedKeys: [
74
+ 'quickAccess',
75
+ 'thisPc',
76
+ ],
77
+ quickAccessPins: [],
78
+ },
79
+ defaultApps: {},
80
+ personalization: { ...defaultDesktopPersonalization },
31
81
  })
32
82
 
83
+ function setWindowSurface(value: DesktopWindowSurface) {
84
+ const base = mergePersonalization(state.value.personalization)
85
+ state.value.personalization = mergePersonalization({
86
+ ...base,
87
+ windowSurface: value,
88
+ })
89
+ }
90
+
91
+ function setWindowTint(value: string) {
92
+ const v = value.trim()
93
+ if (!v) return
94
+ const base = mergePersonalization(state.value.personalization)
95
+ state.value.personalization = mergePersonalization({
96
+ ...base,
97
+ windowTint: v,
98
+ })
99
+ }
100
+
101
+ function setAppearance(value: DesktopAppearance) {
102
+ const base = mergePersonalization(state.value.personalization)
103
+ state.value.personalization = mergePersonalization({
104
+ ...base,
105
+ appearance: value,
106
+ })
107
+ }
108
+
33
109
  return {
34
110
  state,
111
+ setWindowSurface,
112
+ setWindowTint,
113
+ setAppearance,
35
114
  }
36
115
  },
37
116
  {
@@ -0,0 +1,53 @@
1
+ import { defineStore } from 'pinia'
2
+ import { useDesktopStore } from './storeDesktop'
3
+
4
+ export const useDesktopDefaultAppsStore = defineStore(
5
+ 'owd/desktop/defaultApps',
6
+ () => {
7
+ const desktopStore = useDesktopStore()
8
+
9
+ /**
10
+ * Sets a default app for a given feature
11
+ */
12
+ function setDefaultApp(
13
+ feature: string,
14
+ application: IApplicationController,
15
+ entry: string,
16
+ ) {
17
+ if (!desktopStore.state.hasOwnProperty('defaultApps')) {
18
+ desktopStore.state.defaultApps = {}
19
+ }
20
+
21
+ desktopStore.state.defaultApps[feature] = {
22
+ applicationId: application.id,
23
+ entry,
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Gets the default app config for a given feature
29
+ */
30
+ function getDefaultApp(feature: string): DefaultAppConfig | undefined {
31
+ if (!desktopStore.state.hasOwnProperty('defaultApps')) {
32
+ desktopStore.state.defaultApps = {}
33
+ }
34
+
35
+ return desktopStore.state.defaultApps[feature]
36
+ }
37
+
38
+ /**
39
+ * Loads default apps from current config
40
+ */
41
+ function loadDefaultAppsFromConfig(runtimeConfig) {
42
+ if (runtimeConfig.public.desktop.defaultApps) {
43
+ desktopStore.state.defaultApps = runtimeConfig.public.desktop.defaultApps
44
+ }
45
+ }
46
+
47
+ return {
48
+ setDefaultApp,
49
+ getDefaultApp,
50
+ loadDefaultAppsFromConfig,
51
+ }
52
+ },
53
+ )
@@ -0,0 +1,74 @@
1
+ import { defineStore } from 'pinia'
2
+ import { computed } from 'vue'
3
+ import { useDesktopStore } from './storeDesktop'
4
+
5
+ type ExplorerQuickAccessPin = {
6
+ id: string
7
+ label: string
8
+ path: string
9
+ icon?: string
10
+ }
11
+
12
+ export const useDesktopExplorerStore = defineStore(
13
+ 'owd/desktop/explorer',
14
+ () => {
15
+ const desktopStore = useDesktopStore()
16
+
17
+ const navExpandedKeys = computed(
18
+ () => desktopStore.state.explorer.navExpandedKeys,
19
+ )
20
+ const quickAccessPins = computed(
21
+ () => desktopStore.state.explorer.quickAccessPins,
22
+ )
23
+
24
+ function setNavExpandedKeys(keys: string[]) {
25
+ desktopStore.state.explorer.navExpandedKeys = [...new Set(keys)]
26
+ }
27
+
28
+ function setQuickAccessPins(pins: ExplorerQuickAccessPin[]) {
29
+ desktopStore.state.explorer.quickAccessPins = pins
30
+ }
31
+
32
+ function pinQuickAccess(entry: ExplorerQuickAccessPin) {
33
+ const exists = desktopStore.state.explorer.quickAccessPins.some(
34
+ (pin) => pin.path === entry.path,
35
+ )
36
+ if (exists) return
37
+ desktopStore.state.explorer.quickAccessPins.push(entry)
38
+ }
39
+
40
+ function unpinQuickAccess(path: string) {
41
+ desktopStore.state.explorer.quickAccessPins =
42
+ desktopStore.state.explorer.quickAccessPins.filter(
43
+ (pin) => pin.path !== path,
44
+ )
45
+ }
46
+
47
+ function reorderQuickAccess(startIndex: number, endIndex: number) {
48
+ const list = [...desktopStore.state.explorer.quickAccessPins]
49
+ if (
50
+ startIndex < 0 ||
51
+ endIndex < 0 ||
52
+ startIndex >= list.length ||
53
+ endIndex >= list.length ||
54
+ startIndex === endIndex
55
+ ) {
56
+ return
57
+ }
58
+ const [item] = list.splice(startIndex, 1)
59
+ if (!item) return
60
+ list.splice(endIndex, 0, item)
61
+ desktopStore.state.explorer.quickAccessPins = list
62
+ }
63
+
64
+ return {
65
+ navExpandedKeys,
66
+ quickAccessPins,
67
+ setNavExpandedKeys,
68
+ setQuickAccessPins,
69
+ pinQuickAccess,
70
+ unpinQuickAccess,
71
+ reorderQuickAccess,
72
+ }
73
+ },
74
+ )