@owdproject/core 3.2.0 → 3.3.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 (32) hide show
  1. package/bin/lib/migrateModulePlayground.config.mjs +4 -4
  2. package/bin/lib/validateModule.js +14 -0
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +86 -38
  5. package/module.ts +35 -24
  6. package/package.json +5 -2
  7. package/runtime/components/{Core/Application/CoreApplicationRender.vue → Desktop/Application/DesktopApplicationRender.vue} +1 -1
  8. package/runtime/components/{Core/Application/CoreApplicationWindowsRender.vue → Desktop/Application/DesktopApplicationWindowsRender.vue} +3 -5
  9. package/runtime/composables/useApplicationManager.ts +1 -1
  10. package/runtime/composables/useDesktopManager.ts +3 -3
  11. package/runtime/composables/{useWorkspaceOverview.ts → useWorkspaceManager.ts} +2 -2
  12. package/runtime/plugins/02.desktop-register-desktop-apps.client.ts +17 -0
  13. package/runtime/stores/storeDesktop.ts +0 -11
  14. package/runtime/utils/splitDesktopUserConfig.ts +85 -0
  15. package/runtime/utils/utilCommon.ts +0 -32
  16. package/runtime/utils/utilDesktop.ts +27 -2
  17. package/runtime/utils/{validateOwdUserConfig.ts → validateDesktopUserConfig.ts} +1 -1
  18. package/types/index.d.ts +0 -19
  19. package/runtime/components/Core/Explorer/CoreExplorerFile.vue +0 -76
  20. package/runtime/components/Core/Explorer/CoreExplorerFolder.vue +0 -10
  21. package/runtime/composables/useOwdDialogs.ts +0 -13
  22. package/runtime/dialogs/owdDialogProvider.ts +0 -81
  23. package/runtime/plugins/01.owd-dialogs-fallback.ts +0 -15
  24. package/runtime/stores/storeDesktopExplorer.ts +0 -74
  25. /package/runtime/components/{Core/Background/CoreBackground.vue → Desktop/Background/DesktopBackground.vue} +0 -0
  26. /package/runtime/components/{Core/Desktop/CoreDesktop.vue → Desktop/Core/DesktopCore.vue} +0 -0
  27. /package/runtime/components/{Core/Time/CoreTime.vue → Desktop/Time/DesktopTime.vue} +0 -0
  28. /package/runtime/components/{Core/Window/CoreWindow.vue → Desktop/Window/DesktopWindow.vue} +0 -0
  29. /package/runtime/components/{Core/Window/CoreWindowContent.vue → Desktop/Window/DesktopWindowContent.vue} +0 -0
  30. /package/runtime/components/{Core/Window/CoreWindowNav.vue → Desktop/Window/DesktopWindowNav.vue} +0 -0
  31. /package/runtime/{core → internal}/controllers/ApplicationController.ts +0 -0
  32. /package/runtime/{core → internal}/controllers/WindowController.ts +0 -0
