proteum 1.0.2 → 2.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/AGENTS.md +101 -0
- package/agents/codex/AGENTS.md +95 -0
- package/agents/codex/CODING_STYLE.md +71 -0
- package/agents/codex/agents.md.zip +0 -0
- package/agents/codex/client/AGENTS.md +102 -0
- package/agents/codex/client/pages/AGENTS.md +35 -0
- package/agents/codex/server/routes/AGENTS.md +12 -0
- package/agents/codex/server/services/AGENTS.md +137 -0
- package/agents/codex/tests/AGENTS.md +8 -0
- package/cli/app/config.ts +13 -11
- package/cli/app/index.ts +74 -82
- package/cli/bin.js +1 -1
- package/cli/commands/build.ts +51 -14
- package/cli/commands/check.ts +19 -0
- package/cli/commands/deploy/app.ts +4 -8
- package/cli/commands/deploy/web.ts +16 -20
- package/cli/commands/dev.ts +189 -64
- package/cli/commands/devEvents.ts +106 -0
- package/cli/commands/init.ts +63 -57
- package/cli/commands/lint.ts +21 -0
- package/cli/commands/refresh.ts +18 -0
- package/cli/commands/typecheck.ts +18 -0
- package/cli/compiler/client/identite.ts +80 -53
- package/cli/compiler/client/index.ts +139 -213
- package/cli/compiler/common/bundleAnalysis.ts +94 -0
- package/cli/compiler/common/clientManifest.ts +67 -0
- package/cli/compiler/common/controllers.ts +288 -0
- package/cli/compiler/common/files/autres.ts +7 -18
- package/cli/compiler/common/files/images.ts +40 -37
- package/cli/compiler/common/files/style.ts +11 -22
- package/cli/compiler/common/generatedRouteModules.ts +368 -0
- package/cli/compiler/common/index.ts +31 -65
- package/cli/compiler/common/loaders/forbid-ssr-import.js +13 -0
- package/cli/compiler/common/rspackAliases.ts +13 -0
- package/cli/compiler/common/scripts.ts +37 -0
- package/cli/compiler/index.ts +781 -230
- package/cli/compiler/server/index.ts +59 -75
- package/cli/compiler/writeIfChanged.ts +21 -0
- package/cli/index.ts +71 -72
- package/cli/paths.ts +51 -57
- package/cli/print.ts +17 -11
- package/cli/tsconfig.json +5 -4
- package/cli/utils/agents.ts +100 -0
- package/cli/utils/check.ts +71 -0
- package/cli/utils/index.ts +1 -3
- package/cli/utils/keyboard.ts +8 -25
- package/cli/utils/runProcess.ts +30 -0
- package/client/app/component.tsx +29 -29
- package/client/app/index.ts +36 -57
- package/client/app/service.ts +7 -12
- package/client/app.tsconfig.json +2 -2
- package/client/components/Dialog/Manager.ssr.tsx +40 -0
- package/client/components/Dialog/Manager.tsx +119 -150
- package/client/components/Dialog/status.tsx +3 -3
- package/client/components/index.ts +1 -1
- package/client/components/types.d.ts +1 -3
- package/client/dev/hmr.ts +65 -0
- package/client/global.d.ts +2 -2
- package/client/hooks.ts +6 -9
- package/client/index.ts +2 -1
- package/client/islands/index.ts +7 -0
- package/client/islands/useDeferredModule.ts +199 -0
- package/client/pages/_layout/index.tsx +4 -12
- package/client/pages/useHeader.tsx +14 -21
- package/client/router.ts +27 -0
- package/client/services/router/components/Link.tsx +34 -27
- package/client/services/router/components/Page.tsx +6 -14
- package/client/services/router/components/router.ssr.tsx +36 -0
- package/client/services/router/components/router.tsx +63 -83
- package/client/services/router/index.tsx +185 -220
- package/client/services/router/request/api.ts +97 -119
- package/client/services/router/request/history.ts +2 -2
- package/client/services/router/request/index.ts +13 -12
- package/client/services/router/request/multipart.ts +72 -62
- package/client/services/router/response/index.tsx +68 -61
- package/client/services/router/response/page.ts +28 -32
- package/client/utils/dom.ts +17 -33
- package/common/app/index.ts +3 -3
- package/common/data/chaines/index.ts +22 -23
- package/common/data/dates.ts +35 -70
- package/common/data/markdown.ts +42 -39
- package/common/dev/serverHotReload.ts +26 -0
- package/common/errors/index.tsx +110 -142
- package/common/router/contracts.ts +29 -0
- package/common/router/index.ts +89 -108
- package/common/router/layouts.ts +34 -47
- package/common/router/pageSetup.ts +50 -0
- package/common/router/register.ts +53 -24
- package/common/router/request/api.ts +30 -36
- package/common/router/request/index.ts +2 -8
- package/common/router/response/index.ts +8 -15
- package/common/router/response/page.ts +70 -58
- package/common/utils.ts +1 -1
- package/doc/TODO.md +1 -1
- package/eslint.js +62 -0
- package/package.json +14 -49
- package/prettier.config.cjs +9 -0
- package/scripts/cleanup-generated-controllers.ts +62 -0
- package/scripts/fix-reference-app-typing.ts +490 -0
- package/scripts/refactor-client-app-imports.ts +244 -0
- package/scripts/refactor-client-pages.ts +587 -0
- package/scripts/refactor-server-controllers.ts +470 -0
- package/scripts/refactor-server-runtime-aliases.ts +360 -0
- package/scripts/restore-client-app-import-files.ts +41 -0
- package/scripts/restore-files-from-git-head.ts +20 -0
- package/scripts/update-codex-agents.ts +35 -0
- package/server/app/commands.ts +35 -64
- package/server/app/container/config.ts +48 -59
- package/server/app/container/console/index.ts +202 -248
- package/server/app/container/index.ts +33 -71
- package/server/app/controller/index.ts +61 -0
- package/server/app/index.ts +39 -105
- package/server/app/service/container.ts +41 -42
- package/server/app/service/index.ts +120 -147
- package/server/context.ts +1 -1
- package/server/index.ts +25 -1
- package/server/services/auth/index.ts +75 -115
- package/server/services/auth/router/index.ts +31 -32
- package/server/services/auth/router/request.ts +14 -16
- package/server/services/cron/CronTask.ts +13 -26
- package/server/services/cron/index.ts +14 -36
- package/server/services/disks/driver.ts +40 -58
- package/server/services/disks/drivers/local/index.ts +79 -90
- package/server/services/disks/drivers/s3/index.ts +116 -163
- package/server/services/disks/index.ts +23 -38
- package/server/services/email/index.ts +45 -104
- package/server/services/email/utils.ts +14 -27
- package/server/services/fetch/index.ts +53 -85
- package/server/services/prisma/Facet.ts +39 -91
- package/server/services/prisma/index.ts +74 -110
- package/server/services/router/generatedRuntime.ts +29 -0
- package/server/services/router/http/index.ts +78 -73
- package/server/services/router/http/multipart.ts +19 -42
- package/server/services/router/index.ts +378 -365
- package/server/services/router/request/api.ts +26 -25
- package/server/services/router/request/index.ts +44 -51
- package/server/services/router/request/service.ts +7 -11
- package/server/services/router/request/validation/zod.ts +111 -148
- package/server/services/router/response/index.ts +110 -125
- package/server/services/router/response/mask/Filter.ts +31 -72
- package/server/services/router/response/mask/index.ts +8 -15
- package/server/services/router/response/mask/selecteurs.ts +11 -25
- package/server/services/router/response/page/clientManifest.ts +25 -0
- package/server/services/router/response/page/document.tsx +199 -127
- package/server/services/router/response/page/index.tsx +89 -94
- package/server/services/router/service.ts +13 -15
- package/server/services/schema/index.ts +17 -26
- package/server/services/schema/request.ts +19 -33
- package/server/services/schema/router/index.ts +8 -11
- package/server/services/security/encrypt/aes/index.ts +15 -35
- package/server/utils/slug.ts +29 -35
- package/skills/clean-project-code/SKILL.md +63 -0
- package/skills/clean-project-code/agents/openai.yaml +4 -0
- package/tsconfig.common.json +4 -3
- package/tsconfig.json +4 -1
- package/types/aliases.d.ts +17 -21
- package/types/controller-input.test.ts +48 -0
- package/types/express-extra.d.ts +6 -0
- package/types/global/constants.d.ts +13 -0
- package/types/global/express-extra.d.ts +6 -0
- package/types/global/modules.d.ts +13 -16
- package/types/global/utils.d.ts +17 -49
- package/types/global/vendors.d.ts +62 -0
- package/types/icons.d.ts +65 -1
- package/types/uuid.d.ts +3 -0
- package/types/vendors.d.ts +62 -0
- package/cli/compiler/common/babel/index.ts +0 -170
- package/cli/compiler/common/babel/plugins/index.ts +0 -0
- package/cli/compiler/common/babel/plugins/services.ts +0 -586
- package/cli/compiler/common/babel/routes/imports.ts +0 -127
- package/cli/compiler/common/babel/routes/routes.ts +0 -1130
- package/client/services/captcha/index.ts +0 -67
- package/client/services/socket/index.ts +0 -147
- package/common/data/rte/nodes.ts +0 -83
- package/common/data/stats.ts +0 -90
- package/common/utils/rte.ts +0 -183
- package/server/services/auth/old.ts +0 -277
- package/server/services/cache/commands.ts +0 -41
- package/server/services/cache/index.ts +0 -297
- package/server/services/cache/service.json +0 -6
- package/server/services/socket/index.ts +0 -162
- package/server/services/socket/scope.ts +0 -226
- package/server/services/socket/service.json +0 -6
- package/server/services_old/SocketClient.ts +0 -92
- package/server/services_old/Token.old.ts +0 -97
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
type TDevEvent = { type: 'reload'; reason: 'server' | 'manual' | 'client' };
|
|
2
|
+
|
|
3
|
+
const devEventsPath = '/__proteum_hmr';
|
|
4
|
+
let reloadPending = false;
|
|
5
|
+
let source: EventSource | null = null;
|
|
6
|
+
|
|
7
|
+
const delay = (timeout: number) => new Promise<void>((resolve) => window.setTimeout(resolve, timeout));
|
|
8
|
+
|
|
9
|
+
const buildDevEventsUrl = () => {
|
|
10
|
+
if (typeof window === 'undefined') return null;
|
|
11
|
+
if (PROTEUM_DEV_EVENT_PORT === null) return null;
|
|
12
|
+
|
|
13
|
+
const hostname = window.location.hostname || 'localhost';
|
|
14
|
+
return `${window.location.protocol}//${hostname}:${PROTEUM_DEV_EVENT_PORT}${devEventsPath}`;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const waitForServer = async () => {
|
|
18
|
+
for (let attempt = 0; attempt < 40; attempt += 1) {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(`/ping?ts=${Date.now()}`, { cache: 'no-store' });
|
|
21
|
+
if (response.ok) return;
|
|
22
|
+
} catch (error) {}
|
|
23
|
+
|
|
24
|
+
await delay(250);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const requestReload = async () => {
|
|
29
|
+
if (reloadPending) return;
|
|
30
|
+
reloadPending = true;
|
|
31
|
+
source?.close();
|
|
32
|
+
source = null;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await waitForServer();
|
|
36
|
+
} finally {
|
|
37
|
+
window.location.reload();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleEvent = (_event: TDevEvent) => {
|
|
42
|
+
void requestReload();
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const devEventsUrl = buildDevEventsUrl();
|
|
46
|
+
|
|
47
|
+
if (devEventsUrl && typeof EventSource !== 'undefined') {
|
|
48
|
+
source = new EventSource(devEventsUrl);
|
|
49
|
+
|
|
50
|
+
source.onmessage = (event) => {
|
|
51
|
+
try {
|
|
52
|
+
const payload = JSON.parse(event.data) as TDevEvent;
|
|
53
|
+
handleEvent(payload);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.warn('[hmr] Failed to parse dev event payload.', error);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
source.onerror = () => {
|
|
60
|
+
// EventSource reconnects automatically. Transient disconnects are expected
|
|
61
|
+
// during rebuilds and page reloads, so warning here would be mostly noise.
|
|
62
|
+
};
|
|
63
|
+
} else {
|
|
64
|
+
console.warn('[hmr] Dev event stream is unavailable in this environment.');
|
|
65
|
+
}
|
package/client/global.d.ts
CHANGED
package/client/hooks.ts
CHANGED
|
@@ -9,14 +9,11 @@ export { default as useScript } from '@client/hooks/useScript';*/
|
|
|
9
9
|
|
|
10
10
|
// Utils
|
|
11
11
|
export const Switch = (val: string | number, options: { [cle: string]: ComponentChild }) => {
|
|
12
|
-
return
|
|
13
|
-
}
|
|
12
|
+
return val in options ? options[val] : null;
|
|
13
|
+
};
|
|
14
14
|
|
|
15
|
-
export const useState = <TData extends TObjetDonnees>(initial: TData): [
|
|
16
|
-
TData,
|
|
17
|
-
(data: Partial<TData>) => void
|
|
18
|
-
] => {
|
|
15
|
+
export const useState = <TData extends TObjetDonnees>(initial: TData): [TData, (data: Partial<TData>) => void] => {
|
|
19
16
|
const [state, setState] = React.useState<TData>(initial);
|
|
20
|
-
const setPartialState = (data: Partial<TData>) => setState(current => ({ ...current, ...data }));
|
|
21
|
-
return [state, setPartialState]
|
|
22
|
-
}
|
|
17
|
+
const setPartialState = (data: Partial<TData>) => setState((current) => ({ ...current, ...data }));
|
|
18
|
+
return [state, setPartialState];
|
|
19
|
+
};
|
package/client/index.ts
CHANGED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export type TDeferredModuleLoader<TModule> = () => Promise<TModule>;
|
|
4
|
+
|
|
5
|
+
export type TDeferredModuleStrategy = 'immediate' | 'idle' | 'visible';
|
|
6
|
+
|
|
7
|
+
export type TDeferredModuleOptions = {
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
when?: TDeferredModuleStrategy;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
rootMargin?: string;
|
|
12
|
+
cache?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type TDeferredModuleResult<TModule> = {
|
|
16
|
+
value?: TModule;
|
|
17
|
+
error?: unknown;
|
|
18
|
+
isLoaded: boolean;
|
|
19
|
+
isLoading: boolean;
|
|
20
|
+
load: () => Promise<TModule>;
|
|
21
|
+
ref: (node: HTMLElement | null) => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type TDeferredModuleEntry<TModule> = {
|
|
25
|
+
status: 'idle' | 'loading' | 'loaded' | 'error';
|
|
26
|
+
promise?: Promise<TModule>;
|
|
27
|
+
value?: TModule;
|
|
28
|
+
error?: unknown;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const moduleCache = new WeakMap<TDeferredModuleLoader<unknown>, TDeferredModuleEntry<unknown>>();
|
|
32
|
+
|
|
33
|
+
const getModuleEntry = <TModule>(
|
|
34
|
+
loader: TDeferredModuleLoader<TModule>,
|
|
35
|
+
cacheEnabled: boolean,
|
|
36
|
+
): TDeferredModuleEntry<TModule> | undefined => {
|
|
37
|
+
if (!cacheEnabled) return undefined;
|
|
38
|
+
|
|
39
|
+
return moduleCache.get(loader) as TDeferredModuleEntry<TModule> | undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const setModuleEntry = <TModule>(
|
|
43
|
+
loader: TDeferredModuleLoader<TModule>,
|
|
44
|
+
entry: TDeferredModuleEntry<TModule>,
|
|
45
|
+
cacheEnabled: boolean,
|
|
46
|
+
) => {
|
|
47
|
+
if (!cacheEnabled) return;
|
|
48
|
+
|
|
49
|
+
moduleCache.set(loader as TDeferredModuleLoader<unknown>, entry as TDeferredModuleEntry<unknown>);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getInitialState = <TModule>(loader: TDeferredModuleLoader<TModule>, cacheEnabled: boolean) => {
|
|
53
|
+
const entry = getModuleEntry(loader, cacheEnabled);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
value: entry?.value,
|
|
57
|
+
error: entry?.error,
|
|
58
|
+
isLoaded: entry?.status === 'loaded',
|
|
59
|
+
isLoading: entry?.status === 'loading',
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default function useDeferredModule<TModule>(
|
|
64
|
+
loader: TDeferredModuleLoader<TModule>,
|
|
65
|
+
{
|
|
66
|
+
enabled = true,
|
|
67
|
+
when = 'immediate',
|
|
68
|
+
timeoutMs = 1000,
|
|
69
|
+
rootMargin = '200px',
|
|
70
|
+
cache = true,
|
|
71
|
+
}: TDeferredModuleOptions = {},
|
|
72
|
+
): TDeferredModuleResult<TModule> {
|
|
73
|
+
const mountedRef = React.useRef(true);
|
|
74
|
+
const [targetNode, setTargetNode] = React.useState<HTMLElement | null>(null);
|
|
75
|
+
const [state, setState] = React.useState(() => getInitialState(loader, cache));
|
|
76
|
+
|
|
77
|
+
React.useEffect(() => {
|
|
78
|
+
mountedRef.current = true;
|
|
79
|
+
|
|
80
|
+
return () => {
|
|
81
|
+
mountedRef.current = false;
|
|
82
|
+
};
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
React.useEffect(() => {
|
|
86
|
+
setState(getInitialState(loader, cache));
|
|
87
|
+
}, [loader, cache]);
|
|
88
|
+
|
|
89
|
+
const ref = React.useCallback(
|
|
90
|
+
(node: HTMLElement | null) => {
|
|
91
|
+
if (when !== 'visible') return;
|
|
92
|
+
setTargetNode(node);
|
|
93
|
+
},
|
|
94
|
+
[when],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const load = React.useCallback(async () => {
|
|
98
|
+
const cachedEntry = getModuleEntry(loader, cache);
|
|
99
|
+
|
|
100
|
+
if (cachedEntry?.status === 'loaded') {
|
|
101
|
+
if (mountedRef.current)
|
|
102
|
+
setState({ value: cachedEntry.value, error: undefined, isLoaded: true, isLoading: false });
|
|
103
|
+
|
|
104
|
+
return cachedEntry.value as TModule;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (cachedEntry?.status === 'loading' && cachedEntry.promise) {
|
|
108
|
+
if (mountedRef.current) setState((current) => ({ ...current, isLoading: true }));
|
|
109
|
+
|
|
110
|
+
return cachedEntry.promise;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const promise = loader();
|
|
114
|
+
const nextEntry: TDeferredModuleEntry<TModule> = { status: 'loading', promise };
|
|
115
|
+
|
|
116
|
+
setModuleEntry(loader, nextEntry, cache);
|
|
117
|
+
|
|
118
|
+
if (mountedRef.current) setState((current) => ({ ...current, error: undefined, isLoading: true }));
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const value = await promise;
|
|
122
|
+
setModuleEntry(loader, { status: 'loaded', value }, cache);
|
|
123
|
+
|
|
124
|
+
if (mountedRef.current) setState({ value, error: undefined, isLoaded: true, isLoading: false });
|
|
125
|
+
|
|
126
|
+
return value;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
setModuleEntry(loader, { status: 'error', error }, cache);
|
|
129
|
+
|
|
130
|
+
if (mountedRef.current) setState({ value: undefined, error, isLoaded: false, isLoading: false });
|
|
131
|
+
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}, [cache, loader]);
|
|
135
|
+
|
|
136
|
+
React.useEffect(() => {
|
|
137
|
+
if (!enabled || state.isLoaded || state.isLoading) return;
|
|
138
|
+
|
|
139
|
+
if (when === 'immediate') {
|
|
140
|
+
void load();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (when === 'idle') {
|
|
145
|
+
if (typeof window === 'undefined') return;
|
|
146
|
+
|
|
147
|
+
let cancelled = false;
|
|
148
|
+
|
|
149
|
+
if ('requestIdleCallback' in window) {
|
|
150
|
+
const idleId = window.requestIdleCallback(
|
|
151
|
+
() => {
|
|
152
|
+
if (!cancelled) void load();
|
|
153
|
+
},
|
|
154
|
+
{ timeout: timeoutMs },
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return () => {
|
|
158
|
+
cancelled = true;
|
|
159
|
+
window.cancelIdleCallback(idleId);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const timeoutId = setTimeout(() => {
|
|
164
|
+
if (!cancelled) void load();
|
|
165
|
+
}, timeoutMs);
|
|
166
|
+
|
|
167
|
+
return () => {
|
|
168
|
+
cancelled = true;
|
|
169
|
+
clearTimeout(timeoutId);
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (when === 'visible') {
|
|
174
|
+
if (typeof window === 'undefined' || !targetNode) return;
|
|
175
|
+
|
|
176
|
+
if (typeof IntersectionObserver === 'undefined') {
|
|
177
|
+
void load();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const observer = new IntersectionObserver(
|
|
182
|
+
(entries) => {
|
|
183
|
+
const isVisible = entries.some((entry) => entry.isIntersecting);
|
|
184
|
+
if (!isVisible) return;
|
|
185
|
+
|
|
186
|
+
observer.disconnect();
|
|
187
|
+
void load();
|
|
188
|
+
},
|
|
189
|
+
{ rootMargin },
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
observer.observe(targetNode);
|
|
193
|
+
|
|
194
|
+
return () => observer.disconnect();
|
|
195
|
+
}
|
|
196
|
+
}, [enabled, load, rootMargin, state.isLoaded, state.isLoading, targetNode, timeoutMs, when]);
|
|
197
|
+
|
|
198
|
+
return { ...state, load, ref };
|
|
199
|
+
}
|
|
@@ -13,31 +13,23 @@ import { ClientContext } from '@/client/context';
|
|
|
13
13
|
// Core components
|
|
14
14
|
|
|
15
15
|
// Resources
|
|
16
|
-
import
|
|
16
|
+
import './index.less';
|
|
17
17
|
|
|
18
18
|
/*----------------------------------
|
|
19
19
|
- TYPES
|
|
20
20
|
----------------------------------*/
|
|
21
21
|
|
|
22
|
-
|
|
23
22
|
/*----------------------------------
|
|
24
23
|
- COMPOSANT
|
|
25
24
|
----------------------------------*/
|
|
26
|
-
export default function App
|
|
27
|
-
context: ClientContext,
|
|
28
|
-
menu: ComponentChild
|
|
29
|
-
}) {
|
|
30
|
-
|
|
25
|
+
export default function App({ context, menu }: { context: ClientContext; menu?: ComponentChild }) {
|
|
31
26
|
const { Router, page, toast } = context;
|
|
32
27
|
|
|
33
28
|
return (
|
|
34
29
|
<div id="internaLlayout">
|
|
35
|
-
|
|
36
30
|
<div class="center row al-fill">
|
|
37
|
-
|
|
38
31
|
<RouterComponent service={Router} />
|
|
39
|
-
|
|
40
32
|
</div>
|
|
41
33
|
</div>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -9,42 +9,35 @@ import useContext from '@/client/context';
|
|
|
9
9
|
- TYPES
|
|
10
10
|
----------------------------------*/
|
|
11
11
|
export type Props = {
|
|
12
|
-
id?: string
|
|
13
|
-
focus?: boolean
|
|
14
|
-
jail?: boolean
|
|
15
|
-
error?: boolean
|
|
12
|
+
id?: string;
|
|
13
|
+
focus?: boolean;
|
|
14
|
+
jail?: boolean;
|
|
15
|
+
error?: boolean;
|
|
16
16
|
|
|
17
|
-
title: string
|
|
18
|
-
subtitle?: string
|
|
19
|
-
description?: string
|
|
20
|
-
}
|
|
17
|
+
title: string;
|
|
18
|
+
subtitle?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
};
|
|
21
21
|
|
|
22
22
|
/*----------------------------------
|
|
23
23
|
- HOOK
|
|
24
24
|
----------------------------------*/
|
|
25
25
|
export default ({ id, title, subtitle, focus, jail, description }: Props) => {
|
|
26
|
-
|
|
27
26
|
let { page } = useContext();
|
|
28
27
|
|
|
29
28
|
// page est supposé ne pas être undefined
|
|
30
|
-
if (!page)
|
|
31
|
-
return;
|
|
29
|
+
if (!page) return;
|
|
32
30
|
|
|
33
31
|
// SEO Title
|
|
34
32
|
page.title = title;
|
|
35
|
-
if (subtitle !== undefined)
|
|
36
|
-
page.title += ' | ' + subtitle;
|
|
33
|
+
if (subtitle !== undefined) page.title += ' | ' + subtitle;
|
|
37
34
|
|
|
38
35
|
// SEO Description
|
|
39
|
-
if (description !== undefined)
|
|
40
|
-
page.description = description;
|
|
36
|
+
if (description !== undefined) page.description = description;
|
|
41
37
|
|
|
42
38
|
page.bodyId = page.bodyId || id || '';
|
|
43
39
|
|
|
44
|
-
if (focus)
|
|
45
|
-
page.bodyClass.add('focus');
|
|
40
|
+
if (focus) page.bodyClass.add('focus');
|
|
46
41
|
|
|
47
|
-
if (jail)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
42
|
+
if (jail) page.bodyClass.add('jail');
|
|
43
|
+
};
|
package/client/router.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type Router from '@client/services/router';
|
|
2
|
+
|
|
3
|
+
const getRouter = (): Router => {
|
|
4
|
+
if (typeof window === 'undefined') {
|
|
5
|
+
throw new Error(`Client router is not available on the server.`);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const router = (window.app as (Record<string, unknown> & { Router?: Router }) | undefined)?.Router;
|
|
9
|
+
if (!router) {
|
|
10
|
+
throw new Error(`Client router was accessed before the application booted.`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return router;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const ClientRouter = new Proxy({} as Router, {
|
|
17
|
+
get(_target, property) {
|
|
18
|
+
const value = getRouter()[property as keyof Router];
|
|
19
|
+
return typeof value === 'function' ? value.bind(getRouter()) : value;
|
|
20
|
+
},
|
|
21
|
+
set(_target, property, value) {
|
|
22
|
+
((getRouter() as unknown) as Record<PropertyKey, unknown>)[property] = value;
|
|
23
|
+
return true;
|
|
24
|
+
},
|
|
25
|
+
}) as Router;
|
|
26
|
+
|
|
27
|
+
export default ClientRouter;
|
|
@@ -7,40 +7,47 @@ import React from 'react';
|
|
|
7
7
|
import type { ComponentChild } from 'preact';
|
|
8
8
|
import { history } from '../request/history';
|
|
9
9
|
|
|
10
|
-
export const shouldOpenNewTab = (url: string, target?: string) =>
|
|
11
|
-
target !== undefined
|
|
12
|
-
||
|
|
13
|
-
!['/', '#'].includes(url[0])
|
|
14
|
-
||
|
|
15
|
-
url.startsWith('//')
|
|
16
|
-
)
|
|
10
|
+
export const shouldOpenNewTab = (url: string, target?: string) =>
|
|
11
|
+
url && (target !== undefined || !['/', '#'].includes(url[0]) || url.startsWith('//'));
|
|
17
12
|
|
|
18
13
|
/*----------------------------------
|
|
19
14
|
- COMPONENT
|
|
20
15
|
----------------------------------*/
|
|
21
16
|
// Simple link
|
|
22
|
-
export const Link = ({
|
|
23
|
-
to
|
|
24
|
-
children
|
|
25
|
-
class
|
|
26
|
-
className
|
|
27
|
-
|
|
17
|
+
export const Link = ({
|
|
18
|
+
to,
|
|
19
|
+
children,
|
|
20
|
+
class: classNameAttr,
|
|
21
|
+
className,
|
|
22
|
+
onClick,
|
|
23
|
+
target,
|
|
24
|
+
...props
|
|
25
|
+
}: {
|
|
26
|
+
to: string;
|
|
27
|
+
children?: ComponentChild;
|
|
28
|
+
class?: string;
|
|
29
|
+
className?: string;
|
|
30
|
+
} & React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
|
|
31
|
+
const openNewTab = shouldOpenNewTab(to, typeof target === 'string' ? target : undefined);
|
|
32
|
+
const resolvedTarget = openNewTab ? '_blank' : target;
|
|
28
33
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (openNewTab)
|
|
33
|
-
props.target = '_blank';
|
|
34
|
-
// Otherwise, propagate to the router
|
|
35
|
-
else
|
|
36
|
-
props.onClick = (e) => {
|
|
34
|
+
const handleClick: React.MouseEventHandler<HTMLAnchorElement> | undefined = openNewTab
|
|
35
|
+
? onClick
|
|
36
|
+
: (e) => {
|
|
37
37
|
history?.push(to);
|
|
38
38
|
e.preventDefault();
|
|
39
|
-
return false
|
|
40
|
-
}
|
|
39
|
+
return false;
|
|
40
|
+
};
|
|
41
41
|
|
|
42
42
|
return (
|
|
43
|
-
<a
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
43
|
+
<a
|
|
44
|
+
{...props}
|
|
45
|
+
href={to}
|
|
46
|
+
target={resolvedTarget}
|
|
47
|
+
onClick={handleClick}
|
|
48
|
+
class={classNameAttr ?? className}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</a>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
@@ -15,19 +15,15 @@ import type Page from '../response/page';
|
|
|
15
15
|
----------------------------------*/
|
|
16
16
|
|
|
17
17
|
export default ({ page }: { page: Page }) => {
|
|
18
|
-
|
|
19
18
|
/*----------------------------------
|
|
20
19
|
- CONTEXT
|
|
21
20
|
----------------------------------*/
|
|
22
21
|
const context = useContext();
|
|
23
22
|
|
|
24
23
|
// Bind data
|
|
25
|
-
const [apiData, setApiData] = React.useState<{[k: string]: any} | null>(
|
|
24
|
+
const [apiData, setApiData] = React.useState<{ [k: string]: any } | null>(page.data || {});
|
|
26
25
|
page.setAllData = setApiData;
|
|
27
|
-
const fullData = {
|
|
28
|
-
...context.data,
|
|
29
|
-
...apiData
|
|
30
|
-
}
|
|
26
|
+
const fullData = { ...context.data, ...apiData };
|
|
31
27
|
|
|
32
28
|
// Temporary fix: context.page may not be updated at this stage
|
|
33
29
|
// Seems to be the case when we change page, but still same page component with different data
|
|
@@ -38,18 +34,14 @@ export default ({ page }: { page: Page }) => {
|
|
|
38
34
|
|
|
39
35
|
// Page component has not changed, but data were updated (ex: url parameters change)
|
|
40
36
|
React.useEffect(() => {
|
|
41
|
-
|
|
42
37
|
setApiData(page.data);
|
|
43
|
-
|
|
44
38
|
}, [page.data]);
|
|
45
39
|
|
|
40
|
+
const rendererProps = { ...context, ...fullData } as Parameters<NonNullable<typeof page.renderer>>[0];
|
|
41
|
+
|
|
46
42
|
/*----------------------------------
|
|
47
43
|
- RENDER
|
|
48
44
|
----------------------------------*/
|
|
49
45
|
// Make request parameters and api data accessible from the page component
|
|
50
|
-
return page.renderer ?
|
|
51
|
-
|
|
52
|
-
<page.renderer {...context} />
|
|
53
|
-
|
|
54
|
-
) : <>Renderer missing</>
|
|
55
|
-
}
|
|
46
|
+
return page.renderer ? <page.renderer {...rendererProps} /> : <>Renderer missing</>;
|
|
47
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
// Core
|
|
9
|
+
import useContext from '@/client/context';
|
|
10
|
+
|
|
11
|
+
// Specific
|
|
12
|
+
import type ClientRouter from '..';
|
|
13
|
+
import PageComponent from './Page';
|
|
14
|
+
|
|
15
|
+
/*----------------------------------
|
|
16
|
+
- TYPES
|
|
17
|
+
----------------------------------*/
|
|
18
|
+
|
|
19
|
+
export type TProps = { service?: ClientRouter; loaderComponent?: React.ComponentType<{ isLoading: boolean }> };
|
|
20
|
+
|
|
21
|
+
/*----------------------------------
|
|
22
|
+
- COMPONENT
|
|
23
|
+
----------------------------------*/
|
|
24
|
+
export default function RouterComponent({ service: _clientRouter, loaderComponent: _loaderComponent }: TProps) {
|
|
25
|
+
const context = useContext();
|
|
26
|
+
const currentPage = context.page;
|
|
27
|
+
|
|
28
|
+
if (!currentPage) return null;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<PageComponent
|
|
32
|
+
page={currentPage as Parameters<typeof PageComponent>[0]['page']}
|
|
33
|
+
key={currentPage.chunkId === undefined ? undefined : 'page_' + currentPage.chunkId}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|