proteum 1.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/.dockerignore +10 -0
- package/Rte.zip +0 -0
- package/cli/app/config.ts +54 -0
- package/cli/app/index.ts +195 -0
- package/cli/bin.js +11 -0
- package/cli/commands/build.ts +34 -0
- package/cli/commands/deploy/app.ts +29 -0
- package/cli/commands/deploy/web.ts +60 -0
- package/cli/commands/dev.ts +109 -0
- package/cli/commands/init.ts +85 -0
- package/cli/compiler/client/identite.ts +72 -0
- package/cli/compiler/client/index.ts +334 -0
- package/cli/compiler/common/babel/index.ts +170 -0
- package/cli/compiler/common/babel/plugins/index.ts +0 -0
- package/cli/compiler/common/babel/plugins/services.ts +579 -0
- package/cli/compiler/common/babel/routes/imports.ts +127 -0
- package/cli/compiler/common/babel/routes/routes.ts +1130 -0
- package/cli/compiler/common/files/autres.ts +39 -0
- package/cli/compiler/common/files/images.ts +35 -0
- package/cli/compiler/common/files/style.ts +78 -0
- package/cli/compiler/common/index.ts +154 -0
- package/cli/compiler/index.ts +532 -0
- package/cli/compiler/server/index.ts +211 -0
- package/cli/index.ts +189 -0
- package/cli/paths.ts +165 -0
- package/cli/print.ts +12 -0
- package/cli/tsconfig.json +38 -0
- package/cli/utils/index.ts +22 -0
- package/cli/utils/keyboard.ts +78 -0
- package/client/app/component.tsx +54 -0
- package/client/app/index.ts +142 -0
- package/client/app/service.ts +34 -0
- package/client/app.tsconfig.json +28 -0
- package/client/components/Button.tsx +298 -0
- package/client/components/Dialog/Manager.tsx +309 -0
- package/client/components/Dialog/card.tsx +208 -0
- package/client/components/Dialog/index.less +151 -0
- package/client/components/Dialog/status.less +176 -0
- package/client/components/Dialog/status.tsx +48 -0
- package/client/components/index.ts +2 -0
- package/client/components/types.d.ts +3 -0
- package/client/data/input.ts +32 -0
- package/client/global.d.ts +5 -0
- package/client/hooks.ts +22 -0
- package/client/index.ts +6 -0
- package/client/pages/_layout/index.less +6 -0
- package/client/pages/_layout/index.tsx +43 -0
- package/client/pages/bug.tsx.old +60 -0
- package/client/pages/useHeader.tsx +50 -0
- package/client/services/captcha/index.ts +67 -0
- package/client/services/router/components/Link.tsx +46 -0
- package/client/services/router/components/Page.tsx +55 -0
- package/client/services/router/components/router.tsx +218 -0
- package/client/services/router/index.tsx +521 -0
- package/client/services/router/request/api.ts +267 -0
- package/client/services/router/request/history.ts +5 -0
- package/client/services/router/request/index.ts +53 -0
- package/client/services/router/request/multipart.ts +147 -0
- package/client/services/router/response/index.tsx +128 -0
- package/client/services/router/response/page.ts +86 -0
- package/client/services/socket/index.ts +147 -0
- package/client/utils/dom.ts +77 -0
- package/common/app/index.ts +9 -0
- package/common/data/chaines/index.ts +54 -0
- package/common/data/dates.ts +179 -0
- package/common/data/markdown.ts +73 -0
- package/common/data/rte/nodes.ts +83 -0
- package/common/data/stats.ts +90 -0
- package/common/errors/index.tsx +326 -0
- package/common/router/index.ts +213 -0
- package/common/router/layouts.ts +93 -0
- package/common/router/register.ts +55 -0
- package/common/router/request/api.ts +77 -0
- package/common/router/request/index.ts +35 -0
- package/common/router/response/index.ts +45 -0
- package/common/router/response/page.ts +128 -0
- package/common/utils/rte.ts +183 -0
- package/common/utils.ts +7 -0
- package/doc/TODO.md +71 -0
- package/doc/front/router.md +27 -0
- package/doc/workspace/workspace.png +0 -0
- package/doc/workspace/workspace2.png +0 -0
- package/doc/workspace/workspace_26.01.22.png +0 -0
- package/package.json +171 -0
- package/server/app/commands.ts +141 -0
- package/server/app/container/config.ts +203 -0
- package/server/app/container/console/index.ts +550 -0
- package/server/app/container/index.ts +137 -0
- package/server/app/index.ts +273 -0
- package/server/app/service/container.ts +88 -0
- package/server/app/service/index.ts +235 -0
- package/server/app.tsconfig.json +28 -0
- package/server/context.ts +4 -0
- package/server/index.ts +4 -0
- package/server/services/auth/index.ts +250 -0
- package/server/services/auth/old.ts +277 -0
- package/server/services/auth/router/index.ts +95 -0
- package/server/services/auth/router/request.ts +54 -0
- package/server/services/auth/router/service.json +6 -0
- package/server/services/auth/service.json +6 -0
- package/server/services/cache/commands.ts +41 -0
- package/server/services/cache/index.ts +297 -0
- package/server/services/cache/service.json +6 -0
- package/server/services/cron/CronTask.ts +86 -0
- package/server/services/cron/index.ts +112 -0
- package/server/services/cron/service.json +6 -0
- package/server/services/disks/driver.ts +103 -0
- package/server/services/disks/drivers/local/index.ts +188 -0
- package/server/services/disks/drivers/local/service.json +6 -0
- package/server/services/disks/drivers/s3/index.ts +301 -0
- package/server/services/disks/drivers/s3/service.json +6 -0
- package/server/services/disks/index.ts +90 -0
- package/server/services/disks/service.json +6 -0
- package/server/services/email/index.ts +188 -0
- package/server/services/email/utils.ts +53 -0
- package/server/services/fetch/index.ts +201 -0
- package/server/services/fetch/service.json +7 -0
- package/server/services/models.7z +0 -0
- package/server/services/prisma/Facet.ts +142 -0
- package/server/services/prisma/index.ts +201 -0
- package/server/services/prisma/service.json +6 -0
- package/server/services/router/http/index.ts +217 -0
- package/server/services/router/http/multipart.ts +102 -0
- package/server/services/router/http/session.ts.old +40 -0
- package/server/services/router/index.ts +801 -0
- package/server/services/router/request/api.ts +87 -0
- package/server/services/router/request/index.ts +184 -0
- package/server/services/router/request/service.ts +21 -0
- package/server/services/router/request/validation/zod.ts +180 -0
- package/server/services/router/response/index.ts +338 -0
- package/server/services/router/response/mask/Filter.ts +323 -0
- package/server/services/router/response/mask/index.ts +60 -0
- package/server/services/router/response/mask/selecteurs.ts +92 -0
- package/server/services/router/response/page/document.tsx +160 -0
- package/server/services/router/response/page/index.tsx +196 -0
- package/server/services/router/service.json +6 -0
- package/server/services/router/service.ts +36 -0
- package/server/services/schema/index.ts +44 -0
- package/server/services/schema/request.ts +49 -0
- package/server/services/schema/router/index.ts +28 -0
- package/server/services/schema/router/service.json +6 -0
- package/server/services/schema/service.json +6 -0
- package/server/services/security/encrypt/aes/index.ts +85 -0
- package/server/services/security/encrypt/aes/service.json +6 -0
- package/server/services/socket/index.ts +162 -0
- package/server/services/socket/scope.ts +226 -0
- package/server/services/socket/service.json +6 -0
- package/server/services_old/SocketClient.ts +92 -0
- package/server/services_old/Token.old.ts +97 -0
- package/server/utils/slug.ts +79 -0
- package/tsconfig.common.json +45 -0
- package/tsconfig.json +3 -0
- package/types/aliases.d.ts +54 -0
- package/types/global/modules.d.ts +49 -0
- package/types/global/utils.d.ts +103 -0
- package/types/icons.d.ts +1 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Core
|
|
6
|
+
import type ClientApplication from '@client/app';
|
|
7
|
+
import { fromJson as errorFromJson, NetworkError } from '@common/errors';
|
|
8
|
+
import ApiClientService, {
|
|
9
|
+
TPostData, TPostDataWithoutFile,
|
|
10
|
+
TApiFetchOptions, TFetcherList, TFetcherArgs, TFetcher,
|
|
11
|
+
TDataReturnedByFetchers
|
|
12
|
+
} from '@common/router/request/api';
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
// Specific
|
|
16
|
+
import type { default as Router, Request } from '..';
|
|
17
|
+
import { toMultipart } from './multipart';
|
|
18
|
+
|
|
19
|
+
/*----------------------------------
|
|
20
|
+
- TYPES
|
|
21
|
+
----------------------------------*/
|
|
22
|
+
|
|
23
|
+
const debug = false;
|
|
24
|
+
|
|
25
|
+
export type Config = {
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/*----------------------------------
|
|
30
|
+
- FUNCTION
|
|
31
|
+
----------------------------------*/
|
|
32
|
+
export default class ApiClient implements ApiClientService {
|
|
33
|
+
|
|
34
|
+
// APO Client needs to know the current request so we can monitor which api request is made from which page
|
|
35
|
+
public constructor(
|
|
36
|
+
public app: ClientApplication,
|
|
37
|
+
public request: Request,
|
|
38
|
+
public router = request.router,
|
|
39
|
+
) {
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/*----------------------------------
|
|
44
|
+
- HIGH LEVEL
|
|
45
|
+
----------------------------------*/
|
|
46
|
+
|
|
47
|
+
public fetch<FetchersList extends TFetcherList = TFetcherList>(
|
|
48
|
+
fetchers: FetchersList
|
|
49
|
+
): TDataReturnedByFetchers<FetchersList> {
|
|
50
|
+
throw new Error("api.fetch shouldn't be called here.");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public post = <TData extends unknown = unknown>(path: string, data?: TPostData, opts?: TApiFetchOptions) =>
|
|
54
|
+
this.createFetcher<TData>('POST', path, data, opts);
|
|
55
|
+
|
|
56
|
+
public set( newData: TObjetDonnees ) {
|
|
57
|
+
|
|
58
|
+
if (!('context' in this.router))
|
|
59
|
+
throw new Error("api.set is not available on server side.");
|
|
60
|
+
|
|
61
|
+
if (this.router.context.page)
|
|
62
|
+
this.router.context.page.setAllData(curData => ({ ...curData, ...newData }));
|
|
63
|
+
else
|
|
64
|
+
throw new Error(`[api] this.router.context.page undefined`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public reload( ids?: string | string[], params?: TObjetDonnees ) {
|
|
68
|
+
|
|
69
|
+
if (!('context' in this.router))
|
|
70
|
+
throw new Error("api.reload is not available on server side.");
|
|
71
|
+
|
|
72
|
+
const page = this.router.context.page;
|
|
73
|
+
|
|
74
|
+
if (ids === undefined)
|
|
75
|
+
ids = Object.keys(page.fetchers);
|
|
76
|
+
else if (typeof ids === 'string')
|
|
77
|
+
ids = [ids];
|
|
78
|
+
|
|
79
|
+
debug && console.log("[api] Reload data", ids, params, page.fetchers);
|
|
80
|
+
|
|
81
|
+
for (const id of ids) {
|
|
82
|
+
|
|
83
|
+
const fetcher = page.fetchers[id];
|
|
84
|
+
if (fetcher === undefined)
|
|
85
|
+
return console.error(`Unable to reload ${id}: Request not found in fetchers list.`);
|
|
86
|
+
|
|
87
|
+
if (params !== undefined)
|
|
88
|
+
fetcher.data = { ...(fetcher.data || {}), ...params };
|
|
89
|
+
|
|
90
|
+
debug && console.log("[api][reload]", id, fetcher.method, fetcher.path, fetcher.data);
|
|
91
|
+
|
|
92
|
+
this.fetchAsync(fetcher.method, fetcher.path, fetcher.data).then((data) => {
|
|
93
|
+
|
|
94
|
+
this.set({ [id]: data });
|
|
95
|
+
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/*----------------------------------
|
|
101
|
+
- LOW LEVEL
|
|
102
|
+
----------------------------------*/
|
|
103
|
+
public createFetcher<TData extends unknown = unknown>(...args: TFetcherArgs): TFetcher<TData> {
|
|
104
|
+
const [method, path, data, options] = args;
|
|
105
|
+
|
|
106
|
+
// Lazily create (and cache) the underlying promise so the fetcher behaves like a real promise instance.
|
|
107
|
+
let promise: Promise<TData> | undefined;
|
|
108
|
+
|
|
109
|
+
const fetcher = {
|
|
110
|
+
method, path, data, options,
|
|
111
|
+
} as TFetcher<TData>;
|
|
112
|
+
|
|
113
|
+
const getPromise = () => {
|
|
114
|
+
if (!promise)
|
|
115
|
+
promise = this.fetchAsync<TData>(fetcher.method, fetcher.path, fetcher.data, fetcher.options);
|
|
116
|
+
|
|
117
|
+
return promise;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// For async calls: api.post(...).then((data) => ...)
|
|
121
|
+
fetcher.then = (onfulfilled?: any, onrejected?: any) =>
|
|
122
|
+
getPromise().then(onfulfilled, onrejected) as any;
|
|
123
|
+
|
|
124
|
+
fetcher.catch = (onrejected?: any) =>
|
|
125
|
+
getPromise().catch(onrejected) as any;
|
|
126
|
+
|
|
127
|
+
fetcher.finally = (onfinally?: any) =>
|
|
128
|
+
getPromise().finally(onfinally) as any;
|
|
129
|
+
|
|
130
|
+
fetcher.run = () => getPromise();
|
|
131
|
+
|
|
132
|
+
return fetcher;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public async fetchAsync<TData extends unknown = unknown>(...[
|
|
136
|
+
method, path, data, options
|
|
137
|
+
]: TFetcherArgs): Promise<TData> {
|
|
138
|
+
|
|
139
|
+
/*if (options?.captcha !== undefined)
|
|
140
|
+
await this.gui.captcha.check(options?.captcha);*/
|
|
141
|
+
|
|
142
|
+
return await this.execute<TData>(method, path, data, options);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public async fetchSync(fetchers: TFetcherList, alreadyLoadedData: {}): Promise<TObjetDonnees> {
|
|
146
|
+
|
|
147
|
+
// Pick the fetchers where the data is needed
|
|
148
|
+
const fetchersToRun: TFetcherList = {};
|
|
149
|
+
let fetchersCount: number = 0;
|
|
150
|
+
for (const fetcherId in fetchers)
|
|
151
|
+
// The fetcher can be undefined
|
|
152
|
+
if (!( fetcherId in alreadyLoadedData ) && fetchers[ fetcherId ]) {
|
|
153
|
+
fetchersToRun[ fetcherId ] = fetchers[ fetcherId ]
|
|
154
|
+
fetchersCount++;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Fetch all the api data thanks to one http request
|
|
158
|
+
const fetchedData = fetchersCount === 0
|
|
159
|
+
? 0
|
|
160
|
+
: await this.execute("POST", "/api", {
|
|
161
|
+
fetchers: fetchersToRun
|
|
162
|
+
}).then((res) => {
|
|
163
|
+
|
|
164
|
+
const data: TObjetDonnees = {};
|
|
165
|
+
for (const id in res)
|
|
166
|
+
data[id] = res[id];
|
|
167
|
+
|
|
168
|
+
return data;
|
|
169
|
+
|
|
170
|
+
}).catch(e => {
|
|
171
|
+
|
|
172
|
+
// API Error hook
|
|
173
|
+
this.app.handleError(e);
|
|
174
|
+
|
|
175
|
+
throw e;
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// Errors will be catched in the caller
|
|
179
|
+
|
|
180
|
+
return { ...alreadyLoadedData, ...fetchedData }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public configure = (...[method, path, data, options = {}]: TFetcherArgs) => {
|
|
184
|
+
|
|
185
|
+
let url = this.router.url(path, {}, false);
|
|
186
|
+
|
|
187
|
+
debug && console.log(`[api] Sending request`, method, url, data);
|
|
188
|
+
|
|
189
|
+
// Create Fetch config
|
|
190
|
+
const config: With<RequestInit, 'headers'> = {
|
|
191
|
+
method: method,
|
|
192
|
+
headers: {
|
|
193
|
+
'Accept': "application/json",
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Update options depending on data
|
|
198
|
+
if (data) {
|
|
199
|
+
|
|
200
|
+
// If file included in data, need to use multipart
|
|
201
|
+
// TODO: deep check
|
|
202
|
+
const hasFile = Object.values(data).some((value) => value instanceof File);
|
|
203
|
+
if (hasFile) {
|
|
204
|
+
// GET request = Can't send files
|
|
205
|
+
if (method === "GET")
|
|
206
|
+
throw new Error("Cannot send file in GET request");
|
|
207
|
+
// Auto switch to multiplart
|
|
208
|
+
else if (options.encoding === undefined)
|
|
209
|
+
options.encoding = 'multipart';
|
|
210
|
+
else if (options.encoding !== 'multipart')
|
|
211
|
+
// Encoding set to JSON = Can't send files
|
|
212
|
+
throw new Error("Cannot send file in non-multipart request");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Data encoding
|
|
216
|
+
if (method === "GET") {
|
|
217
|
+
|
|
218
|
+
const params = new URLSearchParams( data as unknown as TPostDataWithoutFile ).toString();
|
|
219
|
+
url = `${url}?${params}`;
|
|
220
|
+
|
|
221
|
+
} else if (options.encoding === 'multipart') {
|
|
222
|
+
|
|
223
|
+
debug && console.log("[api] Multipart request", data);
|
|
224
|
+
// Browser will automatically choose the right headers
|
|
225
|
+
config.body = toMultipart(data);
|
|
226
|
+
|
|
227
|
+
} else {
|
|
228
|
+
config.headers["Content-Type"] = "application/json";
|
|
229
|
+
config.body = JSON.stringify(data);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return { url, config };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public execute<TData = unknown>(...args: TFetcherArgs): Promise<TData> {
|
|
237
|
+
const { url, config } = this.configure(...args);
|
|
238
|
+
|
|
239
|
+
console.log(`[api] Fetching`, url, config);
|
|
240
|
+
|
|
241
|
+
return fetch(url, config)
|
|
242
|
+
.then(async (response) => {
|
|
243
|
+
if (!response.ok) {
|
|
244
|
+
|
|
245
|
+
const errorData = await response.json();
|
|
246
|
+
console.warn(`[api] Failure:`, response.status, errorData);
|
|
247
|
+
const error = errorFromJson(errorData);
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
const json = await response.json() as TData;
|
|
251
|
+
debug && console.log(`[api] Success:`, json);
|
|
252
|
+
return json;
|
|
253
|
+
})
|
|
254
|
+
.catch((error) => {
|
|
255
|
+
if (error instanceof TypeError) {
|
|
256
|
+
// Network error
|
|
257
|
+
console.warn(`[api] Network Failure:`, error);
|
|
258
|
+
const networkError = new NetworkError(error.message);
|
|
259
|
+
this.app.handleError(networkError);
|
|
260
|
+
throw networkError;
|
|
261
|
+
} else {
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import { Location } from 'history';
|
|
7
|
+
|
|
8
|
+
// Core
|
|
9
|
+
import BaseRequest from '@common/router/request';
|
|
10
|
+
|
|
11
|
+
// Specific
|
|
12
|
+
import type ClientRouter from '..';
|
|
13
|
+
import ApiClient from './api';
|
|
14
|
+
import type ClientResponse from '../response';
|
|
15
|
+
|
|
16
|
+
/*----------------------------------
|
|
17
|
+
- TYPES
|
|
18
|
+
----------------------------------*/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/*----------------------------------
|
|
22
|
+
- ROUTER
|
|
23
|
+
----------------------------------*/
|
|
24
|
+
// Since we do SSR, the server router can also be passed here
|
|
25
|
+
export default class ClientRequest<TRouter extends ClientRouter = ClientRouter> extends BaseRequest {
|
|
26
|
+
|
|
27
|
+
public api: ApiClient;
|
|
28
|
+
public response?: ClientResponse<TRouter>;
|
|
29
|
+
|
|
30
|
+
public hash?: string;
|
|
31
|
+
|
|
32
|
+
public constructor(
|
|
33
|
+
location: Location,
|
|
34
|
+
public router: TRouter,
|
|
35
|
+
public app = router.app
|
|
36
|
+
) {
|
|
37
|
+
|
|
38
|
+
super(location.pathname);
|
|
39
|
+
|
|
40
|
+
this.host = window.location.host;
|
|
41
|
+
this.url = window.location.protocol + '//' + window.location.host + this.path;
|
|
42
|
+
this.hash = location.hash;
|
|
43
|
+
|
|
44
|
+
// Extract search params
|
|
45
|
+
if (location.search) {
|
|
46
|
+
this.url += location.search;
|
|
47
|
+
this.data = Object.fromEntries( new URLSearchParams( location.search ));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Request services
|
|
51
|
+
this.api = new ApiClient(this.app, this);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Core
|
|
6
|
+
import { TPostData } from '@common/router/request/api';
|
|
7
|
+
|
|
8
|
+
/*----------------------------------
|
|
9
|
+
- TYPES
|
|
10
|
+
----------------------------------*/
|
|
11
|
+
|
|
12
|
+
function mergeObjects(object1, object2) {
|
|
13
|
+
return [object1, object2].reduce(function (carry, objectToMerge) {
|
|
14
|
+
Object.keys(objectToMerge).forEach(function (objectKey) {
|
|
15
|
+
carry[objectKey] = objectToMerge[objectKey];
|
|
16
|
+
});
|
|
17
|
+
return carry;
|
|
18
|
+
}, {});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isArray(val) {
|
|
22
|
+
|
|
23
|
+
return ({}).toString.call(val) === '[object Array]';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isJsonObject(val) {
|
|
27
|
+
|
|
28
|
+
return !isArray(val) && typeof val === 'object' && !!val && !(val instanceof Blob) && !(val instanceof Date);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isAppendFunctionPresent(formData) {
|
|
32
|
+
|
|
33
|
+
return typeof formData.append === 'function';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isGlobalFormDataPresent() {
|
|
37
|
+
|
|
38
|
+
return typeof FormData === 'function';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getDefaultFormData() {
|
|
42
|
+
|
|
43
|
+
if (isGlobalFormDataPresent()) {
|
|
44
|
+
return new FormData();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function convertRecursively(
|
|
49
|
+
jsonObject: {},
|
|
50
|
+
options: TOptions,
|
|
51
|
+
formData: FormData,
|
|
52
|
+
parentKey: string
|
|
53
|
+
) {
|
|
54
|
+
|
|
55
|
+
var index = 0;
|
|
56
|
+
|
|
57
|
+
for (var key in jsonObject) {
|
|
58
|
+
|
|
59
|
+
if (jsonObject.hasOwnProperty(key)) {
|
|
60
|
+
|
|
61
|
+
var propName = parentKey || key;
|
|
62
|
+
var value = options.mapping(jsonObject[key]);
|
|
63
|
+
|
|
64
|
+
if (parentKey && isJsonObject(jsonObject)) {
|
|
65
|
+
propName = parentKey + '[' + key + ']';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (parentKey && isArray(jsonObject)) {
|
|
69
|
+
|
|
70
|
+
if (isArray(value) || options.showLeafArrayIndexes ) {
|
|
71
|
+
propName = parentKey + '[' + index + ']';
|
|
72
|
+
} else {
|
|
73
|
+
propName = parentKey + '[]';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isArray(value) || isJsonObject(value)) {
|
|
78
|
+
|
|
79
|
+
convertRecursively(value, options, formData, propName);
|
|
80
|
+
|
|
81
|
+
} else if (value instanceof FileList) {
|
|
82
|
+
|
|
83
|
+
for (var j = 0; j < value.length; j++) {
|
|
84
|
+
formData.append(propName + '[' + j + ']', value.item(j));
|
|
85
|
+
}
|
|
86
|
+
} else if (value instanceof Blob) {
|
|
87
|
+
|
|
88
|
+
formData.append(propName, value, value.name);
|
|
89
|
+
|
|
90
|
+
} else if (value instanceof Date) {
|
|
91
|
+
|
|
92
|
+
formData.append(propName, value.toISOString());
|
|
93
|
+
|
|
94
|
+
} else if (((value === null && options.includeNullValues) || value !== null) && value !== undefined) {
|
|
95
|
+
|
|
96
|
+
formData.append(propName, value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
index++;
|
|
100
|
+
}
|
|
101
|
+
return formData;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/*----------------------------------
|
|
105
|
+
- UTILS
|
|
106
|
+
----------------------------------*/
|
|
107
|
+
/* Based on https://github.com/hyperatom/json-form-data
|
|
108
|
+
Changes:
|
|
109
|
+
- Add support for FileToUpload
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
// options type
|
|
113
|
+
type TOptions = {
|
|
114
|
+
initialFormData: FormData,
|
|
115
|
+
showLeafArrayIndexes: boolean,
|
|
116
|
+
includeNullValues: boolean,
|
|
117
|
+
mapping: (value: any) => any
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const toMultipart = (jsonObject: TPostData, options?: TOptions) => {
|
|
121
|
+
|
|
122
|
+
if (options && options.initialFormData) {
|
|
123
|
+
|
|
124
|
+
if (!isAppendFunctionPresent(options.initialFormData)) {
|
|
125
|
+
throw 'initialFormData must have an append function.';
|
|
126
|
+
}
|
|
127
|
+
} else if (!isGlobalFormDataPresent()) {
|
|
128
|
+
|
|
129
|
+
throw 'This environment does not have global form data. options.initialFormData must be specified.';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
var defaultOptions = {
|
|
133
|
+
initialFormData: getDefaultFormData(),
|
|
134
|
+
showLeafArrayIndexes: true,
|
|
135
|
+
includeNullValues: false,
|
|
136
|
+
mapping: function(value) {
|
|
137
|
+
if (typeof value === 'boolean') {
|
|
138
|
+
return +value ? '1': '0';
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
var mergedOptions = mergeObjects(defaultOptions, options || {});
|
|
145
|
+
|
|
146
|
+
return convertRecursively(jsonObject, mergedOptions, mergedOptions.initialFormData);
|
|
147
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Libs
|
|
6
|
+
import type ServerRouter from '@server/services/router';
|
|
7
|
+
import type ServerResponse from '@server/services/router/response';
|
|
8
|
+
|
|
9
|
+
import type { TAnyRoute, TErrorRoute } from '@common/router';
|
|
10
|
+
import BaseResponse, { TResponseData } from '@common/router/response';
|
|
11
|
+
|
|
12
|
+
import type ClientApplication from '@client/app';
|
|
13
|
+
import type { default as ClientRouter } from '@client/services/router'
|
|
14
|
+
import type ClientResponse from '@client/services/router/response'
|
|
15
|
+
import ClientRequest from '@client/services/router/request'
|
|
16
|
+
import ClientPage from '@client/services/router/response/page'
|
|
17
|
+
import { history } from '@client/services/router/request/history';
|
|
18
|
+
|
|
19
|
+
/*----------------------------------
|
|
20
|
+
- TYPES
|
|
21
|
+
----------------------------------*/
|
|
22
|
+
|
|
23
|
+
export type TPageResponse<TRouter extends ClientRouter> = (
|
|
24
|
+
ClientResponse<TRouter, ClientPage>
|
|
25
|
+
|
|
|
26
|
+
ServerResponse<ServerRouter, ClientPage>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export type TRouterContext<
|
|
30
|
+
TRouter extends ClientRouter = ClientRouter,
|
|
31
|
+
TApplication extends ClientApplication = ClientApplication
|
|
32
|
+
> = (
|
|
33
|
+
// ClientPage context
|
|
34
|
+
{
|
|
35
|
+
app: TApplication,
|
|
36
|
+
request: ClientRequest<TRouter>,
|
|
37
|
+
route: TAnyRoute<TRouterContext>,
|
|
38
|
+
api: ClientRequest<TRouter>["api"],
|
|
39
|
+
page: ClientPage<TRouter>,
|
|
40
|
+
data: TObjetDonnees
|
|
41
|
+
}
|
|
42
|
+
// Expose client application services (api, socket, ...)
|
|
43
|
+
//TRouter["app"]
|
|
44
|
+
& TApplication
|
|
45
|
+
& ReturnType<TRouter["config"]["context"]>
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
/*----------------------------------
|
|
49
|
+
- ROUTER
|
|
50
|
+
----------------------------------*/
|
|
51
|
+
export default class ClientPageResponse<
|
|
52
|
+
TRouter extends ClientRouter,
|
|
53
|
+
TData extends TResponseData = TResponseData
|
|
54
|
+
> extends BaseResponse<TData> {
|
|
55
|
+
|
|
56
|
+
public context: TRouterContext<TRouter, TRouter["app"]>;
|
|
57
|
+
|
|
58
|
+
public constructor(
|
|
59
|
+
public request: ClientRequest<TRouter>,
|
|
60
|
+
public route: TAnyRoute | TErrorRoute,
|
|
61
|
+
|
|
62
|
+
public app = request.app,
|
|
63
|
+
) {
|
|
64
|
+
|
|
65
|
+
super(request);
|
|
66
|
+
|
|
67
|
+
request.response = this;
|
|
68
|
+
|
|
69
|
+
// Create response context for controllers
|
|
70
|
+
this.context = this.createContext();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private createContext(): TRouterContext<TRouter, TRouter["app"]> {
|
|
74
|
+
|
|
75
|
+
const basicContext: TRouterContext<TRouter, TRouter["app"]> = {
|
|
76
|
+
|
|
77
|
+
// App services (TODO: expose only services)
|
|
78
|
+
...this.request.app,
|
|
79
|
+
|
|
80
|
+
// Router context
|
|
81
|
+
app: this.app,
|
|
82
|
+
request: this.request,
|
|
83
|
+
route: this.route,
|
|
84
|
+
api: this.request.api,
|
|
85
|
+
// Will be assigned when the controller will be runned
|
|
86
|
+
page: undefined as unknown as ClientPage<TRouter>,
|
|
87
|
+
data: {},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const newContext: TRouterContext<TRouter, TRouter["app"]> = {
|
|
91
|
+
...basicContext,
|
|
92
|
+
// Custom context
|
|
93
|
+
...this.request.router.config.context( basicContext, this.request.router )
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
newContext.context = newContext;
|
|
97
|
+
|
|
98
|
+
// Update context object if already exists
|
|
99
|
+
// NOTE: we don't create a nex instance of context because we don't want to rereder the full page (inc layout) to update the context given by thr react context provider
|
|
100
|
+
const existingContext = this.request.router.context;
|
|
101
|
+
if (existingContext === undefined) {
|
|
102
|
+
|
|
103
|
+
this.request.router.context = newContext
|
|
104
|
+
|
|
105
|
+
} else for(const key in newContext)
|
|
106
|
+
existingContext[ key ] = newContext[ key ];
|
|
107
|
+
|
|
108
|
+
return newContext
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public async runController( additionnalData: {} = {} ): Promise<ClientPage> {
|
|
112
|
+
|
|
113
|
+
// Run contoller
|
|
114
|
+
const result = this.route.controller(this.context);
|
|
115
|
+
|
|
116
|
+
// Default data type for `return <raw data>`
|
|
117
|
+
if (result instanceof ClientPage)
|
|
118
|
+
await result.preRender(additionnalData);
|
|
119
|
+
else
|
|
120
|
+
throw new Error(`Unsupported response format: ${result.constructor?.name}`);
|
|
121
|
+
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public redirect(url: string) {
|
|
126
|
+
history?.replace(url);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import type { ComponentChild } from 'preact';
|
|
7
|
+
|
|
8
|
+
// Core
|
|
9
|
+
import type { TClientOrServerContextForPage, Layout, TRoute, TErrorRoute } from '@common/router';
|
|
10
|
+
import PageResponse, { TFrontRenderer } from "@common/router/response/page";
|
|
11
|
+
|
|
12
|
+
// Specific
|
|
13
|
+
import type ClientRouter from '..';
|
|
14
|
+
|
|
15
|
+
/*----------------------------------
|
|
16
|
+
- TYPES
|
|
17
|
+
----------------------------------*/
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/*----------------------------------
|
|
22
|
+
- CLASS
|
|
23
|
+
----------------------------------*/
|
|
24
|
+
|
|
25
|
+
export default class ClientPage<TRouter = ClientRouter> extends PageResponse<TRouter> {
|
|
26
|
+
|
|
27
|
+
public scrollToId: string;
|
|
28
|
+
|
|
29
|
+
public constructor(
|
|
30
|
+
public route: TRoute | TErrorRoute,
|
|
31
|
+
public component: TFrontRenderer,
|
|
32
|
+
public context: TClientOrServerContextForPage,
|
|
33
|
+
public layout?: Layout
|
|
34
|
+
) {
|
|
35
|
+
|
|
36
|
+
super(route, component, context);
|
|
37
|
+
|
|
38
|
+
this.bodyId = context.route.options.bodyId;
|
|
39
|
+
this.scrollToId = context.request.hash;
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public async preRender( data?: TObjetDonnees ) {
|
|
44
|
+
|
|
45
|
+
// Add the page to the context
|
|
46
|
+
this.context.page = this;
|
|
47
|
+
|
|
48
|
+
// Data succesfully loaded
|
|
49
|
+
this.context.data = this.data = data || await this.fetchData();
|
|
50
|
+
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/*----------------------------------
|
|
55
|
+
- ACTIONS
|
|
56
|
+
----------------------------------*/
|
|
57
|
+
// Should be called AFTER rendering the page
|
|
58
|
+
public updateClient() {
|
|
59
|
+
|
|
60
|
+
document.body.id = this.bodyId || this.id;
|
|
61
|
+
document.title = this.title || APP_NAME;
|
|
62
|
+
document.body.className = [...this.bodyClass].join(' ');
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public setAllData( callback: (data: {[k: string]: any}) => void) {
|
|
67
|
+
console.warn(`page.setAllData not yet attached to the page Reatc component.`);
|
|
68
|
+
}
|
|
69
|
+
public setData( key: string, value: ((value: any) => void) | any ) {
|
|
70
|
+
this.setAllData(old => ({
|
|
71
|
+
...old,
|
|
72
|
+
[key]: typeof value === 'function' ? value(old[key]) : value
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public setLoading(state: boolean) {
|
|
77
|
+
|
|
78
|
+
if (state === true) {
|
|
79
|
+
if (!document.body.classList.contains("loading"))
|
|
80
|
+
document.body.classList.add("loading");
|
|
81
|
+
} else {
|
|
82
|
+
document.body.classList.remove("loading");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
}
|