@@ -104,8 +104,8 @@ export const configs = [
104
104
  fsMounts: { '/mnt/test': '/test.zip' },
105
105
  copyTestZip: true,
106
106
  playgroundExtraDevDeps: {
107
- '@owdproject/module-fs': 'workspace:*',
108
- '@owdproject/module-persistence': 'workspace:*',
107
+ '@owdproject/module-fs': '^0.0.4',
108
+ '@owdproject/module-persistence': '^0.0.2',
109
109
  },
110
110
  launch: {
111
111
  file: 'launch-audioplayer.client.ts',
@@ -143,8 +143,8 @@ export const configs = [
143
143
  fsMounts: { '/mnt/test': '/test.zip' },
144
144
  copyTestZip: true,
145
145
  playgroundExtraDevDeps: {
146
- '@owdproject/module-fs': 'workspace:*',
147
- '@owdproject/module-persistence': 'workspace:*',
146
+ '@owdproject/module-fs': '^0.0.4',
147
+ '@owdproject/module-persistence': '^0.0.2',
148
148
  '@owdproject/kit-fs': 'workspace:*',
149
149
  '@owdproject/kit-explorer': 'workspace:*',
150
150
  },
@@ -295,6 +295,20 @@ export function validateOwdModule(packageDir, options = {}) {
295
295
  if (scripts['prepack'] && !scripts['prepack'].includes('nuxt-module-build')) {
296
296
  issue('error', 'prepack-builder', 'prepack must run nuxt-module-build build')
297
297
  }
298
+ if (scripts.prepare) {
299
+ issue(
300
+ 'error',
301
+ 'no-prepare-script',
302
+ 'Do not add a "prepare" script on publishable packages — it runs on npm publish/install and breaks release. Use "prepack" (full build) and "dev:prepare" (local stub + playground) only.',
303
+ )
304
+ }
305
+ if (!rootExport?.development?.includes('src/module')) {
306
+ issue(
307
+ 'warning',
308
+ 'exports-development',
309
+ 'exports["."].development should point to ./src/module.ts (monorepo dev without manual stub after every edit)',
310
+ )
311
+ }
298
312
  if (!scripts['dev:generate']?.includes('NUXT_APP_BASE_URL')) {
299
313
  issue(
300
314
  'warning',
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "desktop",
3
3
  "configKey": "owd",
4
- "version": "3.2.0",
4
+ "version": "3.3.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,28 +1,9 @@
1
1
  import { defineNuxtModule, createResolver, installModule, addComponentsDir, addPlugin, addImportsDir } from '@nuxt/kit';
2
+ import { defu } from 'defu';
2
3
  import { existsSync } from 'node:fs';
3
4
  import { join } from 'node:path';
4
5
 
5
- function deepMerge(target, source) {
6
- function isObject(value) {
7
- return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof RegExp);
8
- }
9
- if (isObject(target) && isObject(source)) {
10
- const result = { ...target };
11
- for (const key in source) {
12
- const targetValue = target[key];
13
- const sourceValue = source[key];
14
- if (isObject(sourceValue) && isObject(targetValue)) {
15
- result[key] = deepMerge(targetValue, sourceValue);
16
- } else {
17
- result[key] = sourceValue;
18
- }
19
- }
20
- return result;
21
- }
22
- return source;
23
- }
24
-
25
- function assertValidOwdUserConfig(config, configFile = "desktop.config.ts") {
6
+ function assertValidDesktopUserConfig(config, configFile = "desktop.config.ts") {
26
7
  if (!config || typeof config !== "object" || Array.isArray(config)) {
27
8
  throw new Error(
28
9
  `[@owdproject/core] ${configFile} must default-export a non-array object from defineDesktopConfig({ ... }).`
@@ -82,7 +63,62 @@ function warnLegacyDesktopConfig(resolved) {
82
63
  }
83
64
  }
84
65
 
85
- const version = "3.2.0";
66
+ const DESKTOP_MODULE_KEYS = ["theme", "apps", "modules"];
67
+ const DESKTOP_RUNTIME_KEYS = /* @__PURE__ */ new Set([
68
+ ...DESKTOP_MODULE_KEYS,
69
+ "docs",
70
+ "name",
71
+ "defaultApps",
72
+ "features",
73
+ "explorer",
74
+ "systemBar",
75
+ "dockBar",
76
+ "workspaces"
77
+ ]);
78
+ const NUXT_OPTION_KEYS = /* @__PURE__ */ new Set([
79
+ "ssr",
80
+ "modules",
81
+ "devtools",
82
+ "vite",
83
+ "nitro",
84
+ "runtimeConfig",
85
+ "app",
86
+ "appConfig",
87
+ "css",
88
+ "plugins",
89
+ "hooks",
90
+ "extends",
91
+ "compatibilityDate",
92
+ "experimental",
93
+ "future",
94
+ "i18n",
95
+ "tailwindcss",
96
+ "primevue"
97
+ ]);
98
+ function splitDesktopUserConfig(config, configFile = "desktop.config.ts") {
99
+ const theme = typeof config.theme === "string" ? config.theme : void 0;
100
+ const apps = Array.isArray(config.apps) ? config.apps : void 0;
101
+ const modules = Array.isArray(config.modules) ? config.modules : void 0;
102
+ const desktopRuntime = {};
103
+ for (const [key, value] of Object.entries(config)) {
104
+ if (DESKTOP_MODULE_KEYS.includes(key)) {
105
+ continue;
106
+ }
107
+ desktopRuntime[key] = value;
108
+ if (NUXT_OPTION_KEYS.has(key)) {
109
+ console.warn(
110
+ `[@owdproject/core] ${configFile}: key "${key}" looks like a Nuxt option and is ignored for _nuxt.options. Move it to nuxt.config.ts; shell values belong under defineDesktopConfig({ systemBar, workspaces, ... }).`
111
+ );
112
+ } else if (!DESKTOP_RUNTIME_KEYS.has(key)) {
113
+ console.warn(
114
+ `[@owdproject/core] ${configFile}: unknown desktop key "${key}" \u2014 merged into runtimeConfig.public.desktop only.`
115
+ );
116
+ }
117
+ }
118
+ return { theme, apps, modules, desktopRuntime };
119
+ }
120
+
121
+ const version = "3.3.0";
86
122
  const pkg = {
87
123
  version: version};
88
124
 
@@ -119,21 +155,27 @@ const module$1 = defineNuxtModule({
119
155
  { cause: e }
120
156
  );
121
157
  }
122
- assertValidOwdUserConfig(clientConfig, resolvedConfig.file);
158
+ assertValidDesktopUserConfig(clientConfig, resolvedConfig.file);
123
159
  if (!clientConfig.theme) {
124
160
  clientConfig.theme = "@owdproject/theme-nova";
125
161
  }
126
- _nuxt.options = {
127
- ..._nuxt.options,
128
- ...clientConfig
129
- };
130
- _nuxt.options.runtimeConfig.public.desktop = {
131
- coreVersion: pkg.version
132
- };
133
- _nuxt.options.runtimeConfig.public.desktop = deepMerge(
134
- _nuxt.options.runtimeConfig.public.desktop,
135
- clientConfig
162
+ const {
163
+ theme: configTheme,
164
+ apps: configApps,
165
+ modules: configModules,
166
+ desktopRuntime
167
+ } = splitDesktopUserConfig(
168
+ clientConfig,
169
+ resolvedConfig.file
170
+ );
171
+ clientConfig.theme = configTheme ?? clientConfig.theme;
172
+ if (configApps) clientConfig.apps = configApps;
173
+ if (configModules) clientConfig.modules = configModules;
174
+ _nuxt.options.runtimeConfig.public.desktop = defu(
175
+ desktopRuntime,
176
+ { coreVersion: pkg.version }
136
177
  );
178
+ await installModule("@pinia/nuxt");
137
179
  {
138
180
  if (clientConfig.theme) {
139
181
  await installModule(clientConfig.theme);
@@ -154,6 +196,13 @@ const module$1 = defineNuxtModule({
154
196
  _nuxt.options.primevue = _nuxt.options.primevue || {};
155
197
  _nuxt.options.primevue.options = _nuxt.options.primevue.options || {};
156
198
  _nuxt.options.primevue.options.theme = _nuxt.options.primevue.options.theme || {};
199
+ _nuxt.options.primevue.components = _nuxt.options.primevue.components || {};
200
+ const include = _nuxt.options.primevue.components.include;
201
+ if (include && include !== "*" && Array.isArray(include)) {
202
+ if (!include.includes("ConfirmDialog")) {
203
+ include.push("ConfirmDialog");
204
+ }
205
+ }
157
206
  await installModule("@primevue/nuxt-module");
158
207
  }
159
208
  {
@@ -166,9 +215,6 @@ const module$1 = defineNuxtModule({
166
215
  viewer: false
167
216
  });
168
217
  }
169
- {
170
- await installModule("@pinia/nuxt");
171
- }
172
218
  {
173
219
  await installModule("@nuxt/fonts");
174
220
  }
@@ -202,14 +248,16 @@ const module$1 = defineNuxtModule({
202
248
  addComponentsDir({
203
249
  path: resolve("./runtime/components"),
204
250
  prefix: "",
251
+ pathPrefix: false,
205
252
  global: true
206
253
  });
207
254
  }
208
255
  {
209
256
  addPlugin(resolve("./runtime/plugins/resize.client.ts"));
210
- addPlugin(resolve("./runtime/plugins/01.owd-dialogs-fallback.ts"));
257
+ addPlugin(
258
+ resolve("./runtime/plugins/02.desktop-register-desktop-apps.client.ts")
259
+ );
211
260
  addImportsDir(resolve("./runtime/composables"));
212
- addImportsDir(resolve("./runtime/core"));
213
261
  addImportsDir(resolve("./runtime/stores"));
214
262
  addImportsDir(resolve("./runtime/utils"));
215
263
  }
package/module.ts CHANGED
@@ -6,12 +6,13 @@ import {
6
6
  installModule,
7
7
  addPlugin
8
8
  } from '@nuxt/kit'
9
- import { deepMerge } from './runtime/utils/utilCommon'
10
- import { assertValidOwdUserConfig } from './runtime/utils/validateOwdUserConfig'
9
+ import { defu } from 'defu'
10
+ import { assertValidDesktopUserConfig } from './runtime/utils/validateDesktopUserConfig'
11
11
  import {
12
12
  resolveDesktopConfigPath,
13
13
  warnLegacyDesktopConfig,
14
14
  } from './runtime/utils/resolveDesktopConfigPath'
15
+ import { splitDesktopUserConfig } from './runtime/utils/splitDesktopUserConfig'
15
16
  import pkg from './package.json'
16
17
 
17
18
  export default defineNuxtModule({
@@ -62,31 +63,37 @@ export default defineNuxtModule({
62
63
  )
63
64
  }
64
65
 
65
- assertValidOwdUserConfig(clientConfig, resolvedConfig.file)
66
+ assertValidDesktopUserConfig(clientConfig, resolvedConfig.file)
66
67
 
67
68
  if (!clientConfig.theme) {
68
69
  clientConfig.theme = '@owdproject/theme-nova'
69
70
  }
70
71
 
71
- // extend nuxt.config.ts with desktop.config.ts
72
+ const {
73
+ theme: configTheme,
74
+ apps: configApps,
75
+ modules: configModules,
76
+ desktopRuntime,
77
+ } = splitDesktopUserConfig(
78
+ clientConfig as Record<string, unknown>,
79
+ resolvedConfig.file,
80
+ )
72
81
 
73
- _nuxt.options = {
74
- ..._nuxt.options,
75
- ...clientConfig
76
- }
82
+ clientConfig.theme = configTheme ?? clientConfig.theme
83
+ if (configApps) clientConfig.apps = configApps
84
+ if (configModules) clientConfig.modules = configModules
77
85
 
78
86
  // init desktop runtime config and define core version
79
87
 
80
- _nuxt.options.runtimeConfig.public.desktop = {
81
- coreVersion: pkg.version
82
- }
83
-
84
- // assign open web desktop config to runtime config
85
- _nuxt.options.runtimeConfig.public.desktop = deepMerge(
86
- _nuxt.options.runtimeConfig.public.desktop,
87
- clientConfig
88
+ _nuxt.options.runtimeConfig.public.desktop = defu(
89
+ desktopRuntime,
90
+ { coreVersion: pkg.version },
88
91
  )
89
92
 
93
+ // Pinia before theme/modules/apps so defineDesktopApp can use stores when
94
+ // published app plugins register on app:created.
95
+ await installModule('@pinia/nuxt')
96
+
90
97
  {
91
98
  // install open web desktop theme
92
99
 
@@ -124,6 +131,14 @@ export default defineNuxtModule({
124
131
  _nuxt.options.primevue.options = _nuxt.options.primevue.options || {}
125
132
  _nuxt.options.primevue.options.theme =
126
133
  _nuxt.options.primevue.options.theme || {}
134
+ _nuxt.options.primevue.components =
135
+ _nuxt.options.primevue.components || {}
136
+ const include = _nuxt.options.primevue.components.include
137
+ if (include && include !== '*' && Array.isArray(include)) {
138
+ if (!include.includes('ConfirmDialog')) {
139
+ include.push('ConfirmDialog')
140
+ }
141
+ }
127
142
 
128
143
  await installModule('@primevue/nuxt-module')
129
144
  }
@@ -145,12 +160,6 @@ export default defineNuxtModule({
145
160
  })
146
161
  }
147
162
 
148
- {
149
- // install pinia
150
-
151
- await installModule('@pinia/nuxt')
152
- }
153
-
154
163
  {
155
164
  // install @nuxt/fonts
156
165
 
@@ -205,18 +214,20 @@ export default defineNuxtModule({
205
214
  addComponentsDir({
206
215
  path: resolve('./runtime/components'),
207
216
  prefix: '',
217
+ pathPrefix: false,
208
218
  global: true
209
219
  })
210
220
  }
211
221
 
212
222
  {
213
223
  addPlugin(resolve('./runtime/plugins/resize.client.ts'))
214
- addPlugin(resolve('./runtime/plugins/01.owd-dialogs-fallback.ts'))
224
+ addPlugin(
225
+ resolve('./runtime/plugins/02.desktop-register-desktop-apps.client.ts'),
226
+ )
215
227
 
216
228
  // add other files
217
229
 
218
230
  addImportsDir(resolve('./runtime/composables'))
219
- addImportsDir(resolve('./runtime/core'))
220
231
  addImportsDir(resolve('./runtime/stores'))
221
232
  addImportsDir(resolve('./runtime/utils'))
222
233
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owdproject/core",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Nuxt Desktop module for browser desktop experiences (windows, shell, apps)",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -41,7 +41,9 @@
41
41
  "dev": "pnpm run dev:prepare",
42
42
  "dev:prepare": "nuxt-module-build build --stub",
43
43
  "build:module": "nuxt-module-build build",
44
- "prepack": "nuxt-module-build build"
44
+ "prepack": "nuxt-module-build build",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest"
45
47
  },
46
48
  "exports": {
47
49
  ".": {
@@ -94,6 +96,7 @@
94
96
  "shellwords": "^1.1.1",
95
97
  "vue-resizable": "^2.1.7",
96
98
  "getopts": "^2.3.0",
99
+ "defu": "^6.1.4",
97
100
  "neo-blessed": "^0.2.0"
98
101
  },
99
102
  "optionalDependencies": {
@@ -10,7 +10,7 @@ const runningApps = applicationManager.appsRunning
10
10
  </script>
11
11
 
12
12
  <template>
13
- <CoreApplicationWindowsRender
13
+ <DesktopApplicationWindowsRender
14
14
  v-for="app of runningApps"
15
15
  :key="app.id"
16
16
  :workspace-filter="workspaceFilter"
@@ -31,10 +31,8 @@ function matchesWorkspace(window: IWindowController): boolean {
31
31
 
32
32
  <template>
33
33
  <template v-for="[windowId, window] in windowEntries" :key="windowId">
34
- <component
35
- v-if="matchesWorkspace(window)"
36
- :window="window"
37
- :is="window.config.component"
38
- />
34
+ <Suspense v-if="matchesWorkspace(window)">
35
+ <component :window="window" :is="window.config.component" />
36
+ </Suspense>
39
37
  </template>
40
38
  </template>
@@ -1,5 +1,5 @@
1
1
  import { reactive, markRaw, computed } from 'vue'
2
- import { ApplicationController } from '../core/controllers/ApplicationController'
2
+ import { ApplicationController } from '../internal/controllers/ApplicationController'
3
3
  import { normalizeApplicationConfig } from '../utils/utilApp'
4
4
  import { debugLog } from '../utils/utilDebug'
5
5
  import * as shellwords from 'shellwords'
@@ -1,6 +1,6 @@
1
1
  import { computed } from 'vue'
2
2
  import { useAppConfig } from 'nuxt/app'
3
- import { deepMerge } from '../utils/utilCommon'
3
+ import { defu } from 'defu'
4
4
  import { useDesktopStore } from '../stores/storeDesktop'
5
5
  import { useDesktopDefaultAppsStore } from '../stores/storeDesktopDefaultApps'
6
6
 
@@ -17,9 +17,9 @@ export function useDesktopManager() {
17
17
  * Merges and sets the desktop config at runtime (shell geometry, feature flags, etc.).
18
18
  */
19
19
  function setConfig(newConfig: DesktopConfig) {
20
- appConfig.desktop = deepMerge(
21
- appConfig.desktop,
20
+ appConfig.desktop = defu(
22
21
  newConfig,
22
+ (appConfig.desktop ?? {}) as DesktopConfig,
23
23
  ) as DesktopConfig
24
24
  }
25
25
 
@@ -3,9 +3,9 @@ import { useApplicationManager } from './useApplicationManager'
3
3
  import { useDesktopWorkspaceStore } from '../stores/storeDesktopWorkspace'
4
4
 
5
5
  /**
6
- * Keyboard + HTML5 drop handlers for workspace overview (move windows between desktops).
6
+ * Keyboard + HTML5 drop handlers for workspace management (move windows between desktops).
7
7
  */
8
- export function useWorkspaceOverview() {
8
+ export function useWorkspaceManager() {
9
9
  const applicationManager = useApplicationManager()
10
10
  const desktopWorkspaceStore = useDesktopWorkspaceStore()
11
11
 
@@ -0,0 +1,17 @@
1
+ import { setActivePinia } from 'pinia'
2
+ import { defineNuxtPlugin } from 'nuxt/app'
3
+ import { flushPendingDesktopApps } from '../utils/utilDesktop'
4
+
5
+ export default defineNuxtPlugin({
6
+ name: 'desktop-register-desktop-apps',
7
+ dependsOn: ['pinia'],
8
+ enforce: 'post',
9
+ setup(nuxtApp) {
10
+ nuxtApp.hook('app:created', async () => {
11
+ if (nuxtApp.$pinia) {
12
+ setActivePinia(nuxtApp.$pinia)
13
+ }
14
+ await flushPendingDesktopApps()
15
+ })
16
+ },
17
+ })
@@ -51,10 +51,6 @@ export const useDesktopStore = defineStore(
51
51
  window: {
52
52
  positionZ: number
53
53
  }
54
- explorer: {
55
- navExpandedKeys: string[]
56
- quickAccessPins: { id: string; label: string; path: string; icon?: string }[]
57
- }
58
54
  defaultApps: Record<string, { applicationId: string; entry: string }>
59
55
  personalization: DesktopPersonalization
60
56
  }>({
@@ -69,13 +65,6 @@ export const useDesktopStore = defineStore(
69
65
  window: {
70
66
  positionZ: 0,
71
67
  },
72
- explorer: {
73
- navExpandedKeys: [
74
- 'quickAccess',
75
- 'thisPc',
76
- ],
77
- quickAccessPins: [],
78
- },
79
68
  defaultApps: {},
80
69
  personalization: { ...defaultDesktopPersonalization },
81
70
  })
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Keys consumed by {@link @owdproject/core} module setup (install order).
3
+ * Must not be spread onto `_nuxt.options`.
4
+ */
5
+ export const DESKTOP_MODULE_KEYS = ['theme', 'apps', 'modules'] as const
6
+
7
+ /**
8
+ * Known desktop shell keys merged into `runtimeConfig.public.desktop` / `appConfig.desktop`.
9
+ * Additional keys are merged with a dev warning (extensions, theme overrides).
10
+ */
11
+ export const DESKTOP_RUNTIME_KEYS = new Set([
12
+ ...DESKTOP_MODULE_KEYS,
13
+ 'docs',
14
+ 'name',
15
+ 'defaultApps',
16
+ 'features',
17
+ 'explorer',
18
+ 'systemBar',
19
+ 'dockBar',
20
+ 'workspaces',
21
+ ])
22
+
23
+ /** Nuxt top-level option names — warn if present in desktop.config (likely misconfiguration). */
24
+ const NUXT_OPTION_KEYS = new Set([
25
+ 'ssr',
26
+ 'modules',
27
+ 'devtools',
28
+ 'vite',
29
+ 'nitro',
30
+ 'runtimeConfig',
31
+ 'app',
32
+ 'appConfig',
33
+ 'css',
34
+ 'plugins',
35
+ 'hooks',
36
+ 'extends',
37
+ 'compatibilityDate',
38
+ 'experimental',
39
+ 'future',
40
+ 'i18n',
41
+ 'tailwindcss',
42
+ 'primevue',
43
+ ])
44
+
45
+ export type SplitDesktopUserConfigResult = {
46
+ theme?: string
47
+ apps?: string[]
48
+ modules?: string[]
49
+ desktopRuntime: Record<string, unknown>
50
+ }
51
+
52
+ export function splitDesktopUserConfig(
53
+ config: Record<string, unknown>,
54
+ configFile = 'desktop.config.ts',
55
+ ): SplitDesktopUserConfigResult {
56
+ const theme =
57
+ typeof config.theme === 'string' ? config.theme : undefined
58
+ const apps = Array.isArray(config.apps)
59
+ ? (config.apps as string[])
60
+ : undefined
61
+ const modules = Array.isArray(config.modules)
62
+ ? (config.modules as string[])
63
+ : undefined
64
+
65
+ const desktopRuntime: Record<string, unknown> = {}
66
+
67
+ for (const [key, value] of Object.entries(config)) {
68
+ if (DESKTOP_MODULE_KEYS.includes(key as (typeof DESKTOP_MODULE_KEYS)[number])) {
69
+ continue
70
+ }
71
+ desktopRuntime[key] = value
72
+
73
+ if (NUXT_OPTION_KEYS.has(key)) {
74
+ console.warn(
75
+ `[@owdproject/core] ${configFile}: key "${key}" looks like a Nuxt option and is ignored for _nuxt.options. Move it to nuxt.config.ts; shell values belong under defineDesktopConfig({ systemBar, workspaces, ... }).`,
76
+ )
77
+ } else if (!DESKTOP_RUNTIME_KEYS.has(key)) {
78
+ console.warn(
79
+ `[@owdproject/core] ${configFile}: unknown desktop key "${key}" — merged into runtimeConfig.public.desktop only.`,
80
+ )
81
+ }
82
+ }
83
+
84
+ return { theme, apps, modules, desktopRuntime }
85
+ }
@@ -24,35 +24,3 @@ export function deepEqual(obj1: any, obj2: any) {
24
24
  }
25
25
  return true
26
26
  }
27
-
28
- export function deepMerge<T>(target: T, source: Partial<T>): T {
29
- function isObject(value: any): value is Record<string, any> {
30
- return (
31
- value !== null &&
32
- typeof value === 'object' &&
33
- !Array.isArray(value) &&
34
- !(value instanceof Date) &&
35
- !(value instanceof RegExp)
36
- )
37
- }
38
-
39
- if (isObject(target) && isObject(source)) {
40
- const result = { ...target }
41
-
42
- for (const key in source) {
43
- const targetValue = (target as any)[key]
44
- const sourceValue = (source as any)[key]
45
-
46
- if (isObject(sourceValue) && isObject(targetValue)) {
47
- // @ts-ignore
48
- result[key] = deepMerge(targetValue, sourceValue)
49
- } else {
50
- result[key] = sourceValue
51
- }
52
- }
53
-
54
- return result
55
- }
56
-
57
- return source as T
58
- }
@@ -1,8 +1,33 @@
1
+ import { getActivePinia } from 'pinia'
1
2
  import { useApplicationManager } from '../composables/useApplicationManager'
2
3
 
3
- export function defineDesktopApp(config: ApplicationConfig) {
4
+ const pendingDesktopApps: ApplicationConfig[] = []
5
+
6
+ /**
7
+ * Register a desktop app. Queues until Pinia is active when theme/app plugins
8
+ * run before @pinia/nuxt (e.g. published apps using `app:created`).
9
+ */
10
+ export async function defineDesktopApp(config: ApplicationConfig) {
11
+ if (import.meta.server) return undefined
12
+
13
+ if (getActivePinia()) {
14
+ const applicationManager = useApplicationManager()
15
+ return applicationManager.defineApp(config.id, config)
16
+ }
17
+
18
+ pendingDesktopApps.push(config)
19
+ return undefined
20
+ }
21
+
22
+ export async function flushPendingDesktopApps() {
23
+ if (pendingDesktopApps.length === 0 || !getActivePinia()) return
24
+
25
+ const queue = pendingDesktopApps.splice(0, pendingDesktopApps.length)
4
26
  const applicationManager = useApplicationManager()
5
- return applicationManager.defineApp(config.id, config)
27
+
28
+ for (const config of queue) {
29
+ await applicationManager.defineApp(config.id, config)
30
+ }
6
31
  }
7
32
 
8
33
  export function defineDesktopConfig(config: DesktopConfig) {
@@ -2,7 +2,7 @@
2
2
  * Runtime validation for the object exported from `desktop.config.ts`
3
3
  * (or legacy `owd.config.ts`).
4
4
  */
5
- export function assertValidOwdUserConfig(
5
+ export function assertValidDesktopUserConfig(
6
6
  config: unknown,
7
7
  configFile = 'desktop.config.ts',
8
8
  ): void {
package/types/index.d.ts CHANGED
@@ -360,31 +360,12 @@ interface DesktopConfig {
360
360
  name?: string
361
361
  defaultApps?: DefaultAppsConfig
362
362
  features?: string[]
363
- explorer?: DesktopExplorerConfig
364
363
 
365
364
  systemBar?: DesktopSystemBarConfig
366
365
  dockBar?: DesktopDockBarConfig
367
366
  workspaces?: DesktopWorkspacesConfig
368
367
  }
369
368
 
370
- interface DesktopExplorerFolderConfig {
371
- id: string
372
- label: string
373
- path: string
374
- icon?: string
375
- }
376
-
377
- interface DesktopExplorerConfig {
378
- quickAccess?: DesktopExplorerFolderConfig[]
379
- quickAccessExtra?: DesktopExplorerFolderConfig[]
380
- quickAccessOverride?: DesktopExplorerFolderConfig[]
381
- specialFolders?: DesktopExplorerFolderConfig[]
382
- specialFoldersExtra?: DesktopExplorerFolderConfig[]
383
- specialFoldersOverride?: DesktopExplorerFolderConfig[]
384
- /** Friendly labels for ZenFS mount points shown under “This PC” (paths → label). */
385
- mountLabels?: Record<string, string>
386
- }
387
-
388
369
  interface DesktopWindowsConfig {
389
370
  position: 'relative' | 'absolute' | 'fixed'
390
371
  }
@@ -1,76 +0,0 @@
1
- <template>
2
- <li
3
- :class="[
4
- 'owd-explorer-file',
5
- { 'owd-explorer-file--inactive': props.inactive },
6
- ]"
7
- >
8
- <a
9
- :href="props.url"
10
- :target="props.target"
11
- :title="props.title"
12
- @click="props.fn"
13
- >
14
- <img :src="props.icon" :alt="props.title" />
15
- <div class="owd-explorer-file__name" v-html="props.name" />
16
- </a>
17
- </li>
18
- </template>
19
-
20
- <script setup lang="ts">
21
- const props = defineProps<{
22
- name: string
23
- title: string
24
- icon: string
25
- url: string
26
- target: string
27
- inactive: boolean
28
- }>()
29
- </script>
30
-
31
- <style scoped lang="scss">
32
- li.file {
33
- width: 100px;
34
- margin: 0 5px 5px 0;
35
- text-align: center;
36
- float: left;
37
- border-radius: 2px;
38
- border: 1px solid rgba(0, 0, 0, 0);
39
-
40
- @media (max-width: 480px) {
41
- margin-right: 0;
42
- }
43
-
44
- @media (min-width: 480px) {
45
- &:not(.inactive):hover {
46
- }
47
- }
48
-
49
- &.inactive {
50
- filter: grayscale(100%);
51
- opacity: 0.2;
52
- }
53
-
54
- a {
55
- display: block;
56
- color: #ccc;
57
- text-decoration: none;
58
- transition: color 0.15s ease-in-out;
59
- padding: 10px 0;
60
-
61
- img {
62
- vertical-align: middle;
63
- height: 32px;
64
- border-radius: 2px;
65
- }
66
-
67
- div.name {
68
- font-size: 12px;
69
- letter-spacing: -0.2px;
70
- line-height: 10px;
71
- margin-top: 10px;
72
- cursor: pointer;
73
- }
74
- }
75
- }
76
- </style>
@@ -1,10 +0,0 @@
1
- <template>
2
- <CoreExplorerFile class="owd-explorer-folder">
3
- <slot />
4
- </CoreExplorerFile>
5
- </template>
6
-
7
- <style scoped lang="scss">
8
- .owd-explorer-folder {
9
- }
10
- </style>
@@ -1,13 +0,0 @@
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
- }
@@ -1,81 +0,0 @@
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
- }
@@ -1,15 +0,0 @@
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,74 +0,0 @@
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
- )