proteum 1.0.0 → 1.0.3
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 +9 -0
- package/cli/app/config.ts +61 -0
- package/cli/app/index.ts +227 -0
- package/cli/bin.js +35 -0
- package/cli/commands/build.ts +60 -0
- package/cli/commands/deploy/app.ts +29 -0
- package/cli/commands/deploy/web.ts +60 -0
- package/cli/commands/dev.ts +124 -0
- package/cli/commands/init.ts +85 -0
- package/cli/commands/refresh.ts +18 -0
- package/cli/compiler/client/identite.ts +69 -0
- package/cli/compiler/client/index.ts +343 -0
- package/cli/compiler/common/babel/index.ts +173 -0
- package/cli/compiler/common/babel/plugins/index.ts +0 -0
- package/cli/compiler/common/babel/plugins/services.ts +586 -0
- package/cli/compiler/common/babel/routes/imports.ts +127 -0
- package/cli/compiler/common/babel/routes/routes.ts +1170 -0
- package/cli/compiler/common/files/autres.ts +39 -0
- package/cli/compiler/common/files/images.ts +42 -0
- package/cli/compiler/common/files/style.ts +82 -0
- package/cli/compiler/common/index.ts +165 -0
- package/cli/compiler/index.ts +585 -0
- package/cli/compiler/server/index.ts +220 -0
- package/cli/index.ts +213 -0
- package/cli/paths.ts +165 -0
- package/cli/print.ts +12 -0
- package/cli/tsconfig.json +42 -0
- package/cli/utils/index.ts +22 -0
- package/cli/utils/keyboard.ts +78 -0
- package/client/app/index.ts +2 -0
- package/client/components/Dialog/Manager.tsx +3 -49
- package/client/components/Dialog/index.less +3 -1
- package/client/components/index.ts +1 -2
- package/client/services/router/index.tsx +6 -16
- package/common/errors/index.tsx +12 -31
- package/package.json +58 -22
- package/server/app/container/config.ts +20 -1
- package/server/app/container/console/index.ts +1 -1
- package/server/services/auth/index.ts +62 -27
- package/server/services/auth/router/request.ts +17 -6
- package/server/services/router/http/index.ts +3 -3
- package/server/services/router/response/index.ts +1 -1
- package/server/services/schema/request.ts +28 -10
- package/server/utils/slug.ts +0 -3
- package/tsconfig.common.json +2 -1
- package/types/global/constants.d.ts +12 -0
- package/changelog.md +0 -5
- package/client/components/Button.tsx +0 -298
- package/client/components/Dialog/card.tsx +0 -208
- package/client/data/input.ts +0 -32
- package/client/pages/bug.tsx.old +0 -60
- package/templates/composant.tsx +0 -40
- package/templates/form.ts +0 -30
- package/templates/modal.tsx +0 -47
- package/templates/modele.ts +0 -56
- package/templates/page.tsx +0 -74
- package/templates/route.ts +0 -43
- package/templates/service.ts +0 -75
- package/vscode/copyimportationpath/.eslintrc.json +0 -24
- package/vscode/copyimportationpath/.vscodeignore +0 -12
- package/vscode/copyimportationpath/CHANGELOG.md +0 -9
- package/vscode/copyimportationpath/README.md +0 -3
- package/vscode/copyimportationpath/copyimportationpath-0.0.1.vsix +0 -0
- package/vscode/copyimportationpath/out/extension.js +0 -206
- package/vscode/copyimportationpath/out/extension.js.map +0 -1
- package/vscode/copyimportationpath/package-lock.json +0 -4536
- package/vscode/copyimportationpath/package.json +0 -86
- package/vscode/copyimportationpath/src/extension.ts +0 -300
- package/vscode/copyimportationpath/tsconfig.json +0 -22
- package/vscode/copyimportationpath/vsc-extension-quickstart.md +0 -42
|
@@ -20,10 +20,47 @@ import { InputError, AuthRequired, Forbidden } from '@common/errors';
|
|
|
20
20
|
- TYPES
|
|
21
21
|
----------------------------------*/
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
declare global {
|
|
24
|
+
/**
|
|
25
|
+
* Optional app-level role registry.
|
|
26
|
+
*
|
|
27
|
+
* Apps can add their own roles (keys are role ids):
|
|
28
|
+
* `interface ProteumAuthRoleCatalog { GOD: true }`
|
|
29
|
+
*/
|
|
30
|
+
interface ProteumAuthRoleCatalog {}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* App-level feature catalog consumed by auth permission checks.
|
|
34
|
+
*
|
|
35
|
+
* Apps can augment this interface with their own feature map:
|
|
36
|
+
* `interface ProteumAuthFeatureCatalog extends MyFeatures {}`
|
|
37
|
+
*/
|
|
38
|
+
interface ProteumAuthFeatureCatalog {}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Canonical feature keys union used across app + framework.
|
|
42
|
+
*
|
|
43
|
+
* Notes:
|
|
44
|
+
* - If the app does not define a feature catalog, this defaults to `string`.
|
|
45
|
+
* - Otherwise it becomes the string keys of `ProteumAuthFeatureCatalog`.
|
|
46
|
+
*/
|
|
47
|
+
type FeatureKeys = (
|
|
48
|
+
keyof ProteumAuthFeatureCatalog extends never
|
|
49
|
+
? string
|
|
50
|
+
: Extract<keyof ProteumAuthFeatureCatalog, string>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type TUserRole = (
|
|
55
|
+
typeof UserRoles[number]
|
|
56
|
+
|
|
|
57
|
+
Extract<keyof ProteumAuthRoleCatalog, string>
|
|
58
|
+
);
|
|
24
59
|
|
|
25
60
|
export type THttpRequest = express.Request | http.IncomingMessage;
|
|
26
61
|
|
|
62
|
+
export type TFeatureKey = FeatureKeys;
|
|
63
|
+
|
|
27
64
|
/*----------------------------------
|
|
28
65
|
- CONFIG
|
|
29
66
|
----------------------------------*/
|
|
@@ -167,7 +204,7 @@ export default abstract class AuthService<
|
|
|
167
204
|
return session;
|
|
168
205
|
}
|
|
169
206
|
|
|
170
|
-
public createSession( session: TJwtSession,
|
|
207
|
+
public createSession( session: TJwtSession, request2: TRequest ): string {
|
|
171
208
|
|
|
172
209
|
this.config.debug && console.info(LogPrefix, `Creating new session:`, session);
|
|
173
210
|
|
|
@@ -175,7 +212,7 @@ export default abstract class AuthService<
|
|
|
175
212
|
|
|
176
213
|
this.config.debug && console.info(LogPrefix, `Generated JWT token for session:` + token);
|
|
177
214
|
|
|
178
|
-
|
|
215
|
+
request2.res.cookie('authorization', token, {
|
|
179
216
|
maxAge: this.config.jwt.expiration,
|
|
180
217
|
});
|
|
181
218
|
|
|
@@ -191,30 +228,28 @@ export default abstract class AuthService<
|
|
|
191
228
|
request.res.clearCookie('authorization');
|
|
192
229
|
}
|
|
193
230
|
|
|
194
|
-
public check(
|
|
195
|
-
request: TRequest,
|
|
196
|
-
role
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
motivation?: string,
|
|
212
|
-
dataForDebug?: { [key: string]: any }
|
|
231
|
+
public check(
|
|
232
|
+
request: TRequest,
|
|
233
|
+
role?: TUserRole | false,
|
|
234
|
+
): TUser | null;
|
|
235
|
+
|
|
236
|
+
public check(
|
|
237
|
+
request: TRequest,
|
|
238
|
+
role: TUserRole | false,
|
|
239
|
+
feature: FeatureKeys,
|
|
240
|
+
action?: string,
|
|
241
|
+
): TUser | null;
|
|
242
|
+
|
|
243
|
+
public check(
|
|
244
|
+
request: TRequest,
|
|
245
|
+
role: TUserRole | false = 'USER',
|
|
246
|
+
feature?: FeatureKeys,
|
|
247
|
+
action?: string,
|
|
213
248
|
): TUser | null {
|
|
214
249
|
|
|
215
250
|
const user = request.user;
|
|
216
251
|
|
|
217
|
-
this.config.debug && console.warn(LogPrefix, `Check auth, role = ${role}. Current user =`, user?.name,
|
|
252
|
+
this.config.debug && console.warn(LogPrefix, `Check auth, role = ${role}. Current user =`, user?.name, feature);
|
|
218
253
|
|
|
219
254
|
if (user === undefined) {
|
|
220
255
|
|
|
@@ -223,13 +258,13 @@ export default abstract class AuthService<
|
|
|
223
258
|
// Shoudln't be logged
|
|
224
259
|
} else if (role === false) {
|
|
225
260
|
|
|
226
|
-
return user;
|
|
261
|
+
return user as TUser;
|
|
227
262
|
|
|
228
263
|
// Not connected
|
|
229
264
|
} else if (user === null) {
|
|
230
265
|
|
|
231
266
|
console.warn(LogPrefix, "Refusé pour anonyme (" + request.ip + ")");
|
|
232
|
-
throw new AuthRequired('Please login to continue',
|
|
267
|
+
throw new AuthRequired('Please login to continue', feature as any, action as any);
|
|
233
268
|
|
|
234
269
|
// Insufficient permissions
|
|
235
270
|
} else if (!user.roles.includes(role)) {
|
|
@@ -244,7 +279,7 @@ export default abstract class AuthService<
|
|
|
244
279
|
|
|
245
280
|
}
|
|
246
281
|
|
|
247
|
-
return user;
|
|
282
|
+
return user as TUser;
|
|
248
283
|
}
|
|
249
284
|
|
|
250
|
-
}
|
|
285
|
+
}
|
|
@@ -8,7 +8,6 @@ import jwt from 'jsonwebtoken';
|
|
|
8
8
|
// Core
|
|
9
9
|
import type { default as Router, Request as ServerRequest, TAnyRouter } from '@server/services/router';
|
|
10
10
|
import RequestService from '@server/services/router/request/service';
|
|
11
|
-
import { InputError, AuthRequired, Forbidden } from '@common/errors';
|
|
12
11
|
|
|
13
12
|
// Specific
|
|
14
13
|
import type AuthenticationRouterService from '.';
|
|
@@ -45,10 +44,22 @@ export default class UsersRequestService<
|
|
|
45
44
|
return this.users.logout( this.request );
|
|
46
45
|
}
|
|
47
46
|
|
|
47
|
+
public check(): TUser;
|
|
48
|
+
|
|
48
49
|
// TODO: return user type according to entity
|
|
49
|
-
public check(role: TUserRole,
|
|
50
|
-
|
|
51
|
-
public check(role:
|
|
52
|
-
|
|
50
|
+
public check(role: TUserRole, feature: null): TUser;
|
|
51
|
+
|
|
52
|
+
public check(role: false): null;
|
|
53
|
+
|
|
54
|
+
public check(role: TUserRole, feature: FeatureKeys, action?: string): TUser;
|
|
55
|
+
|
|
56
|
+
public check(role: false, feature: FeatureKeys, action?: string): null;
|
|
57
|
+
|
|
58
|
+
public check(
|
|
59
|
+
role: TUserRole | false = 'USER',
|
|
60
|
+
feature?: FeatureKeys | null,
|
|
61
|
+
action?: string
|
|
62
|
+
) {
|
|
63
|
+
return this.users.check( this.request, role, feature, action ) as any;
|
|
53
64
|
}
|
|
54
|
-
}
|
|
65
|
+
}
|
|
@@ -115,7 +115,7 @@ export default class HttpServer {
|
|
|
115
115
|
routes.use('/public', cors());
|
|
116
116
|
routes.use(
|
|
117
117
|
'/public',
|
|
118
|
-
express.static( Container.path.root
|
|
118
|
+
express.static( path.join(Container.path.root, APP_OUTPUT_DIR, 'public'), {
|
|
119
119
|
dotfiles: 'deny',
|
|
120
120
|
setHeaders: function setCustomCacheControl(res, path) {
|
|
121
121
|
|
|
@@ -193,7 +193,7 @@ export default class HttpServer {
|
|
|
193
193
|
|
|
194
194
|
routes.use( csp.expressCspHeader({
|
|
195
195
|
directives: {
|
|
196
|
-
'script-src': [csp.INLINE, csp.SELF,
|
|
196
|
+
'script-src': [csp.INLINE, csp.SELF, csp.UNSAFE_EVAL,
|
|
197
197
|
...this.config.csp.scripts
|
|
198
198
|
]
|
|
199
199
|
}
|
|
@@ -214,4 +214,4 @@ export default class HttpServer {
|
|
|
214
214
|
public async cleanup() {
|
|
215
215
|
this.http.close();
|
|
216
216
|
}
|
|
217
|
-
}
|
|
217
|
+
}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
// Npm
|
|
6
6
|
import zod from 'zod';
|
|
7
|
-
import { SomeType } from 'zod/v4/core';
|
|
8
7
|
|
|
9
8
|
// Core
|
|
10
9
|
import {
|
|
@@ -24,6 +23,18 @@ export type TConfig = {
|
|
|
24
23
|
debug?: boolean
|
|
25
24
|
}
|
|
26
25
|
|
|
26
|
+
type TValidationSchema = zod.ZodTypeAny;
|
|
27
|
+
type TValidationShape = zod.ZodRawShape;
|
|
28
|
+
|
|
29
|
+
const isZodSchema = (fields: unknown): fields is TValidationSchema => {
|
|
30
|
+
return (
|
|
31
|
+
typeof fields === 'object'
|
|
32
|
+
&& fields !== null
|
|
33
|
+
&& 'safeParse' in fields
|
|
34
|
+
&& typeof (fields as TValidationSchema).safeParse === 'function'
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
/*----------------------------------
|
|
28
39
|
- SERVICE
|
|
29
40
|
----------------------------------*/
|
|
@@ -32,18 +43,25 @@ export default(
|
|
|
32
43
|
config: TConfig,
|
|
33
44
|
router = request.router,
|
|
34
45
|
app = router.app
|
|
35
|
-
) =>
|
|
36
|
-
|
|
37
|
-
...schema,
|
|
46
|
+
) => {
|
|
38
47
|
|
|
39
|
-
validate( fields:
|
|
48
|
+
function validate<TSchema extends TValidationSchema>( fields: TSchema ): zod.output<TSchema>;
|
|
49
|
+
function validate<TShape extends TValidationShape>( fields: TShape ): zod.output<zod.ZodObject<TShape>>;
|
|
50
|
+
function validate( fields: TValidationSchema | TValidationShape ) {
|
|
40
51
|
|
|
41
52
|
config.debug && console.log(LogPrefix, "Validate request data:", request.data);
|
|
42
53
|
|
|
43
|
-
const
|
|
54
|
+
const validationSchema = isZodSchema(fields) ? fields : zod.object(fields);
|
|
44
55
|
|
|
45
|
-
const preprocessedSchema = preprocessSchema(
|
|
56
|
+
//const preprocessedSchema = preprocessSchema(validationSchema);
|
|
46
57
|
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
|
|
58
|
+
return validationSchema.parse(request.data);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
|
|
63
|
+
...schema,
|
|
64
|
+
|
|
65
|
+
validate,
|
|
66
|
+
}
|
|
67
|
+
}
|
package/server/utils/slug.ts
CHANGED
|
@@ -7,9 +7,6 @@ import escapeStringRegexp from 'escape-regexp';
|
|
|
7
7
|
import slugify from 'slugify';
|
|
8
8
|
import { removeStopwords, eng } from 'stopword';
|
|
9
9
|
|
|
10
|
-
// Core
|
|
11
|
-
import type SQL from "@server/services/database";
|
|
12
|
-
|
|
13
10
|
/*----------------------------------
|
|
14
11
|
- TYPES
|
|
15
12
|
----------------------------------*/
|
package/tsconfig.common.json
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare const __DEV__: boolean;
|
|
2
|
+
declare const SERVER: boolean;
|
|
3
|
+
|
|
4
|
+
declare const CORE_VERSION: string;
|
|
5
|
+
declare const CORE_PATH: string;
|
|
6
|
+
|
|
7
|
+
declare const BUILD_DATE: string;
|
|
8
|
+
declare const BUILD_ID: number;
|
|
9
|
+
|
|
10
|
+
declare const APP_PATH: string;
|
|
11
|
+
declare const APP_NAME: string;
|
|
12
|
+
declare const APP_OUTPUT_DIR: string;
|
package/changelog.md
DELETED
|
@@ -1,298 +0,0 @@
|
|
|
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
|
-
}
|