@antelopejs/dms-frontend 0.0.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.
- package/LICENSE +190 -0
- package/README.md +131 -0
- package/dist/commands/build.js +66 -0
- package/dist/commands/clean.js +42 -0
- package/dist/commands/dev.js +116 -0
- package/dist/commands/prepare.js +60 -0
- package/dist/commands/start.js +49 -0
- package/dist/commands/verify-source.js +39 -0
- package/dist/common.js +24 -0
- package/dist/config.js +142 -0
- package/dist/discovery.js +123 -0
- package/dist/fs-sync.js +142 -0
- package/dist/index.js +44 -0
- package/dist/layer-watch.js +154 -0
- package/dist/layers.js +120 -0
- package/dist/manifest.js +109 -0
- package/dist/materialize.js +249 -0
- package/dist/ports.js +30 -0
- package/dist/update-check.js +173 -0
- package/dist/utils/cli-ui.js +178 -0
- package/dist/verify-source-runner.js +228 -0
- package/dist/workspace-setup.js +109 -0
- package/dist/workspace.js +76 -0
- package/package.json +97 -0
- package/templates/vue/DmsDynamicPage.vue +89 -0
- package/templates/vue/app-config-stub.mjs +1 -0
- package/templates/vue/app-runtime.ts +240 -0
- package/templates/vue/compress-assets.mjs +48 -0
- package/templates/vue/email-locales.ts +32 -0
- package/templates/vue/email-renderer.ts +159 -0
- package/templates/vue/email-runtime.ts +23 -0
- package/templates/vue/frontend-module.ts +1418 -0
- package/templates/vue/globals.d.ts +1 -0
- package/templates/vue/index.html +24 -0
- package/templates/vue/main.ts +33 -0
- package/templates/vue/npmrc +2 -0
- package/templates/vue/package.json +35 -0
- package/templates/vue/pnpm-workspace.yaml +4 -0
- package/templates/vue/server/auth/backend.mjs +83 -0
- package/templates/vue/server/auth/client-ip.mjs +52 -0
- package/templates/vue/server/auth/oauth.mjs +213 -0
- package/templates/vue/server/auth/routes.mjs +254 -0
- package/templates/vue/server/auth/session.mjs +180 -0
- package/templates/vue/server/client-manifest.mjs +116 -0
- package/templates/vue/server/email.mjs +36 -0
- package/templates/vue/server/inertia.mjs +79 -0
- package/templates/vue/server/render-token.mjs +81 -0
- package/templates/vue/server/tester.mjs +228 -0
- package/templates/vue/server.mjs +526 -0
- package/templates/vue/ssr-renderer.ts +146 -0
- package/templates/vue/tsconfig.json +31 -0
- package/templates/vue/typecheck-loader.mjs +13 -0
- package/templates/vue/vite.config.ts +161 -0
- package/templates/vue/vite.email.config.ts +77 -0
|
@@ -0,0 +1,1418 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Link as InertiaLink,
|
|
3
|
+
type InertiaLinkProps,
|
|
4
|
+
router as inertiaRouter,
|
|
5
|
+
usePage,
|
|
6
|
+
} from "@inertiajs/vue3";
|
|
7
|
+
import {
|
|
8
|
+
useHead as useUnhead,
|
|
9
|
+
useSeoMeta as useUnheadSeoMeta,
|
|
10
|
+
} from "@unhead/vue";
|
|
11
|
+
import { defu } from "defu";
|
|
12
|
+
import { type FetchOptions, ofetch } from "ofetch";
|
|
13
|
+
import {
|
|
14
|
+
type App,
|
|
15
|
+
type Component,
|
|
16
|
+
type ComputedRef,
|
|
17
|
+
computed,
|
|
18
|
+
defineComponent,
|
|
19
|
+
getCurrentInstance,
|
|
20
|
+
getCurrentScope,
|
|
21
|
+
h,
|
|
22
|
+
hasInjectionContext,
|
|
23
|
+
type InjectionKey,
|
|
24
|
+
inject,
|
|
25
|
+
onMounted,
|
|
26
|
+
onScopeDispose,
|
|
27
|
+
type Plugin,
|
|
28
|
+
type PropType,
|
|
29
|
+
type Ref,
|
|
30
|
+
reactive,
|
|
31
|
+
ref,
|
|
32
|
+
watch,
|
|
33
|
+
} from "vue";
|
|
34
|
+
import type { Composer } from "vue-i18n";
|
|
35
|
+
import { useI18n as useVueI18n } from "vue-i18n";
|
|
36
|
+
|
|
37
|
+
export interface DmsPageRoute {
|
|
38
|
+
displayName?: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
fullSlug?: string;
|
|
41
|
+
icon?: string;
|
|
42
|
+
layoutUrl?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface DmsPagePayload {
|
|
45
|
+
route?: DmsPageRoute;
|
|
46
|
+
componentName?: string;
|
|
47
|
+
layoutUrl?: string;
|
|
48
|
+
layout?: DmsPageLayout;
|
|
49
|
+
shared?: DmsSharedPagePayload;
|
|
50
|
+
[key: string]: unknown;
|
|
51
|
+
}
|
|
52
|
+
export interface DmsSharedPagePayload {
|
|
53
|
+
siteLayout?: unknown;
|
|
54
|
+
siteLayoutTree?: unknown;
|
|
55
|
+
quickActions?: unknown;
|
|
56
|
+
modules?: unknown;
|
|
57
|
+
isOwner?: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface DmsLayoutDefinition {
|
|
60
|
+
componentName?: string;
|
|
61
|
+
options?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
export interface DmsPageLayout {
|
|
64
|
+
componentName?: string;
|
|
65
|
+
layout?: DmsLayoutDefinition;
|
|
66
|
+
[key: string]: unknown;
|
|
67
|
+
}
|
|
68
|
+
export interface DmsPageProps {
|
|
69
|
+
path: string;
|
|
70
|
+
page: DmsPagePayload;
|
|
71
|
+
user?: DmsUser;
|
|
72
|
+
session?: DmsSession;
|
|
73
|
+
error?: DmsErrorData;
|
|
74
|
+
}
|
|
75
|
+
export interface PublicRuntimeConfig extends Record<string, unknown> {}
|
|
76
|
+
export interface RuntimeConfig extends Record<string, unknown> {}
|
|
77
|
+
export interface DmsAppConfig extends Record<string, unknown> {}
|
|
78
|
+
export interface DmsUser extends Record<string, unknown> {}
|
|
79
|
+
export interface DmsSession {
|
|
80
|
+
accountId: string;
|
|
81
|
+
activeTenantId?: string;
|
|
82
|
+
}
|
|
83
|
+
export interface DmsRuntimeConfig extends RuntimeConfig {
|
|
84
|
+
public: PublicRuntimeConfig;
|
|
85
|
+
}
|
|
86
|
+
export interface DmsModuleOptions {
|
|
87
|
+
public: Record<string, unknown>;
|
|
88
|
+
}
|
|
89
|
+
export type DmsLocaleLoader = (
|
|
90
|
+
locale: string,
|
|
91
|
+
) => Promise<Record<string, unknown>>;
|
|
92
|
+
export type DmsNavigationOptions = Pick<
|
|
93
|
+
InertiaLinkProps,
|
|
94
|
+
| "async"
|
|
95
|
+
| "component"
|
|
96
|
+
| "data"
|
|
97
|
+
| "except"
|
|
98
|
+
| "headers"
|
|
99
|
+
| "method"
|
|
100
|
+
| "onBefore"
|
|
101
|
+
| "onCancel"
|
|
102
|
+
| "onCancelToken"
|
|
103
|
+
| "onError"
|
|
104
|
+
| "onFinish"
|
|
105
|
+
| "onProgress"
|
|
106
|
+
| "onStart"
|
|
107
|
+
| "onSuccess"
|
|
108
|
+
| "only"
|
|
109
|
+
| "preserveScroll"
|
|
110
|
+
| "preserveState"
|
|
111
|
+
| "preserveUrl"
|
|
112
|
+
| "queryStringArrayFormat"
|
|
113
|
+
| "replace"
|
|
114
|
+
| "viewTransition"
|
|
115
|
+
>;
|
|
116
|
+
export interface RouteLocationObject {
|
|
117
|
+
path?: string;
|
|
118
|
+
query?: Record<string, unknown>;
|
|
119
|
+
hash?: string;
|
|
120
|
+
}
|
|
121
|
+
export type LocationQueryValue = string | null;
|
|
122
|
+
export type LocationQuery = Record<
|
|
123
|
+
string,
|
|
124
|
+
LocationQueryValue | LocationQueryValue[]
|
|
125
|
+
>;
|
|
126
|
+
export type LocationQueryRaw = Record<
|
|
127
|
+
string,
|
|
128
|
+
| LocationQueryValue
|
|
129
|
+
| number
|
|
130
|
+
| undefined
|
|
131
|
+
| (LocationQueryValue | number | undefined)[]
|
|
132
|
+
>;
|
|
133
|
+
export type RouteLocationRaw = string | RouteLocationObject;
|
|
134
|
+
export interface DmsRoute {
|
|
135
|
+
name?: string;
|
|
136
|
+
fullPath: string;
|
|
137
|
+
path: string;
|
|
138
|
+
query: Record<string, string>;
|
|
139
|
+
params: Record<string, string>;
|
|
140
|
+
meta: Record<string, unknown>;
|
|
141
|
+
matched: unknown[];
|
|
142
|
+
}
|
|
143
|
+
export interface DmsRouter {
|
|
144
|
+
currentRoute: Ref<DmsRoute>;
|
|
145
|
+
push(to: RouteLocationRaw, options?: DmsNavigationOptions): Promise<void>;
|
|
146
|
+
replace(to: RouteLocationRaw, options?: DmsNavigationOptions): Promise<void>;
|
|
147
|
+
back(): void;
|
|
148
|
+
}
|
|
149
|
+
export interface DmsAsyncData<T> {
|
|
150
|
+
data: Ref<T | null>;
|
|
151
|
+
error: Ref<unknown>;
|
|
152
|
+
pending: Ref<boolean>;
|
|
153
|
+
status: Ref<"idle" | "pending" | "success" | "error">;
|
|
154
|
+
execute(): Promise<void>;
|
|
155
|
+
refresh(): Promise<void>;
|
|
156
|
+
}
|
|
157
|
+
export interface UseFetchOptions<T> extends FetchOptions {
|
|
158
|
+
immediate?: boolean;
|
|
159
|
+
watch?: false | unknown[];
|
|
160
|
+
default?: () => T;
|
|
161
|
+
transform?: (value: unknown) => T | Promise<T>;
|
|
162
|
+
pick?: string[];
|
|
163
|
+
$fetch?: typeof ofetch;
|
|
164
|
+
}
|
|
165
|
+
export interface UseAsyncDataOptions<T> {
|
|
166
|
+
default?: () => T;
|
|
167
|
+
immediate?: boolean;
|
|
168
|
+
lazy?: boolean;
|
|
169
|
+
server?: boolean;
|
|
170
|
+
watch?: false | unknown[];
|
|
171
|
+
transform?: (value: unknown) => T | Promise<T>;
|
|
172
|
+
pick?: string[];
|
|
173
|
+
}
|
|
174
|
+
export interface DmsErrorData {
|
|
175
|
+
statusCode?: number;
|
|
176
|
+
statusMessage?: string;
|
|
177
|
+
message?: string;
|
|
178
|
+
data?: unknown;
|
|
179
|
+
fatal?: boolean;
|
|
180
|
+
}
|
|
181
|
+
export interface DmsUserSession<TUser = DmsUser, TSession = DmsSession> {
|
|
182
|
+
user: Ref<TUser | null>;
|
|
183
|
+
session: Ref<TSession | null>;
|
|
184
|
+
loggedIn: Ref<boolean>;
|
|
185
|
+
fetch(): Promise<void>;
|
|
186
|
+
clear(): Promise<void>;
|
|
187
|
+
}
|
|
188
|
+
export interface DmsCookieOptions<T> {
|
|
189
|
+
default?: () => T;
|
|
190
|
+
maxAge?: number;
|
|
191
|
+
path?: string;
|
|
192
|
+
sameSite?: "strict" | "lax" | "none";
|
|
193
|
+
secure?: boolean;
|
|
194
|
+
}
|
|
195
|
+
export type DmsColorModePreference = "system" | "light" | "dark";
|
|
196
|
+
type DmsLinkPrefetchMode = "mount" | "hover" | "click";
|
|
197
|
+
type DmsLinkPrefetch = boolean | DmsLinkPrefetchMode | DmsLinkPrefetchMode[];
|
|
198
|
+
export interface DmsColorMode {
|
|
199
|
+
preference: DmsColorModePreference;
|
|
200
|
+
value: "light" | "dark";
|
|
201
|
+
unknown: boolean;
|
|
202
|
+
forced: boolean;
|
|
203
|
+
}
|
|
204
|
+
export interface DmsAppContext {
|
|
205
|
+
provide<T>(key: string, value: T): void;
|
|
206
|
+
vueApp: App;
|
|
207
|
+
runWithContext<T>(callback: () => T): T;
|
|
208
|
+
hook(name: string, callback: () => void | Promise<void>): void;
|
|
209
|
+
$i18n: DmsI18n;
|
|
210
|
+
}
|
|
211
|
+
export interface DmsLocale {
|
|
212
|
+
code: string;
|
|
213
|
+
name?: string;
|
|
214
|
+
}
|
|
215
|
+
export type DmsI18n = Composer & {
|
|
216
|
+
locales: ComputedRef<DmsLocale[]>;
|
|
217
|
+
setLocale(locale: string): Promise<void>;
|
|
218
|
+
};
|
|
219
|
+
export type DmsPluginSetup = (context: DmsAppContext) => void | Promise<void>;
|
|
220
|
+
export interface DmsMiddlewareRegistrationOptions {
|
|
221
|
+
global?: boolean;
|
|
222
|
+
}
|
|
223
|
+
export interface DmsPluginRegistrationOptions {
|
|
224
|
+
clientOnly?: boolean;
|
|
225
|
+
}
|
|
226
|
+
export type DmsComponentPreloader = () => Promise<unknown>;
|
|
227
|
+
export interface DmsFrontendSdk {
|
|
228
|
+
options: DmsModuleOptions;
|
|
229
|
+
registerComponent(name: string, component: Component): void;
|
|
230
|
+
registerPage(
|
|
231
|
+
name: string,
|
|
232
|
+
component: Component,
|
|
233
|
+
preload?: DmsComponentPreloader,
|
|
234
|
+
): void;
|
|
235
|
+
registerDynamicPage(
|
|
236
|
+
name: string,
|
|
237
|
+
component: Component,
|
|
238
|
+
preload?: DmsComponentPreloader,
|
|
239
|
+
): void;
|
|
240
|
+
registerLayout(
|
|
241
|
+
name: string,
|
|
242
|
+
component: Component,
|
|
243
|
+
preload?: DmsComponentPreloader,
|
|
244
|
+
): void;
|
|
245
|
+
registerErrorPage(
|
|
246
|
+
component: Component,
|
|
247
|
+
preload?: DmsComponentPreloader,
|
|
248
|
+
): void;
|
|
249
|
+
registerPlugin(
|
|
250
|
+
setup: DmsPluginSetup,
|
|
251
|
+
options?: DmsPluginRegistrationOptions,
|
|
252
|
+
): void;
|
|
253
|
+
registerMiddleware(
|
|
254
|
+
name: string,
|
|
255
|
+
handler: DmsMiddleware,
|
|
256
|
+
options?: DmsMiddlewareRegistrationOptions,
|
|
257
|
+
): void;
|
|
258
|
+
provide<T>(key: string | InjectionKey<T>, value: T): void;
|
|
259
|
+
use(plugin: Plugin): void;
|
|
260
|
+
}
|
|
261
|
+
export interface DmsFrontendModule {
|
|
262
|
+
setup(sdk: DmsFrontendSdk): void | Promise<void>;
|
|
263
|
+
}
|
|
264
|
+
export interface DmsFrontendModuleRegistration {
|
|
265
|
+
module: DmsFrontendModule;
|
|
266
|
+
options: DmsModuleOptions;
|
|
267
|
+
}
|
|
268
|
+
// Async middleware without a redirect naturally infers Promise<void>.
|
|
269
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: preserve that valid handler signature.
|
|
270
|
+
export type DmsMiddlewareResult = void | false | RouteLocationRaw;
|
|
271
|
+
export type DmsMiddleware = (
|
|
272
|
+
to: DmsRoute,
|
|
273
|
+
from: DmsRoute,
|
|
274
|
+
) => DmsMiddlewareResult | Promise<DmsMiddlewareResult>;
|
|
275
|
+
export type DmsRuntimeHook = (...args: unknown[]) => void | Promise<void>;
|
|
276
|
+
export interface DmsRuntimeHooks {
|
|
277
|
+
hook(name: string, callback: DmsRuntimeHook): () => void;
|
|
278
|
+
callHook(name: string, ...args: unknown[]): Promise<void>;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
interface DmsComponentRegistration {
|
|
282
|
+
name: string;
|
|
283
|
+
component: Component;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
interface DmsFrontendEntry {
|
|
287
|
+
component: Component;
|
|
288
|
+
preload?: DmsComponentPreloader;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
interface DmsAsyncComponent {
|
|
292
|
+
__asyncLoader?: DmsComponentPreloader;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
interface DmsPluginRegistration {
|
|
296
|
+
setup: DmsPluginSetup;
|
|
297
|
+
clientOnly: boolean;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export interface DmsFrontendRuntime {
|
|
301
|
+
sharedState: Map<string, Ref<unknown>>;
|
|
302
|
+
asyncData: Map<string, DmsAsyncData<unknown>>;
|
|
303
|
+
asyncDataPromises: Map<string, Promise<void>>;
|
|
304
|
+
hydratedAsyncData: Map<string, unknown>;
|
|
305
|
+
runtimeHooks: Map<string, DmsRuntimeHook[]>;
|
|
306
|
+
serverFetch?: typeof ofetch;
|
|
307
|
+
currentError: Ref<Error | null>;
|
|
308
|
+
route: DmsRoute;
|
|
309
|
+
currentRoutePattern?: string;
|
|
310
|
+
appContext?: DmsAppContext;
|
|
311
|
+
isServer: boolean;
|
|
312
|
+
serverRedirect?: string;
|
|
313
|
+
pendingNavigation?: Promise<void>;
|
|
314
|
+
hasNavigationListener: boolean;
|
|
315
|
+
pageVersion: number;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const components = new Map<string, DmsComponentRegistration>();
|
|
319
|
+
const pages = new Map<string, DmsFrontendEntry>();
|
|
320
|
+
const dynamicPages = new Map<string, DmsFrontendEntry>();
|
|
321
|
+
const layouts = new Map<string, DmsFrontendEntry>();
|
|
322
|
+
let errorPage: DmsFrontendEntry | undefined;
|
|
323
|
+
const plugins: Plugin[] = [];
|
|
324
|
+
const pluginSetups: DmsPluginRegistration[] = [];
|
|
325
|
+
const middleware: DmsMiddleware[] = [];
|
|
326
|
+
const namedMiddleware = new Map<string, DmsMiddleware>();
|
|
327
|
+
const injections = new Map<string | symbol, unknown>();
|
|
328
|
+
const runtimeConfig = ref<DmsRuntimeConfig>({
|
|
329
|
+
public: {},
|
|
330
|
+
} as DmsRuntimeConfig);
|
|
331
|
+
const appConfig = ref<Record<string, unknown>>({});
|
|
332
|
+
const COLOR_MODE_STORAGE_KEY = "dms-color-mode";
|
|
333
|
+
const COLOR_MODE_CLASSES = ["light", "dark"];
|
|
334
|
+
const colorMode = reactive<DmsColorMode>({
|
|
335
|
+
preference: "system",
|
|
336
|
+
value: "light",
|
|
337
|
+
unknown: false,
|
|
338
|
+
forced: false,
|
|
339
|
+
});
|
|
340
|
+
let isColorModeInitialized = false;
|
|
341
|
+
const DMS_RUNTIME_KEY: InjectionKey<DmsFrontendRuntime> = Symbol("dms-runtime");
|
|
342
|
+
type DmsRuntimeContext = DmsAppContext["runWithContext"];
|
|
343
|
+
const runtimeContexts = new WeakMap<DmsFrontendRuntime, DmsRuntimeContext>();
|
|
344
|
+
let serverRuntimeResolver: (() => DmsFrontendRuntime | undefined) | undefined;
|
|
345
|
+
let browserRuntime: DmsFrontendRuntime | undefined;
|
|
346
|
+
|
|
347
|
+
/** Creates mutable runtime state owned by one Vue application. */
|
|
348
|
+
export function createDmsFrontendRuntime(
|
|
349
|
+
serverFetch?: typeof ofetch,
|
|
350
|
+
hydratedAsyncData: Record<string, unknown> = {},
|
|
351
|
+
isServer = false,
|
|
352
|
+
): DmsFrontendRuntime {
|
|
353
|
+
return {
|
|
354
|
+
sharedState: new Map(),
|
|
355
|
+
asyncData: new Map(),
|
|
356
|
+
asyncDataPromises: new Map(),
|
|
357
|
+
hydratedAsyncData: new Map(Object.entries(hydratedAsyncData)),
|
|
358
|
+
runtimeHooks: new Map(),
|
|
359
|
+
serverFetch,
|
|
360
|
+
isServer,
|
|
361
|
+
currentError: ref<Error | null>(null),
|
|
362
|
+
route: reactive<DmsRoute>({
|
|
363
|
+
fullPath: "/",
|
|
364
|
+
path: "/",
|
|
365
|
+
query: {},
|
|
366
|
+
params: {},
|
|
367
|
+
meta: {},
|
|
368
|
+
matched: [],
|
|
369
|
+
}),
|
|
370
|
+
hasNavigationListener: false,
|
|
371
|
+
pageVersion: 0,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Makes a runtime available before application plugins are installed. */
|
|
376
|
+
export function provideDmsFrontendRuntime(
|
|
377
|
+
app: App,
|
|
378
|
+
runtime: DmsFrontendRuntime,
|
|
379
|
+
): void {
|
|
380
|
+
app.provide(DMS_RUNTIME_KEY, runtime);
|
|
381
|
+
runtimeContexts.set(runtime, (callback) => app.runWithContext(callback));
|
|
382
|
+
if (typeof window !== "undefined") browserRuntime = runtime;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** Registers the request-local runtime resolver used by the SSR entry point. */
|
|
386
|
+
export function setDmsServerRuntimeResolver(
|
|
387
|
+
resolver: () => DmsFrontendRuntime | undefined,
|
|
388
|
+
): void {
|
|
389
|
+
serverRuntimeResolver = resolver;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function useDmsRuntime(): DmsFrontendRuntime {
|
|
393
|
+
const instance = getCurrentInstance();
|
|
394
|
+
const provided = instance?.appContext.provides[DMS_RUNTIME_KEY as symbol] as
|
|
395
|
+
| DmsFrontendRuntime
|
|
396
|
+
| undefined;
|
|
397
|
+
if (provided) return provided;
|
|
398
|
+
const contextual = hasInjectionContext()
|
|
399
|
+
? inject(DMS_RUNTIME_KEY, undefined)
|
|
400
|
+
: undefined;
|
|
401
|
+
if (contextual) return contextual;
|
|
402
|
+
if (typeof window !== "undefined" && browserRuntime) return browserRuntime;
|
|
403
|
+
const serverRuntime = serverRuntimeResolver?.();
|
|
404
|
+
if (serverRuntime) return serverRuntime;
|
|
405
|
+
throw new Error("DMS runtime is unavailable outside a Vue application");
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Returns lifecycle hooks isolated to the current application runtime. */
|
|
409
|
+
export function useDmsRuntimeHooks(): DmsRuntimeHooks {
|
|
410
|
+
const runtime = useDmsRuntime();
|
|
411
|
+
return {
|
|
412
|
+
hook(name, callback) {
|
|
413
|
+
runtime.runtimeHooks.set(name, [
|
|
414
|
+
...(runtime.runtimeHooks.get(name) ?? []),
|
|
415
|
+
callback,
|
|
416
|
+
]);
|
|
417
|
+
return () =>
|
|
418
|
+
runtime.runtimeHooks.set(
|
|
419
|
+
name,
|
|
420
|
+
(runtime.runtimeHooks.get(name) ?? []).filter(
|
|
421
|
+
(entry) => entry !== callback,
|
|
422
|
+
),
|
|
423
|
+
);
|
|
424
|
+
},
|
|
425
|
+
async callHook(name, ...args) {
|
|
426
|
+
for (const callback of runtime.runtimeHooks.get(name) ?? [])
|
|
427
|
+
await callback(...args);
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function extractRouteParams(
|
|
433
|
+
pattern: string | undefined,
|
|
434
|
+
path: string,
|
|
435
|
+
): Record<string, string> {
|
|
436
|
+
if (!pattern) return {};
|
|
437
|
+
const patternSegments = pattern.split("/").filter(Boolean);
|
|
438
|
+
const pathSegments = path.split("/").filter(Boolean);
|
|
439
|
+
if (patternSegments.length !== pathSegments.length) return {};
|
|
440
|
+
const params: Record<string, string> = {};
|
|
441
|
+
const matches = patternSegments.every((segment, index) => {
|
|
442
|
+
if (!segment.startsWith(":")) return segment === pathSegments[index];
|
|
443
|
+
params[segment.slice(1)] = decodeURIComponent(pathSegments[index] ?? "");
|
|
444
|
+
return true;
|
|
445
|
+
});
|
|
446
|
+
return matches ? params : {};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function parseRoute(runtime: DmsFrontendRuntime, url: string): DmsRoute {
|
|
450
|
+
const parsed = new URL(
|
|
451
|
+
url,
|
|
452
|
+
typeof window === "undefined"
|
|
453
|
+
? "http://frontend.local"
|
|
454
|
+
: window.location.origin,
|
|
455
|
+
);
|
|
456
|
+
return {
|
|
457
|
+
name: parsed.pathname,
|
|
458
|
+
fullPath: `${parsed.pathname}${parsed.search}${parsed.hash}`,
|
|
459
|
+
path: parsed.pathname,
|
|
460
|
+
query: Object.fromEntries(parsed.searchParams),
|
|
461
|
+
params: extractRouteParams(runtime.currentRoutePattern, parsed.pathname),
|
|
462
|
+
meta: {},
|
|
463
|
+
matched: [{}],
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function updateRoute(runtime: DmsFrontendRuntime, url: string): void {
|
|
468
|
+
Object.assign(runtime.route, parseRoute(runtime, url));
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function locationToUrl(
|
|
472
|
+
runtime: DmsFrontendRuntime,
|
|
473
|
+
to: RouteLocationRaw,
|
|
474
|
+
): string {
|
|
475
|
+
if (typeof to === "string") return to;
|
|
476
|
+
const origin =
|
|
477
|
+
typeof window === "undefined"
|
|
478
|
+
? "http://frontend.local"
|
|
479
|
+
: window.location.origin;
|
|
480
|
+
const parsed = new URL(to.path ?? runtime.route.path, origin);
|
|
481
|
+
if (to.query !== undefined) {
|
|
482
|
+
parsed.search = Object.entries(to.query)
|
|
483
|
+
.flatMap(([key, value]) =>
|
|
484
|
+
(Array.isArray(value) ? value : [value])
|
|
485
|
+
.filter((item) => item !== undefined)
|
|
486
|
+
.map((item) =>
|
|
487
|
+
item === null
|
|
488
|
+
? encodeURIComponent(key)
|
|
489
|
+
: `${encodeURIComponent(key)}=${encodeURIComponent(String(item))}`,
|
|
490
|
+
),
|
|
491
|
+
)
|
|
492
|
+
.join("&");
|
|
493
|
+
}
|
|
494
|
+
parsed.hash = to.hash ?? "";
|
|
495
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async function runMiddleware(
|
|
499
|
+
runtime: DmsFrontendRuntime,
|
|
500
|
+
to: DmsRoute,
|
|
501
|
+
): Promise<DmsMiddlewareResult> {
|
|
502
|
+
const declared = Array.isArray(to.meta.middleware) ? to.meta.middleware : [];
|
|
503
|
+
const metadataNames = Object.keys(to.meta).filter((name) => to.meta[name]);
|
|
504
|
+
const declaredHandlers = [...declared, ...metadataNames]
|
|
505
|
+
.map((name) => namedMiddleware.get(String(name)))
|
|
506
|
+
.filter((handler): handler is DmsMiddleware => !!handler);
|
|
507
|
+
for (const handler of [...middleware, ...declaredHandlers]) {
|
|
508
|
+
const runWithContext = runtimeContexts.get(runtime);
|
|
509
|
+
const result = await (runWithContext
|
|
510
|
+
? runWithContext(() => handler(to, runtime.route))
|
|
511
|
+
: handler(to, runtime.route));
|
|
512
|
+
if (result !== undefined) return result;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function visit(
|
|
517
|
+
runtime: DmsFrontendRuntime,
|
|
518
|
+
to: RouteLocationRaw,
|
|
519
|
+
options: DmsNavigationOptions = {},
|
|
520
|
+
): Promise<void> {
|
|
521
|
+
const url = locationToUrl(runtime, to);
|
|
522
|
+
const result = await runMiddleware(runtime, parseRoute(runtime, url));
|
|
523
|
+
if (result === false) return;
|
|
524
|
+
if (result !== undefined) return visit(runtime, result, options);
|
|
525
|
+
if (runtime.isServer) {
|
|
526
|
+
runtime.serverRedirect = url;
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
await new Promise<void>((resolve) =>
|
|
530
|
+
inertiaRouter.visit(url, {
|
|
531
|
+
...options,
|
|
532
|
+
onFinish: (completedVisit) => {
|
|
533
|
+
options.onFinish?.(completedVisit);
|
|
534
|
+
resolve();
|
|
535
|
+
},
|
|
536
|
+
}),
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export function useDmsRoute(pattern?: string): DmsRoute {
|
|
541
|
+
const runtime = useDmsRuntime();
|
|
542
|
+
runtime.currentRoutePattern = pattern;
|
|
543
|
+
const page = usePage();
|
|
544
|
+
updateRoute(runtime, page.url);
|
|
545
|
+
watch(
|
|
546
|
+
() => page.url,
|
|
547
|
+
(url) => updateRoute(runtime, url),
|
|
548
|
+
);
|
|
549
|
+
return runtime.route;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export function useDmsRouter(): DmsRouter {
|
|
553
|
+
const runtime = useDmsRuntime();
|
|
554
|
+
const navigate = (to: RouteLocationRaw, options?: DmsNavigationOptions) =>
|
|
555
|
+
locationToUrl(runtime, to) === runtime.route.fullPath
|
|
556
|
+
? Promise.resolve()
|
|
557
|
+
: visit(runtime, to, options);
|
|
558
|
+
return {
|
|
559
|
+
currentRoute: computed(() => runtime.route),
|
|
560
|
+
push: navigate,
|
|
561
|
+
replace: (to, options) => navigate(to, { ...options, replace: true }),
|
|
562
|
+
back: () => window.history.back(),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function navigateDms(
|
|
567
|
+
to: RouteLocationRaw,
|
|
568
|
+
options?: DmsNavigationOptions,
|
|
569
|
+
): Promise<void> {
|
|
570
|
+
return visit(useDmsRuntime(), to, options);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function createDmsFetch(defaults: FetchOptions = {}): typeof ofetch {
|
|
574
|
+
const execute = (request: string, options?: FetchOptions) => {
|
|
575
|
+
const runtime = useDmsRuntime();
|
|
576
|
+
return (runtime.serverFetch ?? ofetch)(request, {
|
|
577
|
+
...defaults,
|
|
578
|
+
...options,
|
|
579
|
+
});
|
|
580
|
+
};
|
|
581
|
+
return Object.assign(execute, {
|
|
582
|
+
create: (options: FetchOptions) =>
|
|
583
|
+
createDmsFetch({ ...defaults, ...options }),
|
|
584
|
+
native: ofetch.native,
|
|
585
|
+
raw: ofetch.raw,
|
|
586
|
+
}) as typeof ofetch;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export const $fetch = createDmsFetch();
|
|
590
|
+
|
|
591
|
+
function pickValue<T>(value: unknown, keys?: string[]): T {
|
|
592
|
+
if (!keys || typeof value !== "object" || value === null) return value as T;
|
|
593
|
+
return Object.fromEntries(
|
|
594
|
+
keys
|
|
595
|
+
.filter((key) => key in value)
|
|
596
|
+
.map((key) => [key, Reflect.get(value, key)]),
|
|
597
|
+
) as T;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function fetchOptions<T>(options: UseFetchOptions<T>): FetchOptions {
|
|
601
|
+
const {
|
|
602
|
+
immediate: _immediate,
|
|
603
|
+
watch: _watch,
|
|
604
|
+
default: _default,
|
|
605
|
+
transform: _transform,
|
|
606
|
+
pick: _pick,
|
|
607
|
+
$fetch: _fetch,
|
|
608
|
+
...requestOptions
|
|
609
|
+
} = options;
|
|
610
|
+
return requestOptions;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export function useDmsFetch<T>(
|
|
614
|
+
request: string | (() => string),
|
|
615
|
+
options: UseFetchOptions<T> = {},
|
|
616
|
+
): DmsAsyncData<T> {
|
|
617
|
+
const runtime = useDmsRuntime();
|
|
618
|
+
const data = ref<T | null>(options.default?.() ?? null) as Ref<T | null>;
|
|
619
|
+
const error = ref<unknown>(null);
|
|
620
|
+
const pending = ref(false);
|
|
621
|
+
const status = ref<"idle" | "pending" | "success" | "error">("idle");
|
|
622
|
+
const refresh = async (): Promise<void> => {
|
|
623
|
+
pending.value = true;
|
|
624
|
+
status.value = "pending";
|
|
625
|
+
error.value = null;
|
|
626
|
+
try {
|
|
627
|
+
const value = await (options.$fetch ?? runtime.serverFetch ?? ofetch)(
|
|
628
|
+
typeof request === "function" ? request() : request,
|
|
629
|
+
fetchOptions(options),
|
|
630
|
+
);
|
|
631
|
+
const transformed = options.transform
|
|
632
|
+
? await options.transform(value)
|
|
633
|
+
: value;
|
|
634
|
+
data.value = pickValue<T>(transformed, options.pick);
|
|
635
|
+
status.value = "success";
|
|
636
|
+
} catch (reason) {
|
|
637
|
+
error.value = reason;
|
|
638
|
+
status.value = "error";
|
|
639
|
+
} finally {
|
|
640
|
+
pending.value = false;
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
if (options.watch !== false) {
|
|
644
|
+
const sources = [
|
|
645
|
+
...(typeof request === "function" ? [request] : []),
|
|
646
|
+
...(options.watch ?? []),
|
|
647
|
+
] as never[];
|
|
648
|
+
if (sources.length) watch(sources, refresh);
|
|
649
|
+
}
|
|
650
|
+
if (options.immediate !== false) void refresh();
|
|
651
|
+
return { data, error, pending, status, execute: refresh, refresh };
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
interface AsyncDataBinding<T> {
|
|
655
|
+
execute: () => Promise<T>;
|
|
656
|
+
options: UseAsyncDataOptions<T>;
|
|
657
|
+
active: boolean;
|
|
658
|
+
pageVersion: number;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
interface BoundAsyncData<T> extends DmsAsyncData<T> {
|
|
662
|
+
bind(binding: AsyncDataBinding<T>): boolean;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
interface AsyncDataRegistration<T> {
|
|
666
|
+
entry: BoundAsyncData<T>;
|
|
667
|
+
shouldRefresh: boolean;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function createBoundAsyncData<T>(
|
|
671
|
+
initial: T | null,
|
|
672
|
+
hydrated: boolean,
|
|
673
|
+
): BoundAsyncData<T> {
|
|
674
|
+
const data = ref(initial) as Ref<T | null>;
|
|
675
|
+
const error = ref<unknown>(null);
|
|
676
|
+
const pending = ref(false);
|
|
677
|
+
const status = ref<DmsAsyncData<T>["status"]["value"]>(
|
|
678
|
+
hydrated ? "success" : "idle",
|
|
679
|
+
);
|
|
680
|
+
const bindings: AsyncDataBinding<T>[] = [];
|
|
681
|
+
let generation = 0;
|
|
682
|
+
let lastBindingPageVersion: number | undefined;
|
|
683
|
+
let pendingBinding: AsyncDataBinding<T> | undefined;
|
|
684
|
+
const refresh = async () => {
|
|
685
|
+
const binding = bindings.at(-1);
|
|
686
|
+
if (!binding) return;
|
|
687
|
+
const request = ++generation;
|
|
688
|
+
pendingBinding = binding;
|
|
689
|
+
pending.value = true;
|
|
690
|
+
status.value = "pending";
|
|
691
|
+
error.value = null;
|
|
692
|
+
const current = () => binding.active && request === generation;
|
|
693
|
+
try {
|
|
694
|
+
const value = await binding.execute();
|
|
695
|
+
const transformed = binding.options.transform
|
|
696
|
+
? await binding.options.transform(value)
|
|
697
|
+
: value;
|
|
698
|
+
if (!current()) return;
|
|
699
|
+
data.value = pickValue<T>(transformed, binding.options.pick);
|
|
700
|
+
status.value = "success";
|
|
701
|
+
} catch (reason) {
|
|
702
|
+
if (!current()) return;
|
|
703
|
+
error.value = reason;
|
|
704
|
+
status.value = "error";
|
|
705
|
+
} finally {
|
|
706
|
+
if (current()) pending.value = false;
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
return {
|
|
710
|
+
data,
|
|
711
|
+
error,
|
|
712
|
+
pending,
|
|
713
|
+
status,
|
|
714
|
+
execute: refresh,
|
|
715
|
+
refresh,
|
|
716
|
+
bind(binding) {
|
|
717
|
+
const shouldRefresh =
|
|
718
|
+
bindings.length === 0 &&
|
|
719
|
+
lastBindingPageVersion !== undefined &&
|
|
720
|
+
lastBindingPageVersion !== binding.pageVersion;
|
|
721
|
+
lastBindingPageVersion = binding.pageVersion;
|
|
722
|
+
bindings.push(binding);
|
|
723
|
+
if (binding.options.watch && binding.options.watch.length)
|
|
724
|
+
watch(binding.options.watch as never[], refresh);
|
|
725
|
+
if (getCurrentScope())
|
|
726
|
+
onScopeDispose(() => {
|
|
727
|
+
binding.active = false;
|
|
728
|
+
bindings.splice(bindings.indexOf(binding), 1);
|
|
729
|
+
if (pendingBinding === binding && pending.value) {
|
|
730
|
+
generation++;
|
|
731
|
+
pending.value = false;
|
|
732
|
+
status.value = data.value === null ? "idle" : "success";
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
return shouldRefresh;
|
|
736
|
+
},
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function createAsyncData<T>(
|
|
741
|
+
runtime: DmsFrontendRuntime,
|
|
742
|
+
keyOrHandler: string | (() => Promise<T>),
|
|
743
|
+
handler?: () => Promise<T>,
|
|
744
|
+
options: UseAsyncDataOptions<T> = {},
|
|
745
|
+
): AsyncDataRegistration<T> {
|
|
746
|
+
const execute =
|
|
747
|
+
handler ?? (typeof keyOrHandler === "function" ? keyOrHandler : undefined);
|
|
748
|
+
if (!execute) throw new Error("useDmsAsyncData requires a handler");
|
|
749
|
+
const key = typeof keyOrHandler === "string" ? keyOrHandler : undefined;
|
|
750
|
+
const hasHydratedData = key ? runtime.hydratedAsyncData.has(key) : false;
|
|
751
|
+
const hydratedData = key ? runtime.hydratedAsyncData.get(key) : undefined;
|
|
752
|
+
const cached = key
|
|
753
|
+
? (runtime.asyncData.get(key) as BoundAsyncData<T> | undefined)
|
|
754
|
+
: undefined;
|
|
755
|
+
const entry =
|
|
756
|
+
cached ??
|
|
757
|
+
createBoundAsyncData<T>(
|
|
758
|
+
hasHydratedData ? (hydratedData as T) : (options.default?.() ?? null),
|
|
759
|
+
hasHydratedData,
|
|
760
|
+
);
|
|
761
|
+
const shouldRefresh = entry.bind({
|
|
762
|
+
execute,
|
|
763
|
+
options,
|
|
764
|
+
active: true,
|
|
765
|
+
pageVersion: runtime.pageVersion,
|
|
766
|
+
});
|
|
767
|
+
if (key) runtime.asyncData.set(key, entry as DmsAsyncData<unknown>);
|
|
768
|
+
return { entry, shouldRefresh };
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
export async function useDmsAsyncData<T>(
|
|
772
|
+
keyOrHandler: string | (() => Promise<T>),
|
|
773
|
+
handler?: () => Promise<T>,
|
|
774
|
+
options: UseAsyncDataOptions<T> = {},
|
|
775
|
+
): Promise<DmsAsyncData<T>> {
|
|
776
|
+
const runtime = useDmsRuntime();
|
|
777
|
+
const key = typeof keyOrHandler === "string" ? keyOrHandler : undefined;
|
|
778
|
+
const cached = key ? runtime.asyncData.get(key) : undefined;
|
|
779
|
+
const isHydrated = key ? runtime.hydratedAsyncData.has(key) : false;
|
|
780
|
+
const { entry, shouldRefresh } = createAsyncData(
|
|
781
|
+
runtime,
|
|
782
|
+
keyOrHandler,
|
|
783
|
+
handler,
|
|
784
|
+
options,
|
|
785
|
+
);
|
|
786
|
+
if (key) runtime.hydratedAsyncData.delete(key);
|
|
787
|
+
const pending =
|
|
788
|
+
key && entry.pending.value ? runtime.asyncDataPromises.get(key) : undefined;
|
|
789
|
+
if (pending) await pending;
|
|
790
|
+
if (
|
|
791
|
+
(!cached || entry.status.value === "idle" || shouldRefresh) &&
|
|
792
|
+
!isHydrated &&
|
|
793
|
+
!pending &&
|
|
794
|
+
options.immediate !== false
|
|
795
|
+
) {
|
|
796
|
+
const execution = entry.execute();
|
|
797
|
+
if (key) runtime.asyncDataPromises.set(key, execution);
|
|
798
|
+
await execution;
|
|
799
|
+
if (key && runtime.asyncDataPromises.get(key) === execution)
|
|
800
|
+
runtime.asyncDataPromises.delete(key);
|
|
801
|
+
}
|
|
802
|
+
return entry;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
export function useDmsLazyAsyncData<T>(
|
|
806
|
+
keyOrHandler: string | (() => Promise<T>),
|
|
807
|
+
handler?: () => Promise<T>,
|
|
808
|
+
options: UseAsyncDataOptions<T> = {},
|
|
809
|
+
): DmsAsyncData<T> {
|
|
810
|
+
const runtime = useDmsRuntime();
|
|
811
|
+
const key = typeof keyOrHandler === "string" ? keyOrHandler : undefined;
|
|
812
|
+
const { entry, shouldRefresh } = createAsyncData(
|
|
813
|
+
runtime,
|
|
814
|
+
keyOrHandler,
|
|
815
|
+
handler,
|
|
816
|
+
options,
|
|
817
|
+
);
|
|
818
|
+
if (key) runtime.hydratedAsyncData.delete(key);
|
|
819
|
+
if (
|
|
820
|
+
options.immediate !== false &&
|
|
821
|
+
(entry.status.value === "idle" || shouldRefresh)
|
|
822
|
+
)
|
|
823
|
+
queueMicrotask(() => {
|
|
824
|
+
if (
|
|
825
|
+
entry.status.value === "idle" ||
|
|
826
|
+
(shouldRefresh && !entry.pending.value)
|
|
827
|
+
)
|
|
828
|
+
void entry.execute();
|
|
829
|
+
});
|
|
830
|
+
return entry;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
export async function refreshDmsData(keys?: string | string[]): Promise<void> {
|
|
834
|
+
const runtime = useDmsRuntime();
|
|
835
|
+
const selected = keys
|
|
836
|
+
? Array.isArray(keys)
|
|
837
|
+
? keys
|
|
838
|
+
: [keys]
|
|
839
|
+
: [...runtime.asyncData.keys()];
|
|
840
|
+
await Promise.all(
|
|
841
|
+
selected.map((key) => runtime.asyncData.get(key)?.refresh()),
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
export function serializeDmsAsyncData(
|
|
846
|
+
runtime: DmsFrontendRuntime,
|
|
847
|
+
): Record<string, unknown> {
|
|
848
|
+
return Object.fromEntries(
|
|
849
|
+
[...runtime.asyncData.entries()]
|
|
850
|
+
.filter(([, entry]) => entry.status.value === "success")
|
|
851
|
+
.map(([key, entry]) => [key, entry.data.value]),
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
export function useDmsState<T>(key: string, init?: () => T): Ref<T> {
|
|
856
|
+
const runtime = useDmsRuntime();
|
|
857
|
+
if (!runtime.sharedState.has(key))
|
|
858
|
+
runtime.sharedState.set(key, ref(init?.()));
|
|
859
|
+
return runtime.sharedState.get(key) as Ref<T>;
|
|
860
|
+
}
|
|
861
|
+
export function useDmsRuntimeConfig(): DmsRuntimeConfig {
|
|
862
|
+
return runtimeConfig.value;
|
|
863
|
+
}
|
|
864
|
+
export function useDmsAppConfig(): DmsAppConfig {
|
|
865
|
+
return appConfig.value as DmsAppConfig;
|
|
866
|
+
}
|
|
867
|
+
export function configureDmsRuntime(config: DmsRuntimeConfig): void {
|
|
868
|
+
runtimeConfig.value = config;
|
|
869
|
+
}
|
|
870
|
+
export function defineAppConfig<T extends Record<string, unknown>>(
|
|
871
|
+
config: T,
|
|
872
|
+
): T {
|
|
873
|
+
return config;
|
|
874
|
+
}
|
|
875
|
+
export function defineDmsMiddleware(handler: DmsMiddleware): DmsMiddleware {
|
|
876
|
+
return handler;
|
|
877
|
+
}
|
|
878
|
+
export function defineDmsPageMeta(meta: Record<string, unknown>): void {
|
|
879
|
+
const runtime = useDmsRuntime();
|
|
880
|
+
Object.assign(runtime.route.meta, meta);
|
|
881
|
+
const navigation = runMiddleware(runtime, runtime.route).then(
|
|
882
|
+
async (result) => {
|
|
883
|
+
if (result !== undefined && result !== false)
|
|
884
|
+
await visit(runtime, result);
|
|
885
|
+
},
|
|
886
|
+
);
|
|
887
|
+
runtime.pendingNavigation = runtime.pendingNavigation
|
|
888
|
+
? Promise.all([runtime.pendingNavigation, navigation]).then(() => undefined)
|
|
889
|
+
: navigation;
|
|
890
|
+
void runtime.pendingNavigation;
|
|
891
|
+
}
|
|
892
|
+
export function abortNavigation(): false {
|
|
893
|
+
return false;
|
|
894
|
+
}
|
|
895
|
+
export function addDmsMiddleware(
|
|
896
|
+
handler: DmsMiddleware,
|
|
897
|
+
name?: string,
|
|
898
|
+
isGlobal = false,
|
|
899
|
+
): void {
|
|
900
|
+
if (isGlobal) middleware.push(handler);
|
|
901
|
+
else if (name) namedMiddleware.set(name, handler);
|
|
902
|
+
}
|
|
903
|
+
export const useHead = useUnhead;
|
|
904
|
+
export const useSeoMeta = useUnheadSeoMeta;
|
|
905
|
+
export function useI18n(): DmsI18n {
|
|
906
|
+
return useVueI18n() as DmsI18n;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function shouldNavigateDmsLink(event: MouseEvent): boolean {
|
|
910
|
+
const element = event.currentTarget;
|
|
911
|
+
return !(
|
|
912
|
+
event.defaultPrevented ||
|
|
913
|
+
event.button !== 0 ||
|
|
914
|
+
event.altKey ||
|
|
915
|
+
event.ctrlKey ||
|
|
916
|
+
event.metaKey ||
|
|
917
|
+
event.shiftKey ||
|
|
918
|
+
(element instanceof HTMLAnchorElement &&
|
|
919
|
+
Boolean(element.target) &&
|
|
920
|
+
element.target !== "_self")
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function dmsLinkNavigationOptions(
|
|
925
|
+
attributes: Record<string, unknown>,
|
|
926
|
+
): DmsNavigationOptions {
|
|
927
|
+
const options = attributes as DmsNavigationOptions;
|
|
928
|
+
return {
|
|
929
|
+
async: options.async,
|
|
930
|
+
component: options.component,
|
|
931
|
+
data: options.data,
|
|
932
|
+
except: options.except,
|
|
933
|
+
headers: options.headers,
|
|
934
|
+
method: options.method,
|
|
935
|
+
onBefore: options.onBefore,
|
|
936
|
+
onCancel: options.onCancel,
|
|
937
|
+
onCancelToken: options.onCancelToken,
|
|
938
|
+
onError: options.onError,
|
|
939
|
+
onFinish: options.onFinish,
|
|
940
|
+
onProgress: options.onProgress,
|
|
941
|
+
onStart: options.onStart,
|
|
942
|
+
onSuccess: options.onSuccess,
|
|
943
|
+
only: options.only,
|
|
944
|
+
preserveScroll: options.preserveScroll,
|
|
945
|
+
preserveState: options.preserveState,
|
|
946
|
+
preserveUrl: options.preserveUrl,
|
|
947
|
+
queryStringArrayFormat: options.queryStringArrayFormat,
|
|
948
|
+
replace: options.replace,
|
|
949
|
+
viewTransition: options.viewTransition,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
export const DmsLink: Component = defineComponent({
|
|
954
|
+
name: "DmsLink",
|
|
955
|
+
inheritAttrs: false,
|
|
956
|
+
props: {
|
|
957
|
+
to: {
|
|
958
|
+
type: [String, Object] as PropType<RouteLocationRaw>,
|
|
959
|
+
required: true,
|
|
960
|
+
},
|
|
961
|
+
},
|
|
962
|
+
setup(props, { attrs, slots }) {
|
|
963
|
+
const runtime = useDmsRuntime();
|
|
964
|
+
const prefetch = attrs.prefetch as DmsLinkPrefetch | undefined;
|
|
965
|
+
const onClick = (event: MouseEvent) => {
|
|
966
|
+
if (typeof attrs.onClick === "function") attrs.onClick(event);
|
|
967
|
+
if (!shouldNavigateDmsLink(event)) return;
|
|
968
|
+
event.preventDefault();
|
|
969
|
+
void visit(runtime, props.to, dmsLinkNavigationOptions(attrs));
|
|
970
|
+
};
|
|
971
|
+
return (): ReturnType<typeof h> =>
|
|
972
|
+
h(
|
|
973
|
+
InertiaLink,
|
|
974
|
+
{
|
|
975
|
+
...attrs,
|
|
976
|
+
href: locationToUrl(runtime, props.to),
|
|
977
|
+
onClick,
|
|
978
|
+
prefetch: prefetch ?? "hover",
|
|
979
|
+
},
|
|
980
|
+
slots,
|
|
981
|
+
);
|
|
982
|
+
},
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
export const DmsClientOnly = defineComponent({
|
|
986
|
+
name: "DmsClientOnly",
|
|
987
|
+
setup(_, { slots }) {
|
|
988
|
+
const isMounted = ref(false);
|
|
989
|
+
onMounted(() => {
|
|
990
|
+
isMounted.value = true;
|
|
991
|
+
});
|
|
992
|
+
return () =>
|
|
993
|
+
isMounted.value ? slots.default?.() : (slots.fallback?.() ?? null);
|
|
994
|
+
},
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
export function defineDmsPlugin(setup: DmsPluginSetup): DmsPluginSetup {
|
|
998
|
+
return setup;
|
|
999
|
+
}
|
|
1000
|
+
export function useDmsApp(): DmsAppContext {
|
|
1001
|
+
const instance = getCurrentInstance();
|
|
1002
|
+
const contextual = instance?.appContext.config.globalProperties
|
|
1003
|
+
.$dms as DmsAppContext;
|
|
1004
|
+
return contextual ?? (useDmsRuntime().appContext as DmsAppContext);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
export function useDmsCookie<T = string | null>(
|
|
1008
|
+
name: string,
|
|
1009
|
+
options: DmsCookieOptions<T> = {},
|
|
1010
|
+
): Ref<T> {
|
|
1011
|
+
if (typeof document === "undefined")
|
|
1012
|
+
return ref(options.default?.() ?? null) as Ref<T>;
|
|
1013
|
+
const match = document.cookie
|
|
1014
|
+
.split("; ")
|
|
1015
|
+
.find((entry) => entry.startsWith(`${name}=`));
|
|
1016
|
+
const stored = match
|
|
1017
|
+
? decodeURIComponent(match.slice(name.length + 1))
|
|
1018
|
+
: undefined;
|
|
1019
|
+
let parsed: T;
|
|
1020
|
+
try {
|
|
1021
|
+
parsed =
|
|
1022
|
+
stored === undefined
|
|
1023
|
+
? ((options.default?.() ?? null) as T)
|
|
1024
|
+
: JSON.parse(stored);
|
|
1025
|
+
} catch {
|
|
1026
|
+
parsed = stored as T;
|
|
1027
|
+
}
|
|
1028
|
+
const value = ref(parsed) as Ref<T>;
|
|
1029
|
+
return computed({
|
|
1030
|
+
get: () => value.value,
|
|
1031
|
+
set: (next) => {
|
|
1032
|
+
value.value = next;
|
|
1033
|
+
const attributes = [
|
|
1034
|
+
`path=${options.path ?? "/"}`,
|
|
1035
|
+
options.maxAge === undefined ? "" : `max-age=${options.maxAge}`,
|
|
1036
|
+
options.sameSite ? `samesite=${options.sameSite}` : "",
|
|
1037
|
+
options.secure ? "secure" : "",
|
|
1038
|
+
].filter(Boolean);
|
|
1039
|
+
// biome-ignore lint/suspicious/noDocumentCookie: reactive cookie refs require synchronous writes.
|
|
1040
|
+
document.cookie = `${name}=${encodeURIComponent(JSON.stringify(next ?? null))}; ${attributes.join("; ")}`;
|
|
1041
|
+
},
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function preferredColorMode(): "light" | "dark" {
|
|
1046
|
+
return window.matchMedia?.("(prefers-color-scheme: dark)").matches
|
|
1047
|
+
? "dark"
|
|
1048
|
+
: "light";
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function applyColorMode(preference: DmsColorModePreference): void {
|
|
1052
|
+
colorMode.value = preference === "system" ? preferredColorMode() : preference;
|
|
1053
|
+
document.documentElement.classList.remove(...COLOR_MODE_CLASSES);
|
|
1054
|
+
document.documentElement.classList.add(colorMode.value);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function initializeColorMode(): void {
|
|
1058
|
+
if (isColorModeInitialized) return;
|
|
1059
|
+
isColorModeInitialized = true;
|
|
1060
|
+
if (typeof window === "undefined") return;
|
|
1061
|
+
const stored = window.localStorage.getItem(COLOR_MODE_STORAGE_KEY);
|
|
1062
|
+
if (COLOR_MODE_CLASSES.includes(stored ?? "") || stored === "system") {
|
|
1063
|
+
colorMode.preference = stored as DmsColorModePreference;
|
|
1064
|
+
}
|
|
1065
|
+
watch(
|
|
1066
|
+
() => colorMode.preference,
|
|
1067
|
+
(preference) => {
|
|
1068
|
+
applyColorMode(preference);
|
|
1069
|
+
window.localStorage.setItem(COLOR_MODE_STORAGE_KEY, preference);
|
|
1070
|
+
},
|
|
1071
|
+
{ immediate: true },
|
|
1072
|
+
);
|
|
1073
|
+
window
|
|
1074
|
+
.matchMedia?.("(prefers-color-scheme: dark)")
|
|
1075
|
+
.addEventListener("change", () => {
|
|
1076
|
+
if (colorMode.preference === "system") applyColorMode("system");
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
export function useColorMode(): DmsColorMode {
|
|
1081
|
+
initializeColorMode();
|
|
1082
|
+
return colorMode;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
export function createError(
|
|
1086
|
+
input: string | DmsErrorData,
|
|
1087
|
+
): Error & DmsErrorData {
|
|
1088
|
+
const details = typeof input === "string" ? { message: input } : input;
|
|
1089
|
+
return Object.assign(
|
|
1090
|
+
new Error(details.message ?? details.statusMessage),
|
|
1091
|
+
details,
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
export function showError(input: string | DmsErrorData): Error & DmsErrorData {
|
|
1095
|
+
const error = createError(input);
|
|
1096
|
+
useDmsRuntime().currentError.value = error;
|
|
1097
|
+
return error;
|
|
1098
|
+
}
|
|
1099
|
+
export function useError(): Ref<(Error & DmsErrorData) | null> {
|
|
1100
|
+
return useDmsRuntime().currentError as Ref<(Error & DmsErrorData) | null>;
|
|
1101
|
+
}
|
|
1102
|
+
export async function clearError(options?: {
|
|
1103
|
+
redirect?: string;
|
|
1104
|
+
}): Promise<void> {
|
|
1105
|
+
useDmsRuntime().currentError.value = null;
|
|
1106
|
+
if (options?.redirect) await navigateDms(options.redirect);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
export function useUserSession<
|
|
1110
|
+
TUser = DmsUser,
|
|
1111
|
+
TSession = DmsSession,
|
|
1112
|
+
>(): DmsUserSession<TUser, TSession> {
|
|
1113
|
+
const user = useDmsState<TUser | null>("dms-user", () => null);
|
|
1114
|
+
const session = useDmsState<TSession | null>("dms-session", () => null);
|
|
1115
|
+
return {
|
|
1116
|
+
user,
|
|
1117
|
+
session,
|
|
1118
|
+
loggedIn: computed(() => user.value !== null),
|
|
1119
|
+
fetch: async () => {
|
|
1120
|
+
const value = await ofetch<{
|
|
1121
|
+
user?: TUser;
|
|
1122
|
+
session?: TSession;
|
|
1123
|
+
}>("/api/_auth/session", { method: "POST" });
|
|
1124
|
+
user.value = value.user ?? null;
|
|
1125
|
+
session.value = value.session ?? null;
|
|
1126
|
+
},
|
|
1127
|
+
clear: async () => {
|
|
1128
|
+
await ofetch("/api/_auth/session", { method: "DELETE" });
|
|
1129
|
+
user.value = null;
|
|
1130
|
+
session.value = null;
|
|
1131
|
+
},
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/** Registers a component unless a higher-priority module already owns its name. */
|
|
1136
|
+
export function registerDmsComponent(name: string, component: Component): void {
|
|
1137
|
+
const key = normalizeDmsName(name);
|
|
1138
|
+
if (!components.has(key)) components.set(key, { name, component });
|
|
1139
|
+
}
|
|
1140
|
+
export function resolveDmsComponent(name: string): Component | undefined {
|
|
1141
|
+
return components.get(normalizeDmsName(name))?.component;
|
|
1142
|
+
}
|
|
1143
|
+
export const getDmsComponent = resolveDmsComponent;
|
|
1144
|
+
export async function preloadComponents(names: string[]): Promise<void> {
|
|
1145
|
+
const registry = useDmsRuntime().appContext?.vueApp._context.components ?? {};
|
|
1146
|
+
await Promise.all(
|
|
1147
|
+
names.map((name) =>
|
|
1148
|
+
(registry[name] as DmsAsyncComponent | undefined)?.__asyncLoader?.(),
|
|
1149
|
+
),
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
export const prefetchComponents = preloadComponents;
|
|
1153
|
+
export function normalizeDmsName(name: string): string {
|
|
1154
|
+
return name
|
|
1155
|
+
.replace(/^lazy/i, "")
|
|
1156
|
+
.replace(/^dms[-_]?/i, "")
|
|
1157
|
+
.replace(/[^a-z0-9]/gi, "")
|
|
1158
|
+
.toLowerCase();
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
function collectRenderedComponentNames(
|
|
1162
|
+
value: unknown,
|
|
1163
|
+
names: Set<string>,
|
|
1164
|
+
): void {
|
|
1165
|
+
if (!value || typeof value !== "object") return;
|
|
1166
|
+
if (Array.isArray(value)) {
|
|
1167
|
+
value.forEach((entry) => {
|
|
1168
|
+
collectRenderedComponentNames(entry, names);
|
|
1169
|
+
});
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
const definition = value as Record<string, unknown>;
|
|
1173
|
+
for (const key of ["component", "componentName"]) {
|
|
1174
|
+
if (typeof definition[key] === "string") names.add(definition[key]);
|
|
1175
|
+
}
|
|
1176
|
+
collectRenderedComponentNames(definition.children, names);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
async function preloadDmsLayoutComponents(
|
|
1180
|
+
layout: DmsPageLayout | undefined,
|
|
1181
|
+
): Promise<void> {
|
|
1182
|
+
const names = new Set<string>();
|
|
1183
|
+
collectRenderedComponentNames(layout?.layout, names);
|
|
1184
|
+
Object.values(layout?.components ?? {}).forEach((component) => {
|
|
1185
|
+
collectRenderedComponentNames(component, names);
|
|
1186
|
+
});
|
|
1187
|
+
await Promise.all(
|
|
1188
|
+
[...names].map((name) => {
|
|
1189
|
+
const component = resolveDmsComponent(name) as
|
|
1190
|
+
| DmsAsyncComponent
|
|
1191
|
+
| undefined;
|
|
1192
|
+
return component?.__asyncLoader?.();
|
|
1193
|
+
}),
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
export function normalizeDmsPageKey(name: string): string {
|
|
1198
|
+
return name.split(/[?#]/, 1)[0].replace(/^\/+|\/+$/g, "");
|
|
1199
|
+
}
|
|
1200
|
+
const CATCH_ALL_PAGE_KEY = "[...slug]";
|
|
1201
|
+
|
|
1202
|
+
function findDmsPageEntry(props: DmsPageProps): DmsFrontendEntry | undefined {
|
|
1203
|
+
const name = [
|
|
1204
|
+
props.page.componentName,
|
|
1205
|
+
props.page.route?.fullSlug,
|
|
1206
|
+
props.path,
|
|
1207
|
+
].find((candidate) => candidate && pages.has(normalizeDmsPageKey(candidate)));
|
|
1208
|
+
return name
|
|
1209
|
+
? pages.get(normalizeDmsPageKey(name))
|
|
1210
|
+
: dynamicPages.get(CATCH_ALL_PAGE_KEY);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
export function hasDmsPage(name: string): boolean {
|
|
1214
|
+
return pages.has(normalizeDmsPageKey(name));
|
|
1215
|
+
}
|
|
1216
|
+
export function getDmsPage(props: DmsPageProps): Component | undefined {
|
|
1217
|
+
if (props.error) return errorPage?.component;
|
|
1218
|
+
return findDmsPageEntry(props)?.component;
|
|
1219
|
+
}
|
|
1220
|
+
export function getDmsDynamicPage(name = "default"): Component | undefined {
|
|
1221
|
+
return dynamicPages.get(name)?.component;
|
|
1222
|
+
}
|
|
1223
|
+
export function getDmsLayout(props: DmsPageProps): Component | undefined {
|
|
1224
|
+
const layout = props.page.layout;
|
|
1225
|
+
const name = layout?.layout?.componentName ?? layout?.componentName;
|
|
1226
|
+
return name ? layouts.get(normalizeDmsName(name))?.component : undefined;
|
|
1227
|
+
}
|
|
1228
|
+
export function getDmsLayoutProps(
|
|
1229
|
+
props: DmsPageProps,
|
|
1230
|
+
): Record<string, unknown> {
|
|
1231
|
+
const definition = props.page.layout?.layout;
|
|
1232
|
+
return {
|
|
1233
|
+
...(definition?.options ?? {}),
|
|
1234
|
+
icon: props.page.route?.icon,
|
|
1235
|
+
title: props.page.route?.displayName,
|
|
1236
|
+
description: props.page.route?.description,
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
export function hydrateDmsPageProps(props: DmsPageProps, url?: string): void {
|
|
1241
|
+
const runtime = useDmsRuntime();
|
|
1242
|
+
if (url) updateRoute(runtime, url);
|
|
1243
|
+
runtime.pageVersion++;
|
|
1244
|
+
runtime.currentError.value = null;
|
|
1245
|
+
useDmsState<DmsUser | null>("dms-user", () => null).value =
|
|
1246
|
+
props.user ?? null;
|
|
1247
|
+
useDmsState<DmsSession | null>("dms-session", () => null).value =
|
|
1248
|
+
props.session ?? null;
|
|
1249
|
+
const shared = props.page.shared;
|
|
1250
|
+
if (shared) {
|
|
1251
|
+
useDmsState<unknown>("dms-siteLayout", () => undefined).value =
|
|
1252
|
+
shared.siteLayout;
|
|
1253
|
+
useDmsState<unknown>("dms-pageTree", () => undefined).value =
|
|
1254
|
+
shared.siteLayoutTree;
|
|
1255
|
+
useDmsState<unknown>("dms-quickActions", () => undefined).value =
|
|
1256
|
+
shared.quickActions;
|
|
1257
|
+
useDmsState<unknown>("dms-modules", () => undefined).value = shared.modules;
|
|
1258
|
+
useDmsState("dms-isOwner", () => false).value = shared.isOwner ?? false;
|
|
1259
|
+
useDmsState("dms-lastRefreshTime", () => 0).value = Date.now();
|
|
1260
|
+
}
|
|
1261
|
+
const layoutUrl = props.page.route?.layoutUrl ?? props.page.layoutUrl;
|
|
1262
|
+
const pageLayouts = useDmsState<Record<string, DmsPageLayout>>(
|
|
1263
|
+
"dms-pageLayouts",
|
|
1264
|
+
() => ({}),
|
|
1265
|
+
);
|
|
1266
|
+
// Each navigation is a new authorization snapshot. Never retain layouts
|
|
1267
|
+
// from an earlier account, tenant, or permission context.
|
|
1268
|
+
pageLayouts.value =
|
|
1269
|
+
layoutUrl && props.page.layout ? { [layoutUrl]: props.page.layout } : {};
|
|
1270
|
+
}
|
|
1271
|
+
export function getDmsErrorPage(): Component | undefined {
|
|
1272
|
+
return errorPage?.component;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
export async function preloadDmsPage(props: DmsPageProps): Promise<void> {
|
|
1276
|
+
const layoutName =
|
|
1277
|
+
props.page.layout?.layout?.componentName ??
|
|
1278
|
+
props.page.layout?.componentName;
|
|
1279
|
+
const page = findDmsPageEntry(props) ?? dynamicPages.get("default");
|
|
1280
|
+
const layout = layoutName
|
|
1281
|
+
? layouts.get(normalizeDmsName(layoutName))
|
|
1282
|
+
: undefined;
|
|
1283
|
+
const entries = props.error ? [errorPage] : [page, layout];
|
|
1284
|
+
await Promise.all(
|
|
1285
|
+
entries.flatMap((entry) => (entry?.preload ? [entry.preload()] : [])),
|
|
1286
|
+
);
|
|
1287
|
+
if (!props.error) await preloadDmsLayoutComponents(props.page.layout);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
export function useDmsInjection<T>(
|
|
1291
|
+
key: string | InjectionKey<T>,
|
|
1292
|
+
): T | undefined {
|
|
1293
|
+
return inject(key as InjectionKey<T>, undefined);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function registerDmsFrontendEntry(
|
|
1297
|
+
registry: Map<string, DmsFrontendEntry>,
|
|
1298
|
+
name: string,
|
|
1299
|
+
component: Component,
|
|
1300
|
+
preload?: DmsComponentPreloader,
|
|
1301
|
+
): void {
|
|
1302
|
+
if (!registry.has(name)) registry.set(name, { component, preload });
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
function createSdk(options: DmsModuleOptions): DmsFrontendSdk {
|
|
1306
|
+
return {
|
|
1307
|
+
options,
|
|
1308
|
+
registerComponent: registerDmsComponent,
|
|
1309
|
+
registerPage: (name, component, preload) => {
|
|
1310
|
+
const key = normalizeDmsPageKey(name);
|
|
1311
|
+
registerDmsFrontendEntry(pages, key, component, preload);
|
|
1312
|
+
},
|
|
1313
|
+
registerDynamicPage: (name, component, preload) =>
|
|
1314
|
+
registerDmsFrontendEntry(dynamicPages, name, component, preload),
|
|
1315
|
+
registerLayout: (name, component, preload) => {
|
|
1316
|
+
const key = normalizeDmsName(name);
|
|
1317
|
+
registerDmsFrontendEntry(layouts, key, component, preload);
|
|
1318
|
+
},
|
|
1319
|
+
registerErrorPage: (component, preload) => {
|
|
1320
|
+
errorPage ??= { component, preload };
|
|
1321
|
+
},
|
|
1322
|
+
registerPlugin: (setup, options = {}) =>
|
|
1323
|
+
pluginSetups.push({ setup, clientOnly: options.clientOnly ?? false }),
|
|
1324
|
+
registerMiddleware: (name, handler, options = {}) => {
|
|
1325
|
+
if (!namedMiddleware.has(name)) namedMiddleware.set(name, handler);
|
|
1326
|
+
if (options.global) middleware.push(handler);
|
|
1327
|
+
},
|
|
1328
|
+
provide: (key, value) => {
|
|
1329
|
+
if (!injections.has(key)) injections.set(key, value);
|
|
1330
|
+
},
|
|
1331
|
+
use: (plugin) => plugins.push(plugin),
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
/** Initializes frontend adapters in their generated priority order. */
|
|
1336
|
+
export async function setupFrontendModules(
|
|
1337
|
+
registrations: DmsFrontendModuleRegistration[],
|
|
1338
|
+
): Promise<void> {
|
|
1339
|
+
for (const registration of registrations) {
|
|
1340
|
+
runtimeConfig.value.public = defu(
|
|
1341
|
+
runtimeConfig.value.public,
|
|
1342
|
+
registration.options.public,
|
|
1343
|
+
);
|
|
1344
|
+
await registration.module.setup(createSdk(registration.options));
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
export async function installDmsPlugins(
|
|
1349
|
+
app: App,
|
|
1350
|
+
i18n: Composer,
|
|
1351
|
+
runtime: DmsFrontendRuntime,
|
|
1352
|
+
loadLocaleMessages: DmsLocaleLoader,
|
|
1353
|
+
supportedLocales: string[],
|
|
1354
|
+
): Promise<() => Promise<void>> {
|
|
1355
|
+
const dmsI18n = i18n as DmsI18n;
|
|
1356
|
+
const hooks = new Map<string, Array<() => void | Promise<void>>>();
|
|
1357
|
+
const appContext: DmsAppContext = {
|
|
1358
|
+
vueApp: app,
|
|
1359
|
+
$i18n: dmsI18n,
|
|
1360
|
+
provide: (key, value) => {
|
|
1361
|
+
app.provide(key, value);
|
|
1362
|
+
app.config.globalProperties[`$${key}`] = value;
|
|
1363
|
+
},
|
|
1364
|
+
runWithContext: (callback) => app.runWithContext(callback),
|
|
1365
|
+
hook: (name, callback) =>
|
|
1366
|
+
hooks.set(name, [...(hooks.get(name) ?? []), callback]),
|
|
1367
|
+
};
|
|
1368
|
+
runtime.appContext = appContext;
|
|
1369
|
+
if (typeof window !== "undefined" && !runtime.hasNavigationListener) {
|
|
1370
|
+
runtime.hasNavigationListener = true;
|
|
1371
|
+
const stopNavigationListener = inertiaRouter.on("before", () => {
|
|
1372
|
+
runtime.currentError.value = null;
|
|
1373
|
+
});
|
|
1374
|
+
app.onUnmount(() => {
|
|
1375
|
+
stopNavigationListener();
|
|
1376
|
+
runtime.hasNavigationListener = false;
|
|
1377
|
+
if (browserRuntime === runtime) browserRuntime = undefined;
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
app.config.errorHandler = (reason) => {
|
|
1381
|
+
runtime.currentError.value ??=
|
|
1382
|
+
reason instanceof Error ? reason : new Error(String(reason));
|
|
1383
|
+
};
|
|
1384
|
+
app.config.globalProperties.$dms = appContext;
|
|
1385
|
+
Reflect.set(app, "$dms", appContext);
|
|
1386
|
+
Reflect.set(app, "$i18n", i18n);
|
|
1387
|
+
Reflect.set(appContext.vueApp, "$dms", appContext);
|
|
1388
|
+
Reflect.set(app.config.globalProperties, "$i18n", i18n);
|
|
1389
|
+
dmsI18n.setLocale = async (locale: string) => {
|
|
1390
|
+
const messages = await loadLocaleMessages(locale);
|
|
1391
|
+
i18n.setLocaleMessage(locale, messages);
|
|
1392
|
+
i18n.locale.value = locale;
|
|
1393
|
+
};
|
|
1394
|
+
dmsI18n.locales = computed(() =>
|
|
1395
|
+
supportedLocales.map((code) => ({
|
|
1396
|
+
code,
|
|
1397
|
+
name: new Intl.DisplayNames([code], { type: "language" }).of(code),
|
|
1398
|
+
})),
|
|
1399
|
+
);
|
|
1400
|
+
app.component("DmsLink", DmsLink);
|
|
1401
|
+
app.component("DmsClientOnly", DmsClientOnly);
|
|
1402
|
+
components.forEach(({ component, name }) => {
|
|
1403
|
+
app.component(name, component);
|
|
1404
|
+
});
|
|
1405
|
+
injections.forEach((value, key) => {
|
|
1406
|
+
app.provide(key, value);
|
|
1407
|
+
});
|
|
1408
|
+
plugins.forEach((plugin) => {
|
|
1409
|
+
app.use(plugin);
|
|
1410
|
+
});
|
|
1411
|
+
for (const registration of pluginSetups) {
|
|
1412
|
+
if (registration.clientOnly && typeof window === "undefined") continue;
|
|
1413
|
+
await app.runWithContext(() => registration.setup(appContext));
|
|
1414
|
+
}
|
|
1415
|
+
return async () => {
|
|
1416
|
+
for (const callback of hooks.get("app:mounted") ?? []) await callback();
|
|
1417
|
+
};
|
|
1418
|
+
}
|