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.
Files changed (156) hide show
  1. package/.dockerignore +10 -0
  2. package/Rte.zip +0 -0
  3. package/cli/app/config.ts +54 -0
  4. package/cli/app/index.ts +195 -0
  5. package/cli/bin.js +11 -0
  6. package/cli/commands/build.ts +34 -0
  7. package/cli/commands/deploy/app.ts +29 -0
  8. package/cli/commands/deploy/web.ts +60 -0
  9. package/cli/commands/dev.ts +109 -0
  10. package/cli/commands/init.ts +85 -0
  11. package/cli/compiler/client/identite.ts +72 -0
  12. package/cli/compiler/client/index.ts +334 -0
  13. package/cli/compiler/common/babel/index.ts +170 -0
  14. package/cli/compiler/common/babel/plugins/index.ts +0 -0
  15. package/cli/compiler/common/babel/plugins/services.ts +579 -0
  16. package/cli/compiler/common/babel/routes/imports.ts +127 -0
  17. package/cli/compiler/common/babel/routes/routes.ts +1130 -0
  18. package/cli/compiler/common/files/autres.ts +39 -0
  19. package/cli/compiler/common/files/images.ts +35 -0
  20. package/cli/compiler/common/files/style.ts +78 -0
  21. package/cli/compiler/common/index.ts +154 -0
  22. package/cli/compiler/index.ts +532 -0
  23. package/cli/compiler/server/index.ts +211 -0
  24. package/cli/index.ts +189 -0
  25. package/cli/paths.ts +165 -0
  26. package/cli/print.ts +12 -0
  27. package/cli/tsconfig.json +38 -0
  28. package/cli/utils/index.ts +22 -0
  29. package/cli/utils/keyboard.ts +78 -0
  30. package/client/app/component.tsx +54 -0
  31. package/client/app/index.ts +142 -0
  32. package/client/app/service.ts +34 -0
  33. package/client/app.tsconfig.json +28 -0
  34. package/client/components/Button.tsx +298 -0
  35. package/client/components/Dialog/Manager.tsx +309 -0
  36. package/client/components/Dialog/card.tsx +208 -0
  37. package/client/components/Dialog/index.less +151 -0
  38. package/client/components/Dialog/status.less +176 -0
  39. package/client/components/Dialog/status.tsx +48 -0
  40. package/client/components/index.ts +2 -0
  41. package/client/components/types.d.ts +3 -0
  42. package/client/data/input.ts +32 -0
  43. package/client/global.d.ts +5 -0
  44. package/client/hooks.ts +22 -0
  45. package/client/index.ts +6 -0
  46. package/client/pages/_layout/index.less +6 -0
  47. package/client/pages/_layout/index.tsx +43 -0
  48. package/client/pages/bug.tsx.old +60 -0
  49. package/client/pages/useHeader.tsx +50 -0
  50. package/client/services/captcha/index.ts +67 -0
  51. package/client/services/router/components/Link.tsx +46 -0
  52. package/client/services/router/components/Page.tsx +55 -0
  53. package/client/services/router/components/router.tsx +218 -0
  54. package/client/services/router/index.tsx +521 -0
  55. package/client/services/router/request/api.ts +267 -0
  56. package/client/services/router/request/history.ts +5 -0
  57. package/client/services/router/request/index.ts +53 -0
  58. package/client/services/router/request/multipart.ts +147 -0
  59. package/client/services/router/response/index.tsx +128 -0
  60. package/client/services/router/response/page.ts +86 -0
  61. package/client/services/socket/index.ts +147 -0
  62. package/client/utils/dom.ts +77 -0
  63. package/common/app/index.ts +9 -0
  64. package/common/data/chaines/index.ts +54 -0
  65. package/common/data/dates.ts +179 -0
  66. package/common/data/markdown.ts +73 -0
  67. package/common/data/rte/nodes.ts +83 -0
  68. package/common/data/stats.ts +90 -0
  69. package/common/errors/index.tsx +326 -0
  70. package/common/router/index.ts +213 -0
  71. package/common/router/layouts.ts +93 -0
  72. package/common/router/register.ts +55 -0
  73. package/common/router/request/api.ts +77 -0
  74. package/common/router/request/index.ts +35 -0
  75. package/common/router/response/index.ts +45 -0
  76. package/common/router/response/page.ts +128 -0
  77. package/common/utils/rte.ts +183 -0
  78. package/common/utils.ts +7 -0
  79. package/doc/TODO.md +71 -0
  80. package/doc/front/router.md +27 -0
  81. package/doc/workspace/workspace.png +0 -0
  82. package/doc/workspace/workspace2.png +0 -0
  83. package/doc/workspace/workspace_26.01.22.png +0 -0
  84. package/package.json +171 -0
  85. package/server/app/commands.ts +141 -0
  86. package/server/app/container/config.ts +203 -0
  87. package/server/app/container/console/index.ts +550 -0
  88. package/server/app/container/index.ts +137 -0
  89. package/server/app/index.ts +273 -0
  90. package/server/app/service/container.ts +88 -0
  91. package/server/app/service/index.ts +235 -0
  92. package/server/app.tsconfig.json +28 -0
  93. package/server/context.ts +4 -0
  94. package/server/index.ts +4 -0
  95. package/server/services/auth/index.ts +250 -0
  96. package/server/services/auth/old.ts +277 -0
  97. package/server/services/auth/router/index.ts +95 -0
  98. package/server/services/auth/router/request.ts +54 -0
  99. package/server/services/auth/router/service.json +6 -0
  100. package/server/services/auth/service.json +6 -0
  101. package/server/services/cache/commands.ts +41 -0
  102. package/server/services/cache/index.ts +297 -0
  103. package/server/services/cache/service.json +6 -0
  104. package/server/services/cron/CronTask.ts +86 -0
  105. package/server/services/cron/index.ts +112 -0
  106. package/server/services/cron/service.json +6 -0
  107. package/server/services/disks/driver.ts +103 -0
  108. package/server/services/disks/drivers/local/index.ts +188 -0
  109. package/server/services/disks/drivers/local/service.json +6 -0
  110. package/server/services/disks/drivers/s3/index.ts +301 -0
  111. package/server/services/disks/drivers/s3/service.json +6 -0
  112. package/server/services/disks/index.ts +90 -0
  113. package/server/services/disks/service.json +6 -0
  114. package/server/services/email/index.ts +188 -0
  115. package/server/services/email/utils.ts +53 -0
  116. package/server/services/fetch/index.ts +201 -0
  117. package/server/services/fetch/service.json +7 -0
  118. package/server/services/models.7z +0 -0
  119. package/server/services/prisma/Facet.ts +142 -0
  120. package/server/services/prisma/index.ts +201 -0
  121. package/server/services/prisma/service.json +6 -0
  122. package/server/services/router/http/index.ts +217 -0
  123. package/server/services/router/http/multipart.ts +102 -0
  124. package/server/services/router/http/session.ts.old +40 -0
  125. package/server/services/router/index.ts +801 -0
  126. package/server/services/router/request/api.ts +87 -0
  127. package/server/services/router/request/index.ts +184 -0
  128. package/server/services/router/request/service.ts +21 -0
  129. package/server/services/router/request/validation/zod.ts +180 -0
  130. package/server/services/router/response/index.ts +338 -0
  131. package/server/services/router/response/mask/Filter.ts +323 -0
  132. package/server/services/router/response/mask/index.ts +60 -0
  133. package/server/services/router/response/mask/selecteurs.ts +92 -0
  134. package/server/services/router/response/page/document.tsx +160 -0
  135. package/server/services/router/response/page/index.tsx +196 -0
  136. package/server/services/router/service.json +6 -0
  137. package/server/services/router/service.ts +36 -0
  138. package/server/services/schema/index.ts +44 -0
  139. package/server/services/schema/request.ts +49 -0
  140. package/server/services/schema/router/index.ts +28 -0
  141. package/server/services/schema/router/service.json +6 -0
  142. package/server/services/schema/service.json +6 -0
  143. package/server/services/security/encrypt/aes/index.ts +85 -0
  144. package/server/services/security/encrypt/aes/service.json +6 -0
  145. package/server/services/socket/index.ts +162 -0
  146. package/server/services/socket/scope.ts +226 -0
  147. package/server/services/socket/service.json +6 -0
  148. package/server/services_old/SocketClient.ts +92 -0
  149. package/server/services_old/Token.old.ts +97 -0
  150. package/server/utils/slug.ts +79 -0
  151. package/tsconfig.common.json +45 -0
  152. package/tsconfig.json +3 -0
  153. package/types/aliases.d.ts +54 -0
  154. package/types/global/modules.d.ts +49 -0
  155. package/types/global/utils.d.ts +103 -0
  156. package/types/icons.d.ts +1 -0
