@owdproject/nuxt 4.0.0-alpha.3 → 4.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/kit/authoring.d.ts +5 -13
- package/dist/kit/authoring.js +59 -43
- package/dist/module.d.mts +6 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/window/DesktopCoreWindow.vue +35 -0
- package/dist/runtime/composables/useDesktop.d.ts +1 -1
- package/dist/runtime/internal/controllers/ApplicationController.d.ts +8 -1
- package/dist/runtime/internal/controllers/ApplicationController.js +38 -2
- package/dist/runtime/stores/storeApplicationMeta.js +16 -6
- package/dist/runtime/stores/storeApplicationWindows.js +28 -18
- package/dist/runtime/stores/storeDesktop.js +26 -13
- package/dist/runtime/stores/storeDesktopWorkspace.js +89 -77
- package/dist/runtime/stores/storeNotification.js +7 -0
- package/dist/types/index.d.ts +9 -1
- package/package.json +8 -2
package/dist/kit/authoring.d.ts
CHANGED
|
@@ -51,21 +51,13 @@ export declare function setDesktopExtensionConfig(nuxt: Nuxt, configKey: string,
|
|
|
51
51
|
* User values from desktop.config win over `partial` (defu: partial fills undefined only).
|
|
52
52
|
*/
|
|
53
53
|
export declare function mergeDesktopExtensionConfig(nuxt: Nuxt, configKey: string, partial: Record<string, unknown>): void;
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
* collection contract framework-agnostic across desktop distributions.
|
|
58
|
-
*/
|
|
59
|
-
export declare function readExtensionMeta(modulePath: string): Promise<Partial<DesktopExtensionMeta>>;
|
|
60
|
-
/**
|
|
61
|
-
* Validates an extension contract and returns a normalized copy. Throws on an unknown
|
|
62
|
-
* kind/framework so misconfigured extensions fail at build time instead of at runtime.
|
|
63
|
-
*/
|
|
54
|
+
/** Reads build-time extension metadata using Nuxt module resolution and getMeta(). */
|
|
55
|
+
export declare function readExtensionMeta(modulePath: string, nuxt?: Nuxt): Promise<Partial<DesktopExtensionMeta>>;
|
|
56
|
+
/** Validates the declared contract; this alone does not check the host integration. */
|
|
64
57
|
export declare function assertDesktopExtensionMeta(meta: Partial<DesktopExtensionMeta>, packageName?: string): DesktopExtensionMeta;
|
|
65
58
|
/**
|
|
66
|
-
* Installs a desktop theme, module, or app
|
|
67
|
-
*
|
|
68
|
-
* so Nuxt merges desktop.config with module defaults.
|
|
59
|
+
* Installs a desktop theme, module, or app after checking explicit extension metadata.
|
|
60
|
+
* Ordinary Nuxt modules and older name/configKey-only descriptors remain supported.
|
|
69
61
|
*/
|
|
70
62
|
export declare function installDesktopPackage(nuxt: Nuxt, modulePath: string, desktop?: Record<string, unknown>): Promise<void>;
|
|
71
63
|
type DesktopModuleMeta = {
|
package/dist/kit/authoring.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import {
|
|
4
4
|
defineNuxtModule,
|
|
5
5
|
installModule,
|
|
6
|
-
|
|
6
|
+
loadNuxtModuleInstance
|
|
7
7
|
} from "@nuxt/kit";
|
|
8
8
|
import { defu } from "defu";
|
|
9
9
|
export function defineDesktopConfig(config) {
|
|
@@ -45,6 +45,11 @@ export function assertValidDesktopUserConfig(config, configFile = "desktop.confi
|
|
|
45
45
|
);
|
|
46
46
|
}
|
|
47
47
|
const c = config;
|
|
48
|
+
if (c.id != null && (typeof c.id !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(c.id))) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`[@owdproject/nuxt] ${configFile}: \`id\` must be a stable identifier of 1\u201364 ASCII letters, digits, dots, underscores or hyphens, starting with a letter or digit (e.g. 'atproto-os').`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
48
53
|
if (c.theme != null && typeof c.theme !== "string") {
|
|
49
54
|
throw new Error(
|
|
50
55
|
`[@owdproject/nuxt] ${configFile}: \`theme\` must be a string (npm package name or local path of the theme module).`
|
|
@@ -139,33 +144,27 @@ export function mergeDesktopExtensionConfig(nuxt, configKey, partial) {
|
|
|
139
144
|
const base = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
|
|
140
145
|
desktop[configKey] = defu(partial, base);
|
|
141
146
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
147
|
+
const EXTENSION_META_KEYS = [
|
|
148
|
+
"kind",
|
|
149
|
+
"framework",
|
|
150
|
+
"packageName",
|
|
151
|
+
"configKey",
|
|
152
|
+
"entry",
|
|
153
|
+
"adapter",
|
|
154
|
+
"requires"
|
|
155
|
+
];
|
|
156
|
+
async function loadModuleMeta(modulePath, nuxt) {
|
|
157
|
+
const { nuxtModule, buildTimeModuleMeta } = await loadNuxtModuleInstance(modulePath, nuxt);
|
|
158
|
+
const descriptor = nuxtModule;
|
|
159
|
+
const declared = typeof descriptor.getMeta === "function" ? await descriptor.getMeta() : descriptor.meta;
|
|
160
|
+
if (declared !== void 0 && (!declared || typeof declared !== "object" || Array.isArray(declared))) {
|
|
161
|
+
throw new Error(`[@owdproject/nuxt] ${modulePath}: module metadata must be an object.`);
|
|
154
162
|
}
|
|
155
|
-
|
|
156
|
-
return typeof key === "string" && key.length > 0 ? key : void 0;
|
|
163
|
+
return { ...buildTimeModuleMeta, ...declared };
|
|
157
164
|
}
|
|
158
|
-
export async function readExtensionMeta(modulePath) {
|
|
159
|
-
const meta =
|
|
160
|
-
return
|
|
161
|
-
kind: meta.kind,
|
|
162
|
-
framework: meta.framework,
|
|
163
|
-
packageName: meta.packageName,
|
|
164
|
-
configKey: meta.configKey,
|
|
165
|
-
entry: meta.entry,
|
|
166
|
-
adapter: meta.adapter,
|
|
167
|
-
requires: meta.requires
|
|
168
|
-
};
|
|
165
|
+
export async function readExtensionMeta(modulePath, nuxt) {
|
|
166
|
+
const meta = await loadModuleMeta(modulePath, nuxt);
|
|
167
|
+
return Object.fromEntries(EXTENSION_META_KEYS.map((key) => [key, meta[key]]));
|
|
169
168
|
}
|
|
170
169
|
export function assertDesktopExtensionMeta(meta, packageName = "unknown") {
|
|
171
170
|
if (!meta.kind || !KIND_SEGMENTS.includes(meta.kind)) {
|
|
@@ -178,28 +177,45 @@ export function assertDesktopExtensionMeta(meta, packageName = "unknown") {
|
|
|
178
177
|
`[@owdproject/nuxt] ${packageName}: extension meta.framework must be one of ${FRAMEWORK_SEGMENTS.join(", ")}.`
|
|
179
178
|
);
|
|
180
179
|
}
|
|
181
|
-
|
|
180
|
+
for (const key of ["packageName", "configKey", "entry", "adapter"]) {
|
|
181
|
+
if (meta[key] !== void 0 && (typeof meta[key] !== "string" || !meta[key].trim())) {
|
|
182
|
+
throw new Error(`[@owdproject/nuxt] ${packageName}: extension meta.${key} must be a non-empty string.`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (meta.requires !== void 0 && (!Array.isArray(meta.requires) || !meta.requires.every(
|
|
186
|
+
(requirement) => typeof requirement === "string" && requirement.trim().length > 0
|
|
187
|
+
))) {
|
|
188
|
+
throw new Error(`[@owdproject/nuxt] ${packageName}: extension meta.requires must be an array of non-empty module/kit names.`);
|
|
189
|
+
}
|
|
190
|
+
return { ...meta };
|
|
182
191
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
function assertDesktopExtensionHost(meta, modulePath, nuxt) {
|
|
193
|
+
if (meta.framework !== "nuxt") {
|
|
194
|
+
throw new Error(`[@owdproject/nuxt] ${modulePath}: framework "${meta.framework}" is incompatible with the Nuxt host.`);
|
|
195
|
+
}
|
|
196
|
+
if (meta.adapter !== void 0 && meta.adapter !== "@owdproject/nuxt") {
|
|
197
|
+
throw new Error(`[@owdproject/nuxt] ${modulePath}: adapter "${meta.adapter}" is incompatible with @owdproject/nuxt.`);
|
|
198
|
+
}
|
|
199
|
+
for (const requirement of meta.requires ?? []) {
|
|
200
|
+
const installed = nuxt.options._installedModules?.some(({ meta: meta2, entryPath }) => {
|
|
201
|
+
if (meta2.disabled) return false;
|
|
202
|
+
return [meta2.name, meta2.packageName, meta2.rawPath, entryPath].includes(requirement);
|
|
203
|
+
});
|
|
204
|
+
if (!installed) {
|
|
205
|
+
throw new Error(`[@owdproject/nuxt] ${modulePath}: required kit/module "${requirement}" is not active. Install it through the theme or host before this extension.`);
|
|
206
|
+
}
|
|
191
207
|
}
|
|
192
208
|
}
|
|
193
209
|
export async function installDesktopPackage(nuxt, modulePath, desktop) {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const inline = slice && typeof slice === "object" && !Array.isArray(slice) ? slice : {};
|
|
199
|
-
await installModule(modulePath, inline);
|
|
200
|
-
return;
|
|
210
|
+
const meta = await loadModuleMeta(modulePath, nuxt);
|
|
211
|
+
if (EXTENSION_META_KEYS.some((key) => key !== "configKey" && key in meta)) {
|
|
212
|
+
const extension = assertDesktopExtensionMeta(meta, modulePath);
|
|
213
|
+
assertDesktopExtensionHost(extension, modulePath, nuxt);
|
|
201
214
|
}
|
|
202
|
-
|
|
215
|
+
const configKey = typeof meta.configKey === "string" && meta.configKey.length > 0 ? meta.configKey : void 0;
|
|
216
|
+
const slice = configKey && desktop ? desktop[configKey] : void 0;
|
|
217
|
+
const inline = slice && typeof slice === "object" && !Array.isArray(slice) ? slice : void 0;
|
|
218
|
+
await installModule(modulePath, configKey && desktop ? inline ?? {} : void 0, nuxt);
|
|
203
219
|
}
|
|
204
220
|
export function defineDesktopModule(definition) {
|
|
205
221
|
const configKey = definition.meta.configKey;
|
package/dist/module.d.mts
CHANGED
|
@@ -78,6 +78,12 @@ interface DesktopConfig {
|
|
|
78
78
|
apps?: string[];
|
|
79
79
|
modules?: string[];
|
|
80
80
|
coreVersion?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Stable identity of this desktop distribution (e.g. `'atproto-os'`).
|
|
83
|
+
* Namespaces the persisted profile (AT Protocol record rkeys and local
|
|
84
|
+
* IndexedDB keys) so two different OWD products never share state.
|
|
85
|
+
*/
|
|
86
|
+
id?: string;
|
|
81
87
|
/** Directories a desktop distribution collects extensions from (see {@link DesktopExtensionDirs}). */
|
|
82
88
|
collect?: DesktopExtensionDirs;
|
|
83
89
|
[key: string]: any;
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { createDesktop, WindowManager, DesktopPointerSession, windowMachine, Ifr
|
|
|
8
8
|
export * from '@owdproject/core';
|
|
9
9
|
import { createActor } from 'xstate';
|
|
10
10
|
|
|
11
|
-
const version = "4.0.0-alpha.
|
|
11
|
+
const version = "4.0.0-alpha.4";
|
|
12
12
|
const pkg = {
|
|
13
13
|
version: version};
|
|
14
14
|
|
|
@@ -71,7 +71,42 @@ onMounted(() => {
|
|
|
71
71
|
if (windowRef.value) {
|
|
72
72
|
dragHandleRef.value = windowRef.value.querySelector(".desktop-window-nav__draggable");
|
|
73
73
|
}
|
|
74
|
+
clampIntoWorkarea();
|
|
74
75
|
});
|
|
76
|
+
function clampIntoWorkarea() {
|
|
77
|
+
const area = desktopWorkspaceStore.workarea;
|
|
78
|
+
if (!area || area.width <= 0 || area.height <= 0) return;
|
|
79
|
+
const state = windowController?.state;
|
|
80
|
+
if (!state || state.maximized) return;
|
|
81
|
+
const size = state.size ?? {};
|
|
82
|
+
const rect = windowRef.value?.getBoundingClientRect();
|
|
83
|
+
const width2 = typeof size.width === "number" ? size.width : rect?.width;
|
|
84
|
+
const height2 = typeof size.height === "number" ? size.height : rect?.height;
|
|
85
|
+
if (!width2 || !height2 || !Number.isFinite(width2) || !Number.isFinite(height2)) return;
|
|
86
|
+
const nextWidth = Math.min(width2, area.width);
|
|
87
|
+
const nextHeight = Math.min(height2, area.height);
|
|
88
|
+
const pos = state.position ?? {};
|
|
89
|
+
const maxX = area.x + area.width - nextWidth;
|
|
90
|
+
const maxY = area.y + area.height - nextHeight;
|
|
91
|
+
const nextX = Math.min(Math.max(pos.x ?? area.x, area.x), maxX);
|
|
92
|
+
const nextY = Math.min(Math.max(pos.y ?? area.y, area.y), maxY);
|
|
93
|
+
if (nextWidth !== width2 || nextHeight !== height2) {
|
|
94
|
+
windowController.actions.setSize({ width: nextWidth, height: nextHeight });
|
|
95
|
+
}
|
|
96
|
+
if (nextX !== pos.x || nextY !== pos.y) {
|
|
97
|
+
windowController.actions.setPosition({ x: nextX, y: nextY });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
watch(
|
|
101
|
+
() => [
|
|
102
|
+
desktopWorkspaceStore.workarea.x,
|
|
103
|
+
desktopWorkspaceStore.workarea.y,
|
|
104
|
+
desktopWorkspaceStore.workarea.width,
|
|
105
|
+
desktopWorkspaceStore.workarea.height
|
|
106
|
+
],
|
|
107
|
+
() => clampIntoWorkarea(),
|
|
108
|
+
{ immediate: true }
|
|
109
|
+
);
|
|
75
110
|
const { x, y } = useDraggable(windowRef, {
|
|
76
111
|
handle: dragHandleRef,
|
|
77
112
|
initialValue: {
|
|
@@ -102,7 +102,7 @@ export declare function useDesktop(): {
|
|
|
102
102
|
addWorkspace: (id?: string) => string;
|
|
103
103
|
removeWorkspace: (workspaceId: string) => boolean;
|
|
104
104
|
resolveWorkspaceFallback: (removedId: string) => string | null;
|
|
105
|
-
}, "
|
|
105
|
+
}, "list" | "active" | "overview" | "workarea">, Pick<{
|
|
106
106
|
list: import("vue").Ref<string[], string[]>;
|
|
107
107
|
active: import("vue").Ref<string, string>;
|
|
108
108
|
overview: import("vue").Ref<boolean, boolean>;
|
|
@@ -4,7 +4,7 @@ import { useApplicationWindowsStore } from '../../stores/storeApplicationWindows
|
|
|
4
4
|
export declare class ApplicationController implements IApplicationController {
|
|
5
5
|
readonly id: string;
|
|
6
6
|
readonly config: ApplicationConfig;
|
|
7
|
-
readonly windows: Map<string, IWindowController
|
|
7
|
+
readonly windows: import("vue").ShallowReactive<Map<string, IWindowController>>;
|
|
8
8
|
readonly openWindowCount: import("vue").ShallowRef<number, number>;
|
|
9
9
|
storeWindows: ReturnType<typeof useApplicationWindowsStore> | undefined;
|
|
10
10
|
storeMeta: any;
|
|
@@ -25,6 +25,13 @@ export declare class ApplicationController implements IApplicationController {
|
|
|
25
25
|
get isInitializing(): boolean;
|
|
26
26
|
get isReady(): boolean;
|
|
27
27
|
initApplication(): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Reopen the windows persisted in this application's window store. Returns
|
|
30
|
+
* `true` when at least one window was restored. No-op without a persistence
|
|
31
|
+
* adapter (the window store is then always empty at boot).
|
|
32
|
+
*/
|
|
33
|
+
restoreApplication(): Promise<boolean>;
|
|
34
|
+
private restoreWindows;
|
|
28
35
|
focusWindow(targetWin: IWindowController): void;
|
|
29
36
|
openWindow(model: string, storedState?: WindowStoredState, meta?: Record<string, any>): IWindowController | undefined;
|
|
30
37
|
closeWindow(windowId: string): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { markRaw, reactive, shallowRef } from "vue";
|
|
1
|
+
import { markRaw, reactive, shallowRef, shallowReactive } from "vue";
|
|
2
2
|
import { createActor } from "xstate";
|
|
3
3
|
import { nanoid } from "nanoid";
|
|
4
4
|
import {
|
|
@@ -30,7 +30,7 @@ function formatAppSnapshotValue(value) {
|
|
|
30
30
|
export class ApplicationController {
|
|
31
31
|
id;
|
|
32
32
|
config;
|
|
33
|
-
windows = /* @__PURE__ */ new Map();
|
|
33
|
+
windows = shallowReactive(/* @__PURE__ */ new Map());
|
|
34
34
|
openWindowCount = shallowRef(0);
|
|
35
35
|
storeWindows;
|
|
36
36
|
storeMeta;
|
|
@@ -156,6 +156,15 @@ export class ApplicationController {
|
|
|
156
156
|
this.config.provides.entry
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
|
+
const storeWindows = this.storeWindows;
|
|
160
|
+
if (storeWindows?.$persistedState) {
|
|
161
|
+
await storeWindows.$persistedState.isReady();
|
|
162
|
+
}
|
|
163
|
+
const storeMeta = this.storeMeta;
|
|
164
|
+
if (storeMeta?.$persistedState) {
|
|
165
|
+
await storeMeta.$persistedState.isReady();
|
|
166
|
+
}
|
|
167
|
+
await this.restoreApplication();
|
|
159
168
|
this.appActor.send({ type: "INITIALIZED" });
|
|
160
169
|
await this.config.onReady?.(this);
|
|
161
170
|
} catch (err) {
|
|
@@ -163,6 +172,33 @@ export class ApplicationController {
|
|
|
163
172
|
console.error(`[@owdproject/nuxt] Failed to init app "${this.id}":`, err);
|
|
164
173
|
}
|
|
165
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Reopen the windows persisted in this application's window store. Returns
|
|
177
|
+
* `true` when at least one window was restored. No-op without a persistence
|
|
178
|
+
* adapter (the window store is then always empty at boot).
|
|
179
|
+
*/
|
|
180
|
+
async restoreApplication() {
|
|
181
|
+
if (typeof this.config.onRestore === "function") {
|
|
182
|
+
await this.config.onRestore(this);
|
|
183
|
+
}
|
|
184
|
+
const windows = this.storeWindows?.windows;
|
|
185
|
+
if (!windows || Object.keys(windows).length === 0) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
this.restoreWindows();
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
restoreWindows() {
|
|
192
|
+
const windows = this.storeWindows?.windows;
|
|
193
|
+
if (!windows) return;
|
|
194
|
+
for (const windowId of Object.keys(windows)) {
|
|
195
|
+
if (this.windows.has(windowId)) continue;
|
|
196
|
+
const storedState = windows[windowId];
|
|
197
|
+
if (storedState?.model) {
|
|
198
|
+
this.openWindow(storedState.model, storedState);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
166
202
|
focusWindow(targetWin) {
|
|
167
203
|
const desktopStore = useDesktopStore();
|
|
168
204
|
let previousFocused;
|
|
@@ -7,13 +7,23 @@ export function applicationMetaStoreId(appId) {
|
|
|
7
7
|
export function useApplicationMetaStore(appId, customSetup) {
|
|
8
8
|
let storeFactory = metaStoreRegistry.get(appId);
|
|
9
9
|
if (!storeFactory) {
|
|
10
|
-
storeFactory = defineStore(
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
storeFactory = defineStore(
|
|
11
|
+
applicationMetaStoreId(appId),
|
|
12
|
+
() => {
|
|
13
|
+
if (customSetup) {
|
|
14
|
+
return customSetup();
|
|
15
|
+
}
|
|
16
|
+
const data = reactive({});
|
|
17
|
+
return { data };
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
// Inert unless an optional persistence module registers the adapter.
|
|
21
|
+
// @ts-expect-error pinia-plugin-persistedstate-2 store option
|
|
22
|
+
persistedState: {
|
|
23
|
+
persist: true
|
|
24
|
+
}
|
|
13
25
|
}
|
|
14
|
-
|
|
15
|
-
return { data };
|
|
16
|
-
});
|
|
26
|
+
);
|
|
17
27
|
metaStoreRegistry.set(appId, storeFactory);
|
|
18
28
|
}
|
|
19
29
|
return storeFactory;
|
|
@@ -7,26 +7,36 @@ export function applicationWindowsStoreId(appId) {
|
|
|
7
7
|
export function useApplicationWindowsStore(appId) {
|
|
8
8
|
let storeFactory = windowsStoreRegistry.get(appId);
|
|
9
9
|
if (!storeFactory) {
|
|
10
|
-
storeFactory = defineStore(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
windows
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
storeFactory = defineStore(
|
|
11
|
+
applicationWindowsStoreId(appId),
|
|
12
|
+
() => {
|
|
13
|
+
const windows = reactive({});
|
|
14
|
+
function setWindowState(windowId, stored) {
|
|
15
|
+
windows[windowId] = stored;
|
|
16
|
+
}
|
|
17
|
+
function removeWindowState(windowId) {
|
|
18
|
+
delete windows[windowId];
|
|
19
|
+
}
|
|
20
|
+
function clearWindows() {
|
|
21
|
+
for (const key of Object.keys(windows)) {
|
|
22
|
+
delete windows[key];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
windows,
|
|
27
|
+
setWindowState,
|
|
28
|
+
removeWindowState,
|
|
29
|
+
clearWindows
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
// Inert unless an optional persistence module registers the adapter.
|
|
34
|
+
// @ts-expect-error pinia-plugin-persistedstate-2 store option
|
|
35
|
+
persistedState: {
|
|
36
|
+
persist: true
|
|
21
37
|
}
|
|
22
38
|
}
|
|
23
|
-
|
|
24
|
-
windows,
|
|
25
|
-
setWindowState,
|
|
26
|
-
removeWindowState,
|
|
27
|
-
clearWindows
|
|
28
|
-
};
|
|
29
|
-
});
|
|
39
|
+
);
|
|
30
40
|
windowsStoreRegistry.set(appId, storeFactory);
|
|
31
41
|
}
|
|
32
42
|
return storeFactory();
|
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import { reactive } from "vue";
|
|
2
2
|
import { defineStore } from "pinia";
|
|
3
|
-
export const useDesktopStore = defineStore(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
export const useDesktopStore = defineStore(
|
|
4
|
+
"desktop",
|
|
5
|
+
() => {
|
|
6
|
+
const state = reactive({
|
|
7
|
+
defaultApps: {},
|
|
8
|
+
positionZ: 100
|
|
9
|
+
});
|
|
10
|
+
function incrementPositionZ() {
|
|
11
|
+
state.positionZ += 1;
|
|
12
|
+
return state.positionZ;
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
state,
|
|
16
|
+
incrementPositionZ
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
// Inert unless an optional persistence module (@owdproject/nuxt-module-pinia-persistence
|
|
21
|
+
// or @owdproject/nuxt-module-atproto-persistence) registers the adapter.
|
|
22
|
+
// @ts-expect-error pinia-plugin-persistedstate-2 store option
|
|
23
|
+
persistedState: {
|
|
24
|
+
persist: true,
|
|
25
|
+
// `defaultApps` is rebuilt from config on boot; persist only the z-index seed.
|
|
26
|
+
includePaths: ["state.positionZ"]
|
|
27
|
+
}
|
|
11
28
|
}
|
|
12
|
-
|
|
13
|
-
state,
|
|
14
|
-
incrementPositionZ
|
|
15
|
-
};
|
|
16
|
-
});
|
|
29
|
+
);
|
|
@@ -8,84 +8,96 @@ export const EMPTY_DESKTOP_WORKAREA = {
|
|
|
8
8
|
width: 0,
|
|
9
9
|
height: 0
|
|
10
10
|
};
|
|
11
|
-
export const useDesktopWorkspaceStore = defineStore(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!list.value.includes(workspaceId)) return false;
|
|
26
|
-
const previous = active.value;
|
|
27
|
-
if (previous === workspaceId) return true;
|
|
28
|
-
const { callHook } = useDesktopHooks();
|
|
29
|
-
const switchCtx = {
|
|
30
|
-
from: previous,
|
|
31
|
-
to: workspaceId,
|
|
32
|
-
cancel: false
|
|
33
|
-
};
|
|
34
|
-
void callHook("workspace:before-switch", switchCtx);
|
|
35
|
-
if (switchCtx.cancel) return false;
|
|
36
|
-
active.value = switchCtx.to;
|
|
37
|
-
void callHook("workspace:switched", { from: previous, to: switchCtx.to });
|
|
38
|
-
useDesktopIPC().emit(
|
|
39
|
-
"workspace:switched",
|
|
40
|
-
{ from: previous, to: switchCtx.to },
|
|
41
|
-
{ source: "workspace-store", severity: "info" }
|
|
11
|
+
export const useDesktopWorkspaceStore = defineStore(
|
|
12
|
+
"desktop/workspace",
|
|
13
|
+
() => {
|
|
14
|
+
const list = ref([
|
|
15
|
+
"workspace-1",
|
|
16
|
+
"workspace-2",
|
|
17
|
+
"workspace-3",
|
|
18
|
+
"workspace-4"
|
|
19
|
+
]);
|
|
20
|
+
const active = ref("workspace-1");
|
|
21
|
+
const overview = ref(false);
|
|
22
|
+
const workarea = ref({ ...EMPTY_DESKTOP_WORKAREA });
|
|
23
|
+
const workspaceActiveIndex = computed(
|
|
24
|
+
() => list.value.indexOf(active.value)
|
|
42
25
|
);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
26
|
+
function setWorkspace(workspaceId) {
|
|
27
|
+
if (!list.value.includes(workspaceId)) return false;
|
|
28
|
+
const previous = active.value;
|
|
29
|
+
if (previous === workspaceId) return true;
|
|
30
|
+
const { callHook } = useDesktopHooks();
|
|
31
|
+
const switchCtx = {
|
|
32
|
+
from: previous,
|
|
33
|
+
to: workspaceId,
|
|
34
|
+
cancel: false
|
|
35
|
+
};
|
|
36
|
+
void callHook("workspace:before-switch", switchCtx);
|
|
37
|
+
if (switchCtx.cancel) return false;
|
|
38
|
+
active.value = switchCtx.to;
|
|
39
|
+
void callHook("workspace:switched", { from: previous, to: switchCtx.to });
|
|
40
|
+
useDesktopIPC().emit(
|
|
41
|
+
"workspace:switched",
|
|
42
|
+
{ from: previous, to: switchCtx.to },
|
|
43
|
+
{ source: "workspace-store", severity: "info" }
|
|
44
|
+
);
|
|
45
|
+
return true;
|
|
58
46
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
47
|
+
function setOverview(value) {
|
|
48
|
+
overview.value = value;
|
|
49
|
+
}
|
|
50
|
+
function toggleOverview() {
|
|
51
|
+
overview.value = !overview.value;
|
|
52
|
+
}
|
|
53
|
+
function setWorkarea(rect) {
|
|
54
|
+
workarea.value = { ...rect };
|
|
55
|
+
}
|
|
56
|
+
function addWorkspace(id) {
|
|
57
|
+
const nextId = id || `workspace-${list.value.length + 1}`;
|
|
58
|
+
if (!list.value.includes(nextId)) {
|
|
59
|
+
list.value.push(nextId);
|
|
60
|
+
}
|
|
61
|
+
return nextId;
|
|
62
|
+
}
|
|
63
|
+
function resolveWorkspaceFallback(removedId) {
|
|
64
|
+
const remaining = list.value.filter((id) => id !== removedId);
|
|
65
|
+
if (remaining.length === 0) return null;
|
|
66
|
+
return remaining[remaining.length - 1] ?? remaining[0] ?? null;
|
|
67
|
+
}
|
|
68
|
+
function removeWorkspace(workspaceId) {
|
|
69
|
+
if (list.value.length <= 1) return false;
|
|
70
|
+
const idx = list.value.indexOf(workspaceId);
|
|
71
|
+
if (idx < 0) return false;
|
|
72
|
+
const fallback = resolveWorkspaceFallback(workspaceId);
|
|
73
|
+
list.value.splice(idx, 1);
|
|
74
|
+
if (active.value === workspaceId && fallback) {
|
|
75
|
+
active.value = fallback;
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
list,
|
|
81
|
+
active,
|
|
82
|
+
overview,
|
|
83
|
+
workarea,
|
|
84
|
+
workspaceActiveIndex,
|
|
85
|
+
setWorkspace,
|
|
86
|
+
setOverview,
|
|
87
|
+
toggleOverview,
|
|
88
|
+
setWorkarea,
|
|
89
|
+
addWorkspace,
|
|
90
|
+
removeWorkspace,
|
|
91
|
+
resolveWorkspaceFallback
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
// Inert unless an optional persistence module registers the adapter.
|
|
96
|
+
// @ts-expect-error pinia-plugin-persistedstate-2 store option
|
|
97
|
+
persistedState: {
|
|
98
|
+
persist: true,
|
|
99
|
+
// `overview` and `workarea` are runtime-only.
|
|
100
|
+
includePaths: ["list", "active"]
|
|
74
101
|
}
|
|
75
|
-
return true;
|
|
76
102
|
}
|
|
77
|
-
|
|
78
|
-
list,
|
|
79
|
-
active,
|
|
80
|
-
overview,
|
|
81
|
-
workarea,
|
|
82
|
-
workspaceActiveIndex,
|
|
83
|
-
setWorkspace,
|
|
84
|
-
setOverview,
|
|
85
|
-
toggleOverview,
|
|
86
|
-
setWorkarea,
|
|
87
|
-
addWorkspace,
|
|
88
|
-
removeWorkspace,
|
|
89
|
-
resolveWorkspaceFallback
|
|
90
|
-
};
|
|
91
|
-
});
|
|
103
|
+
);
|
|
@@ -50,6 +50,13 @@ export const useDesktopNotificationStore = defineStore(
|
|
|
50
50
|
markAsRead,
|
|
51
51
|
markAllAsRead
|
|
52
52
|
};
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
// Inert unless an optional persistence module registers the adapter.
|
|
56
|
+
// @ts-expect-error pinia-plugin-persistedstate-2 store option
|
|
57
|
+
persistedState: {
|
|
58
|
+
persist: true
|
|
59
|
+
}
|
|
53
60
|
}
|
|
54
61
|
);
|
|
55
62
|
export const useNotificationStore = useDesktopNotificationStore;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -76,6 +76,12 @@ export interface DesktopConfig {
|
|
|
76
76
|
apps?: string[];
|
|
77
77
|
modules?: string[];
|
|
78
78
|
coreVersion?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Stable identity of this desktop distribution (e.g. `'atproto-os'`).
|
|
81
|
+
* Namespaces the persisted profile (AT Protocol record rkeys and local
|
|
82
|
+
* IndexedDB keys) so two different OWD products never share state.
|
|
83
|
+
*/
|
|
84
|
+
id?: string;
|
|
79
85
|
/** Directories a desktop distribution collects extensions from (see {@link DesktopExtensionDirs}). */
|
|
80
86
|
collect?: DesktopExtensionDirs;
|
|
81
87
|
[key: string]: any;
|
|
@@ -115,7 +121,7 @@ export interface DesktopExtensionMeta {
|
|
|
115
121
|
entry?: string;
|
|
116
122
|
/** Framework adapter required at runtime, e.g. `@owdproject/nuxt`. */
|
|
117
123
|
adapter?: string;
|
|
118
|
-
/**
|
|
124
|
+
/** Exact identities of kits/modules already installed by the active Nuxt host; no version ranges. */
|
|
119
125
|
requires?: string[];
|
|
120
126
|
}
|
|
121
127
|
export interface IWindowController extends IDesktopWindowController {
|
|
@@ -138,6 +144,8 @@ export interface IApplicationController {
|
|
|
138
144
|
readonly isInitializing: boolean;
|
|
139
145
|
readonly isReady: boolean;
|
|
140
146
|
initApplication(): Promise<void>;
|
|
147
|
+
/** Restore saved windows without duplicating already-open controllers. */
|
|
148
|
+
restoreApplication(): Promise<boolean>;
|
|
141
149
|
openWindow(model: string, storedState?: import('@owdproject/core').WindowStoredState, meta?: Record<string, any>): IWindowController | undefined;
|
|
142
150
|
getFirstWindowByModel(model: string): IWindowController | undefined;
|
|
143
151
|
closeWindow(windowId: string): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owdproject/nuxt",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.4",
|
|
4
4
|
"description": "Official Nuxt & Vue desktop engine for OpenWebDesktop",
|
|
5
5
|
"homepage": "https://github.com/owdproject/nuxt#readme",
|
|
6
6
|
"repository": {
|
|
@@ -112,7 +112,8 @@
|
|
|
112
112
|
},
|
|
113
113
|
"scripts": {
|
|
114
114
|
"build": "nuxt-module-build build && node ./scripts/post-build-pkg-nuxt.mjs",
|
|
115
|
-
"prepack": "nuxt-module-build build && node ./scripts/post-build-pkg-nuxt.mjs"
|
|
115
|
+
"prepack": "nuxt-module-build build && node ./scripts/post-build-pkg-nuxt.mjs",
|
|
116
|
+
"test:persistence": "vitest run test/config.test.ts test/persistence-windows.test.ts"
|
|
116
117
|
},
|
|
117
118
|
"dependencies": {
|
|
118
119
|
"@nuxt/fonts": "^1.2.0",
|
|
@@ -143,5 +144,10 @@
|
|
|
143
144
|
"nuxt": {
|
|
144
145
|
"optional": true
|
|
145
146
|
}
|
|
147
|
+
},
|
|
148
|
+
"devDependencies": {
|
|
149
|
+
"vitest": "^4.1.12",
|
|
150
|
+
"pinia-plugin-persistedstate-2": "^2.0.30",
|
|
151
|
+
"vue": "^3.5.44"
|
|
146
152
|
}
|
|
147
153
|
}
|