@owdproject/core 3.2.0 → 3.3.1

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 +110 -61
  5. package/module.ts +64 -65
  6. package/package.json +6 -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.1",
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,10 +63,82 @@ 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.1";
86
122
  const pkg = {
87
123
  version: version};
88
124
 
125
+ function primevueModuleDependency(nuxt) {
126
+ const existing = nuxt.options.primevue ?? {};
127
+ const components = { ...existing.components ?? {} };
128
+ const include = components.include;
129
+ if (include && include !== "*" && Array.isArray(include) && !include.includes("ConfirmDialog")) {
130
+ components.include = [...include, "ConfirmDialog"];
131
+ }
132
+ return {
133
+ defaults: defu(
134
+ {
135
+ options: { theme: {} },
136
+ components
137
+ },
138
+ existing
139
+ )
140
+ };
141
+ }
89
142
  const module$1 = defineNuxtModule({
90
143
  meta: {
91
144
  name: "desktop",
@@ -96,6 +149,21 @@ const module$1 = defineNuxtModule({
96
149
  apps: [],
97
150
  modules: []
98
151
  },
152
+ moduleDependencies: (nuxt) => ({
153
+ "@pinia/nuxt": {},
154
+ "@primevue/nuxt-module": primevueModuleDependency(nuxt),
155
+ "@nuxt/fonts": {},
156
+ "@nuxt/icon": {
157
+ defaults: {
158
+ clientBundle: {
159
+ scan: true,
160
+ sizeLimitKb: 256
161
+ }
162
+ }
163
+ },
164
+ "@vueuse/nuxt": {},
165
+ "@nuxtjs/i18n": {}
166
+ }),
99
167
  async setup(_options, _nuxt) {
100
168
  const { resolve } = createResolver(import.meta.url);
101
169
  _nuxt.options.experimental = {
@@ -119,20 +187,25 @@ const module$1 = defineNuxtModule({
119
187
  { cause: e }
120
188
  );
121
189
  }
122
- assertValidOwdUserConfig(clientConfig, resolvedConfig.file);
190
+ assertValidDesktopUserConfig(clientConfig, resolvedConfig.file);
123
191
  if (!clientConfig.theme) {
124
192
  clientConfig.theme = "@owdproject/theme-nova";
125
193
  }
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
194
+ const {
195
+ theme: configTheme,
196
+ apps: configApps,
197
+ modules: configModules,
198
+ desktopRuntime
199
+ } = splitDesktopUserConfig(
200
+ clientConfig,
201
+ resolvedConfig.file
202
+ );
203
+ clientConfig.theme = configTheme ?? clientConfig.theme;
204
+ if (configApps) clientConfig.apps = configApps;
205
+ if (configModules) clientConfig.modules = configModules;
206
+ _nuxt.options.runtimeConfig.public.desktop = defu(
207
+ desktopRuntime,
208
+ { coreVersion: pkg.version }
136
209
  );
137
210
  {
138
211
  if (clientConfig.theme) {
@@ -150,12 +223,6 @@ const module$1 = defineNuxtModule({
150
223
  }
151
224
  }
152
225
  _nuxt.options.appConfig.desktop = _nuxt.options.runtimeConfig.public.desktop;
153
- {
154
- _nuxt.options.primevue = _nuxt.options.primevue || {};
155
- _nuxt.options.primevue.options = _nuxt.options.primevue.options || {};
156
- _nuxt.options.primevue.options.theme = _nuxt.options.primevue.options.theme || {};
157
- await installModule("@primevue/nuxt-module");
158
- }
159
226
  {
160
227
  const tailwindPaths = _nuxt.options.runtimeConfig.app.owd?.tailwindPaths || [];
161
228
  tailwindPaths.push("./runtime/components/**/*.{vue,mjs,ts}");
@@ -166,26 +233,6 @@ const module$1 = defineNuxtModule({
166
233
  viewer: false
167
234
  });
168
235
  }
169
- {
170
- await installModule("@pinia/nuxt");
171
- }
172
- {
173
- await installModule("@nuxt/fonts");
174
- }
175
- {
176
- await installModule("@nuxt/icon", {
177
- clientBundle: {
178
- scan: true,
179
- sizeLimitKb: 256
180
- }
181
- });
182
- }
183
- {
184
- await installModule("@vueuse/nuxt");
185
- }
186
- {
187
- await installModule("@nuxtjs/i18n");
188
- }
189
236
  {
190
237
  _nuxt.hook("vite:extendConfig", (viteConfig) => {
191
238
  viteConfig.css = viteConfig.css || {};
@@ -202,14 +249,16 @@ const module$1 = defineNuxtModule({
202
249
  addComponentsDir({
203
250
  path: resolve("./runtime/components"),
204
251
  prefix: "",
252
+ pathPrefix: false,
205
253
  global: true
206
254
  });
207
255
  }
208
256
  {
209
257
  addPlugin(resolve("./runtime/plugins/resize.client.ts"));
210
- addPlugin(resolve("./runtime/plugins/01.owd-dialogs-fallback.ts"));
258
+ addPlugin(
259
+ resolve("./runtime/plugins/02.desktop-register-desktop-apps.client.ts")
260
+ );
211
261
  addImportsDir(resolve("./runtime/composables"));
212
- addImportsDir(resolve("./runtime/core"));
213
262
  addImportsDir(resolve("./runtime/stores"));
214
263
  addImportsDir(resolve("./runtime/utils"));
215
264
  }
package/module.ts CHANGED
@@ -6,14 +6,40 @@ 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 type { Nuxt } from '@nuxt/schema'
11
+ import { assertValidDesktopUserConfig } from './runtime/utils/validateDesktopUserConfig'
11
12
  import {
12
13
  resolveDesktopConfigPath,
13
14
  warnLegacyDesktopConfig,
14
15
  } from './runtime/utils/resolveDesktopConfigPath'
16
+ import { splitDesktopUserConfig } from './runtime/utils/splitDesktopUserConfig'
15
17
  import pkg from './package.json'
16
18
 
19
+ function primevueModuleDependency(nuxt: Nuxt) {
20
+ const existing = nuxt.options.primevue ?? {}
21
+ const components = { ...(existing.components ?? {}) }
22
+ const include = components.include
23
+ if (
24
+ include &&
25
+ include !== '*' &&
26
+ Array.isArray(include) &&
27
+ !include.includes('ConfirmDialog')
28
+ ) {
29
+ components.include = [...include, 'ConfirmDialog']
30
+ }
31
+
32
+ return {
33
+ defaults: defu(
34
+ {
35
+ options: { theme: {} },
36
+ components,
37
+ },
38
+ existing,
39
+ ),
40
+ }
41
+ }
42
+
17
43
  export default defineNuxtModule({
18
44
  meta: {
19
45
  name: 'desktop',
@@ -24,6 +50,21 @@ export default defineNuxtModule({
24
50
  apps: [],
25
51
  modules: [],
26
52
  },
53
+ moduleDependencies: (nuxt) => ({
54
+ '@pinia/nuxt': {},
55
+ '@primevue/nuxt-module': primevueModuleDependency(nuxt),
56
+ '@nuxt/fonts': {},
57
+ '@nuxt/icon': {
58
+ defaults: {
59
+ clientBundle: {
60
+ scan: true,
61
+ sizeLimitKb: 256,
62
+ },
63
+ },
64
+ },
65
+ '@vueuse/nuxt': {},
66
+ '@nuxtjs/i18n': {},
67
+ }),
27
68
  async setup(_options, _nuxt) {
28
69
  const { resolve } = createResolver(import.meta.url)
29
70
 
@@ -62,29 +103,31 @@ export default defineNuxtModule({
62
103
  )
63
104
  }
64
105
 
65
- assertValidOwdUserConfig(clientConfig, resolvedConfig.file)
106
+ assertValidDesktopUserConfig(clientConfig, resolvedConfig.file)
66
107
 
67
108
  if (!clientConfig.theme) {
68
109
  clientConfig.theme = '@owdproject/theme-nova'
69
110
  }
70
111
 
71
- // extend nuxt.config.ts with desktop.config.ts
112
+ const {
113
+ theme: configTheme,
114
+ apps: configApps,
115
+ modules: configModules,
116
+ desktopRuntime,
117
+ } = splitDesktopUserConfig(
118
+ clientConfig as Record<string, unknown>,
119
+ resolvedConfig.file,
120
+ )
72
121
 
73
- _nuxt.options = {
74
- ..._nuxt.options,
75
- ...clientConfig
76
- }
122
+ clientConfig.theme = configTheme ?? clientConfig.theme
123
+ if (configApps) clientConfig.apps = configApps
124
+ if (configModules) clientConfig.modules = configModules
77
125
 
78
126
  // init desktop runtime config and define core version
79
127
 
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
128
+ _nuxt.options.runtimeConfig.public.desktop = defu(
129
+ desktopRuntime,
130
+ { coreVersion: pkg.version },
88
131
  )
89
132
 
90
133
  {
@@ -118,18 +161,7 @@ export default defineNuxtModule({
118
161
  _nuxt.options.appConfig.desktop = _nuxt.options.runtimeConfig.public.desktop
119
162
 
120
163
  {
121
- // install primevue
122
-
123
- _nuxt.options.primevue = _nuxt.options.primevue || {}
124
- _nuxt.options.primevue.options = _nuxt.options.primevue.options || {}
125
- _nuxt.options.primevue.options.theme =
126
- _nuxt.options.primevue.options.theme || {}
127
-
128
- await installModule('@primevue/nuxt-module')
129
- }
130
-
131
- {
132
- // install tailwind
164
+ // Tailwind after theme/apps so registerTailwindPath paths are collected first.
133
165
 
134
166
  const tailwindPaths =
135
167
  _nuxt.options.runtimeConfig.app.owd?.tailwindPaths || []
@@ -145,41 +177,6 @@ export default defineNuxtModule({
145
177
  })
146
178
  }
147
179
 
148
- {
149
- // install pinia
150
-
151
- await installModule('@pinia/nuxt')
152
- }
153
-
154
- {
155
- // install @nuxt/fonts
156
-
157
- await installModule('@nuxt/fonts')
158
- }
159
-
160
- {
161
- // install @nuxt/icon
162
-
163
- await installModule('@nuxt/icon', {
164
- clientBundle: {
165
- scan: true,
166
- sizeLimitKb: 256
167
- }
168
- })
169
- }
170
-
171
- {
172
- // install @vueuse/nuxt
173
-
174
- await installModule('@vueuse/nuxt')
175
- }
176
-
177
- {
178
- // install @nuxtjs/i18n
179
-
180
- await installModule('@nuxtjs/i18n')
181
- }
182
-
183
180
  {
184
181
  // configure scss for vite
185
182
 
@@ -205,18 +202,20 @@ export default defineNuxtModule({
205
202
  addComponentsDir({
206
203
  path: resolve('./runtime/components'),
207
204
  prefix: '',
205
+ pathPrefix: false,
208
206
  global: true
209
207
  })
210
208
  }
211
209
 
212
210
  {
213
211
  addPlugin(resolve('./runtime/plugins/resize.client.ts'))
214
- addPlugin(resolve('./runtime/plugins/01.owd-dialogs-fallback.ts'))
212
+ addPlugin(
213
+ resolve('./runtime/plugins/02.desktop-register-desktop-apps.client.ts'),
214
+ )
215
215
 
216
216
  // add other files
217
217
 
218
218
  addImportsDir(resolve('./runtime/composables'))
219
- addImportsDir(resolve('./runtime/core'))
220
219
  addImportsDir(resolve('./runtime/stores'))
221
220
  addImportsDir(resolve('./runtime/utils'))
222
221
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owdproject/core",
3
- "version": "3.2.0",
3
+ "version": "3.3.1",
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
  ".": {
@@ -82,6 +84,7 @@
82
84
  "@primeuix/themes": "^2.0.3",
83
85
  "@primevue/forms": "^4.5.5",
84
86
  "@primevue/nuxt-module": "^4.5.5",
87
+ "primevue": "^4.5.5",
85
88
  "@vueuse/components": "^14.3.0",
86
89
  "@vueuse/core": "^14.3.0",
87
90
  "@vueuse/nuxt": "^14.3.0",
@@ -94,6 +97,7 @@
94
97
  "shellwords": "^1.1.1",
95
98
  "vue-resizable": "^2.1.7",
96
99
  "getopts": "^2.3.0",
100
+ "defu": "^6.1.4",
97
101
  "neo-blessed": "^0.2.0"
98
102
  },
99
103
  "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
- )