@@ -0,0 +1,78 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // npm
6
+ import readline, { Key } from 'readline';
7
+
8
+ /*----------------------------------
9
+ - TYPES
10
+ ----------------------------------*/
11
+
12
+ type TKeyboardCommand = {
13
+ remove?: boolean,
14
+ run: (str: string, chunk: string, key: Key) => void
15
+ }
16
+
17
+ /*----------------------------------
18
+ - METHODS
19
+ ----------------------------------*/
20
+ class KeyboardCommands {
21
+
22
+ private commands: { [input: string]: TKeyboardCommand } = {}
23
+
24
+ public constructor() {
25
+ this.listen();
26
+ }
27
+
28
+ private listen() {
29
+
30
+ readline.emitKeypressEvents(process.stdin);
31
+ process.stdin.setRawMode(true);
32
+ process.stdin.on('keypress', async (chunk: string, key: Key) => {
33
+
34
+ let str = key.name;
35
+ if (!str) return;
36
+ if (str === 'return') str = 'enter';
37
+
38
+ if (key.ctrl) str = 'ctrl+' + str;
39
+ if (key.shift) str = 'shift+' + str;
40
+ if (key.meta) str = 'meta+' + str;
41
+
42
+ const kCommand = this.commands[str] || this.commands.fallback;
43
+ if (kCommand) {
44
+
45
+ kCommand.run(str, chunk, key);
46
+
47
+ if (kCommand.remove)
48
+ delete this.commands[str];
49
+ }
50
+
51
+ if (str === 'ctrl+c') {
52
+
53
+ console.log(`Exiting ...`);
54
+ process.exit(0);
55
+
56
+ }
57
+
58
+
59
+ });
60
+ }
61
+
62
+
63
+ public input(str: string, run: TKeyboardCommand["run"], options: Omit<TKeyboardCommand, 'run'> = {}) {
64
+ this.commands[str] = { run, ...options }
65
+ }
66
+
67
+ public waitForInput(str: string): Promise<void> {
68
+ return new Promise((resolve) => {
69
+ this.commands[str] = {
70
+ run: () => resolve(),
71
+ remove: true
72
+ }
73
+ });
74
+ }
75
+
76
+ }
77
+
78
+ export default new KeyboardCommands
@@ -0,0 +1,54 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import React from 'react';
7
+
8
+ // Core
9
+ import type { Layout } from '@common/router';
10
+ import { ReactClientContext } from '@/client/context';
11
+ import DialogManager from '@client/components/Dialog/Manager'
12
+
13
+ // Core components
14
+ import RouterComponent from '@client/services/router/components/router';
15
+ import type { TClientOrServerContextForPage } from '@common/router';
16
+
17
+ /*----------------------------------
18
+ - COMPOSANT
19
+ ----------------------------------*/
20
+ export default function App({ context }: {
21
+ context: TClientOrServerContextForPage,
22
+ }) {
23
+
24
+ const curLayout = context.page?.layout;
25
+ const [layout, setLayout] = React.useState<Layout | false | undefined>(curLayout);
26
+ const [apiData, setApiData] = React.useState<{ [k: string]: any } | null>(context.page?.data || {});
27
+
28
+ // TODO: context.page is always provided in the context on the client side
29
+ if (context.app.side === "client")
30
+ context.app.setLayout = setLayout;
31
+
32
+ return (
33
+ <ReactClientContext.Provider value={context}>
34
+
35
+ <DialogManager context={context} />
36
+
37
+ {!layout ? <>
38
+ {/* TODO: move to app, because here, we're not aware that the router service has been defined */}
39
+ <RouterComponent service={context.Router} />
40
+ </> : <> {/* Same as router/components/Page.tsx */}
41
+ <layout.Component
42
+ // Services
43
+ {...context}
44
+ // API data & URL params
45
+ data={{
46
+ ...apiData,
47
+ ...context.request.data
48
+ }}
49
+ />
50
+ </>}
51
+
52
+ </ReactClientContext.Provider>
53
+ )
54
+ }
@@ -0,0 +1,142 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import React from 'react';
7
+
8
+ if (typeof window === 'undefined')
9
+ throw new Error(`This file shouldn't be loaded on server side !!!!`);
10
+
11
+ window.dev && require('preact/debug');
12
+
13
+ // Core
14
+ import { CoreError, InputErrorSchema } from '@common/errors';
15
+ import type { Layout } from '@common/router';
16
+ import { createDialog } from '@client/components/Dialog/Manager';
17
+
18
+ // Local
19
+ import type { AnyService } from './service';
20
+
21
+ export { default as Service } from './service';
22
+
23
+ /*----------------------------------
24
+ - TYPES
25
+ ----------------------------------*/
26
+
27
+ declare global {
28
+ interface Window {
29
+ dev: boolean,
30
+ /*context: ClientContext,
31
+ user: User,*/
32
+ /*context: ClientContext,
33
+ user: User,*/
34
+ }
35
+ }
36
+
37
+ export type TBugReportInfos = {
38
+ stacktrace?: string,
39
+ observation?: string,
40
+ before?: string,
41
+ }
42
+
43
+ export type TClientBugReportInfos = TBugReportInfos & {
44
+ context?: string,
45
+ guiVersion: string,
46
+ url: string,
47
+ }
48
+
49
+ // Without prettify, we don't get a clear list of the class properties
50
+ type Prettify<T> = {
51
+ [K in keyof T]: T[K];
52
+ } & {};
53
+
54
+ export type ApplicationProperties = Prettify<keyof Application>;
55
+
56
+ /*----------------------------------
57
+ - CLASS
58
+ ----------------------------------*/
59
+ export default abstract class Application {
60
+
61
+ public side = 'client' as 'client';
62
+
63
+ private servicesList: AnyService[] = []
64
+
65
+ // TODO: merge modal and toast in the same instance
66
+ public modal = createDialog(this, false);
67
+ public toast = createDialog(this, true);
68
+
69
+ public constructor() {
70
+
71
+ }
72
+
73
+ public registerService( service: AnyService ) {
74
+ console.log(`[app] Register service`, service.constructor?.name);
75
+ this.servicesList.push(service);
76
+ }
77
+
78
+ public start() {
79
+ this.bindErrorHandlers();
80
+ this.startServices();
81
+ this.boot();
82
+ }
83
+
84
+ public abstract boot(): void;
85
+
86
+ public startServices() {
87
+
88
+ console.log(`[app] Starting ${this.servicesList.length} services.`);
89
+
90
+ for (const service of this.servicesList) {
91
+ console.log(`[app] Start service`, service);
92
+ service.start();
93
+ }
94
+
95
+ console.log(`[app] All ${this.servicesList.length} services were started.`);
96
+ }
97
+
98
+ public bindErrorHandlers() {
99
+
100
+ // Impossible de recup le stacktrace ...
101
+ window.addEventListener("unhandledrejection", (e) => {
102
+ const error = new Error(e.reason); // How to get stacktrace ?
103
+ this.handleError(error);
104
+ });
105
+
106
+ window.onerror = (message, file, line, col, stacktrace) => {
107
+ console.error(`Exception catched by method B`, message);
108
+ this.reportBug({
109
+ stacktrace: stacktrace?.stack || JSON.stringify({ message, file, line, col })
110
+ }).then(() => {
111
+
112
+ // TODO in toas service: app.on('bug', () => toast.warning( ... ))
113
+ /*context?.toast.warning("Bug detected",
114
+ "A bug report has been sent, because I've detected a bug on the interface. I'm really sorry for the interruption.",
115
+ null,
116
+ { autohide: false });*/
117
+
118
+ })
119
+ }
120
+ }
121
+
122
+ public abstract handleError( error: CoreError | Error );
123
+
124
+ // TODO: move on app side
125
+ public reportBug = (infos: TBugReportInfos) => fetch('/feedback/bug/ui', {
126
+ method: 'POST',
127
+ headers: {
128
+ 'Accept': "application/json",
129
+ 'Content-Type': 'application/json'
130
+ },
131
+ body: JSON.stringify({
132
+ url: window.location.pathname,
133
+ context: JSON.stringify(window["ssr"]),
134
+ guiVersion: BUILD_DATE,
135
+ ...infos
136
+ })
137
+ })
138
+
139
+ public setLayout(layout: Layout) {
140
+ throw new Error(`page.setLayout has been called before the function is assigned from the <App /> component.`);
141
+ };
142
+ }
@@ -0,0 +1,34 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ import type Application from ".";
6
+
7
+ /*----------------------------------
8
+ - TYPES: OPTIONS
9
+ ----------------------------------*/
10
+
11
+ export type AnyService = Service<{}, Application>
12
+
13
+ /*----------------------------------
14
+ - CLASS
15
+ ----------------------------------*/
16
+ export default abstract class Service<
17
+ TConfig extends {},
18
+ TApplication extends Application
19
+ > {
20
+ public constructor(
21
+ public app: TApplication,
22
+ public config: TConfig,
23
+ ) {
24
+
25
+ // No client service should be loaded from server side
26
+ if (typeof window === 'undefined')
27
+ throw new Error(`Client services shouldn't be loaded on server side.`);
28
+
29
+ // Make the app aware of his services
30
+ app.registerService(this);
31
+ }
32
+
33
+ public abstract start(): void;
34
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "extends": "../../node_modules/proteum/tsconfig.common.json",
3
+ "compilerOptions": {
4
+ "rootDir": "..",
5
+ "baseUrl": "..",
6
+ "paths": {
7
+
8
+ "@client/*": ["../node_modules/proteum/client/*"],
9
+ "@common/*": ["../node_modules/proteum/common/*"],
10
+
11
+ // Only used for typings (ex: ServerResponse)
12
+ // Removed before webpack compilation
13
+ "@server/*": ["../node_modules/proteum/server/*"],
14
+ "@/*": ["./*"],
15
+
16
+ // ATTENTION: Les références à preact doivent toujours pointer vers la même instance
17
+ "react": ["preact/compat"],
18
+ "react-dom/test-utils": ["preact/test-utils"],
19
+ "react-dom": ["preact/compat"], // Must be below test-utils
20
+ "react/jsx-runtime": ["preact/jsx-runtime"],
21
+ "preact/jsx-runtime": ["preact/jsx-runtime"]
22
+ }
23
+ },
24
+ "include": [
25
+ ".",
26
+ "../../node_modules/proteum/types/global"
27
+ ]
28
+ }
@@ -0,0 +1,298 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import React from 'react';
7
+ import { VNode, RefObject,ComponentChild } from 'preact';
8
+
9
+ // Core
10
+ import { shouldOpenNewTab } from '@client/services/router/components/Link';
11
+ import { history } from '@client/services/router/request/history';
12
+ import useContext from '@/client/context';
13
+
14
+ /*----------------------------------
15
+ - TYPES
16
+ ----------------------------------*/
17
+
18
+ export type Props = {
19
+
20
+ id?: string,
21
+ refElem?: RefObject<HTMLElement>,
22
+
23
+ icon?: ComponentChild,
24
+ iconR?: ComponentChild,
25
+
26
+ prefix?: ComponentChild,
27
+ suffix?: ComponentChild,
28
+
29
+ tag?: "a" | "button",
30
+ type?: 'guide' | 'primary' | 'secondary' | 'link',
31
+ shape?: 'default' | 'icon' | 'tile' | 'pill' | 'custom',
32
+ size?: TComponentSize,
33
+ class?: string,
34
+
35
+ state?: [string, React.StateUpdater<string>],
36
+ active?: boolean,
37
+ selected?: boolean,
38
+ disabled?: boolean,
39
+ loading?: boolean,
40
+ autoFocus?: boolean,
41
+ onClick?: (e: MouseEvent) => any,
42
+ async?: boolean,
43
+
44
+ submenu?: ComponentChild,
45
+ nav?: boolean | 'exact'
46
+
47
+ // SEO: if icon only, should provinde a hint (aria-label)
48
+ } & ({
49
+ hint: string,
50
+ children?: ComponentChild | ComponentChild[],
51
+ } | {
52
+ children: ComponentChild | ComponentChild[],
53
+ hint?: string,
54
+ }) & (TButtonProps | TLinkProps)
55
+
56
+ export type TButtonProps = React.JSX.HTMLAttributes<HTMLButtonElement>
57
+
58
+ export type TLinkProps = React.JSX.HTMLAttributes<HTMLAnchorElement>
59
+
60
+ /*----------------------------------
61
+ - HELPERS
62
+ ----------------------------------*/
63
+ const trimSlash = (str: string): string => {
64
+ return str.endsWith('/') ? str.slice(0, -1) : str;
65
+ }
66
+
67
+ const isCurrentUrl = (currentUrl: string, url: string, exact?: boolean) => {
68
+ return (
69
+ (exact && (url === currentUrl || trimSlash(url) === currentUrl))
70
+ ||
71
+ (!exact && currentUrl.startsWith(url))
72
+ )
73
+ }
74
+
75
+ /*----------------------------------
76
+ - CONTROLEUR
77
+ ----------------------------------*/
78
+ export default ({
79
+
80
+ id,
81
+
82
+ // Content
83
+ icon, prefix,
84
+ children,
85
+ iconR, suffix,
86
+ submenu,
87
+ nav,
88
+ hint,
89
+
90
+ // Style
91
+ class: className,
92
+ shape,
93
+ size,
94
+ type,
95
+
96
+ // Interactions
97
+ active,
98
+ selected,
99
+ state: stateUpdater,
100
+ disabled,
101
+ loading,
102
+ //autoFocus,
103
+ async,
104
+
105
+ // HTML attributes
106
+ tag: Tag,
107
+ refElem,
108
+ ...props
109
+
110
+ }: Props) => {
111
+
112
+ const ctx = useContext();
113
+ let [isSelected, setIsSelected] = React.useState(false);
114
+ let [isActive, setIsActive] = React.useState(false);
115
+ const [isLoading, setLoading] = React.useState(false);
116
+
117
+ if (isLoading || loading) {
118
+ icon = <i src="spin" />
119
+ iconR = undefined;
120
+ disabled = true;
121
+ }
122
+
123
+ if (stateUpdater && id !== undefined) {
124
+ const [active, setActive] = stateUpdater;
125
+ if (id === active)
126
+ isSelected = true;
127
+ props.onClick = () => setActive(id);
128
+ }
129
+
130
+ // Hint
131
+ if (hint !== undefined) {
132
+ props['aria-label'] = hint;
133
+ props.title = hint;
134
+ }
135
+
136
+ // Shape classes
137
+ const classNames: string[] = ['btn'];
138
+ if (className)
139
+ classNames.push(className);
140
+
141
+ if (shape !== undefined) {
142
+ if (shape === 'tile')
143
+ classNames.push('col');
144
+ else
145
+ classNames.push(shape);
146
+ }
147
+
148
+ if (size !== undefined)
149
+ classNames.push(size);
150
+
151
+ if (icon) {
152
+ if (children === undefined)
153
+ classNames.push('icon');
154
+ }
155
+
156
+ // state classes
157
+ const [isMouseDown, setMouseDown] = React.useState(false);
158
+ props.onMouseDown = () => setMouseDown(true);
159
+ props.onMouseUp = () => setMouseDown(false);
160
+ props.onMouseLeave = () => setMouseDown(false);
161
+
162
+ // Theming & state
163
+ if (isMouseDown)
164
+ classNames.push('pressed');
165
+ else if (selected || isSelected === true)
166
+ classNames.push('bg accent');
167
+ else if (type !== undefined)
168
+ classNames.push(type === 'link' ? type : (' bg ' + type));
169
+
170
+ if (active || isActive === true)
171
+ classNames.push('active');
172
+
173
+ // Icon
174
+ if (prefix === undefined && icon !== undefined)
175
+ prefix = typeof icon === "string" ? <i class={"svg-" + icon} /> : icon;
176
+ if (suffix === undefined && iconR !== undefined)
177
+ suffix = typeof iconR === "string" ? <i class={"svg-" + iconR} /> : iconR;
178
+
179
+ // Render
180
+ if ('link' in props || Tag === "a") {
181
+
182
+ // Link (only if enabled)
183
+ if (!disabled) {
184
+
185
+ props.href = props.link;
186
+
187
+ // External = open in new tab by default
188
+ if (shouldOpenNewTab( props.href, props.target ))
189
+ props.target = '_blank';
190
+ }
191
+
192
+ // Nav
193
+ if (nav && props.target === undefined) {
194
+
195
+ const checkIfCurrentUrl = (url: string) =>
196
+ isCurrentUrl(url, props.link, nav === 'exact');
197
+
198
+ React.useEffect(() => {
199
+
200
+ // Init
201
+ if (checkIfCurrentUrl(ctx.request.path))
202
+ setIsActive(true);
203
+
204
+ // On location change
205
+ return history?.listen(({ location }) => {
206
+
207
+ setIsActive( checkIfCurrentUrl(location.pathname) );
208
+
209
+ })
210
+
211
+ }, []);
212
+ }
213
+
214
+ Tag = 'a';
215
+
216
+ } else {
217
+ Tag = 'button';
218
+
219
+ // Avoid to trigget onclick when presing enter
220
+ if (type !== 'primary')
221
+ props.type = 'button';
222
+ else
223
+ props.type = 'submit';
224
+ }
225
+
226
+ let render: VNode = (
227
+ <Tag {...props} id={id} class={classNames.join(' ')} disabled={disabled} ref={refElem} onClick={(e: MouseEvent) => {
228
+
229
+ // annulation si:
230
+ // - Pas clic gauche
231
+ // - Event annulé
232
+ if (e.button !== 0)
233
+ return;
234
+
235
+ // Disabled
236
+ if (disabled)
237
+ return false;
238
+
239
+ // Custom event
240
+ if (props.onClick !== undefined) {
241
+
242
+ const returned = props.onClick(e);
243
+ if (async && returned?.then) {
244
+ setLoading(true);
245
+ returned.finally(() => setLoading(false));
246
+ }
247
+ }
248
+
249
+ // Link
250
+ let nativeEvent: boolean = false;
251
+ if (('link' in props) && !e.defaultPrevented) {
252
+
253
+ // Nouvelle fenetre = event par defaut
254
+ if (props.target === '_blank') {
255
+
256
+ nativeEvent = true;
257
+
258
+ // Page change = loading indicator
259
+ } else if (props.target === "_self") {
260
+
261
+ setLoading(true);
262
+ window.location.href = props.link;
263
+
264
+ } else {
265
+
266
+ history?.push(props.link);
267
+ }
268
+ }
269
+
270
+ if (!nativeEvent) {
271
+ e.preventDefault();
272
+ return false;
273
+ }
274
+ }}>
275
+ {prefix}
276
+ {children === undefined
277
+ ? null
278
+ : shape === 'custom'
279
+ ? children : (
280
+ <span class={"label"}>
281
+ {children}
282
+ </span>
283
+ )}
284
+ {suffix}
285
+ </Tag>
286
+ )
287
+
288
+ if (Tag === "li" || submenu) {
289
+ render = (
290
+ <li>
291
+ {render}
292
+ {submenu}
293
+ </li>
294
+ )
295
+ }
296
+
297
+ return render;
298
+ }