5htp-core 0.2.2 → 0.2.4
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/package.json +1 -1
- package/src/client/app/component.tsx +1 -2
- package/src/client/app/index.ts +2 -6
- package/src/client/assets/css/components/button.less +4 -3
- package/src/client/assets/css/components/card.less +26 -11
- package/src/client/assets/css/components/lists.less +1 -1
- package/src/client/assets/css/components/other.less +0 -11
- package/src/client/assets/css/components.less +17 -3
- package/src/client/assets/css/core.less +55 -0
- package/src/client/assets/css/theme.less +0 -4
- package/src/client/assets/css/utils/layouts.less +1 -1
- package/src/client/assets/css/utils/medias.less +0 -47
- package/src/client/components/Dialog/Manager.tsx +1 -4
- package/src/client/components/Dialog/card.tsx +1 -1
- package/src/client/components/Dialog/index.less +2 -2
- package/src/client/components/Form.ts +154 -0
- package/src/client/components/{Form → Form_old}/index.tsx +0 -0
- package/src/client/components/{Form → Form_old}/index.tsx.old +0 -0
- package/src/client/components/Select/index.tsx +159 -24
- package/src/client/components/containers/Popover/index.tsx +38 -120
- package/src/client/components/data/progressbar/circular/index.tsx +1 -3
- package/src/client/components/dropdown/index.tsx +8 -13
- package/src/client/components/inputv3/base.less +0 -1
- package/src/client/components/inputv3/base.tsx +17 -6
- package/src/client/components/inputv3/string/index.tsx +23 -10
- package/src/client/services/router/components/Page.tsx +16 -18
- package/src/client/services/router/components/router.tsx +3 -3
- package/src/client/services/router/index.tsx +17 -14
- package/src/client/services/router/request/api.ts +6 -3
- package/src/client/services/router/response/index.tsx +4 -0
- package/src/client/services/router/response/page.ts +2 -1
- package/src/common/router/index.ts +1 -1
- package/src/common/router/layouts.ts +38 -6
- package/src/common/router/register.ts +3 -9
- package/src/common/router/request/api.ts +3 -1
- package/src/common/validation/index.ts +1 -1
- package/src/common/validation/schema.ts +8 -3
- package/src/common/validation/validators.ts +21 -10
- package/src/server/app/index.ts +2 -1
- package/src/server/services/console/bugReporter.ts +2 -19
- package/src/server/services/database/index.ts +27 -19
- package/src/server/services/router/index.ts +3 -3
- package/src/server/services/router/request/api.ts +3 -0
- package/src/server/services/router/response/index.ts +7 -4
- package/src/server/services/router/response/mask/Filter.ts +16 -72
- package/src/server/services/router/response/mask/index.ts +4 -7
- package/src/server/services/router/response/page/document.tsx +2 -2
- package/src/server/services/router/response/page/index.tsx +2 -2
- package/src/server/services/schema/request.ts +4 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
3
|
"description": "Convenient TypeScript framework designed for Performance and Productivity.",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -21,8 +21,7 @@ export default function App ({ context }: {
|
|
|
21
21
|
context: TClientOrServerContext,
|
|
22
22
|
}) {
|
|
23
23
|
|
|
24
|
-
const
|
|
25
|
-
const curLayout = route.options.layout;
|
|
24
|
+
const curLayout = context.page?.layout;
|
|
26
25
|
const [layout, setLayout] = React.useState<Layout | false | undefined>(curLayout);
|
|
27
26
|
|
|
28
27
|
// TODO: context.page is always provided in the context on the client side
|
package/src/client/app/index.ts
CHANGED
|
@@ -102,11 +102,7 @@ export default abstract class Application {
|
|
|
102
102
|
})
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
public handleError( error: CoreError | Error, httpCode?: number )
|
|
106
|
-
|
|
107
|
-
/*console.error(`[api] Network error:`, e);
|
|
108
|
-
context.toast.error("Please check your internet connection and try again.", undefined, null, { autohide: false });*/
|
|
109
|
-
}
|
|
105
|
+
public abstract handleError( error: CoreError | Error, httpCode?: number );
|
|
110
106
|
|
|
111
107
|
public reportBug = (infos: TBugReportInfos) => fetch('/help/bug/gui', {
|
|
112
108
|
method: 'POST',
|
|
@@ -116,7 +112,7 @@ export default abstract class Application {
|
|
|
116
112
|
},
|
|
117
113
|
body: JSON.stringify({
|
|
118
114
|
url: window.location.pathname,
|
|
119
|
-
|
|
115
|
+
context: JSON.stringify(window["ssr"]),
|
|
120
116
|
guiVersion: BUILD_DATE,
|
|
121
117
|
...infos
|
|
122
118
|
})
|
|
@@ -94,9 +94,6 @@
|
|
|
94
94
|
----------------------------------*/
|
|
95
95
|
|
|
96
96
|
&.bg {
|
|
97
|
-
|
|
98
|
-
background: var(--cBg);
|
|
99
|
-
color: var(--cTxtBase);
|
|
100
97
|
|
|
101
98
|
&:not(:disabled) {
|
|
102
99
|
&:hover,
|
|
@@ -316,6 +313,10 @@ ul.col {
|
|
|
316
313
|
}
|
|
317
314
|
}
|
|
318
315
|
|
|
316
|
+
&.menu > li > .btn {
|
|
317
|
+
width: 100%;
|
|
318
|
+
}
|
|
319
|
+
|
|
319
320
|
.btn.icon > .label,
|
|
320
321
|
> li > .btn + ul {
|
|
321
322
|
left: 100%;
|
|
@@ -20,23 +20,38 @@
|
|
|
20
20
|
|
|
21
21
|
&.row {
|
|
22
22
|
padding: @cardPadding @cardPaddingLong;
|
|
23
|
+
|
|
24
|
+
// TODO: rename & move elsewere
|
|
25
|
+
&.inside {
|
|
26
|
+
|
|
27
|
+
padding: 0;
|
|
28
|
+
gap: 0;
|
|
29
|
+
align-items: stretch;
|
|
30
|
+
|
|
31
|
+
> * {
|
|
32
|
+
padding: @cardPadding @cardPaddingLong;
|
|
33
|
+
&:first-child {
|
|
34
|
+
border-top-left-radius: inherit;
|
|
35
|
+
border-bottom-left-radius: inherit;
|
|
36
|
+
}
|
|
37
|
+
&:last-child {
|
|
38
|
+
border-top-right-radius: inherit;
|
|
39
|
+
border-bottom-right-radius: inherit;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&.sep {
|
|
45
|
+
> * + * {
|
|
46
|
+
border-left: solid 1px #eee;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
23
49
|
}
|
|
24
50
|
|
|
25
51
|
&.col {
|
|
26
52
|
padding: @cardPaddingLong @cardPadding;
|
|
27
53
|
}
|
|
28
54
|
|
|
29
|
-
&.bg {
|
|
30
|
-
--cBg: difference(@cBgPage, #0a0a0a);
|
|
31
|
-
--cBg2: difference(@cBgPage, #111);
|
|
32
|
-
background: var(--cBg);
|
|
33
|
-
|
|
34
|
-
&.img {
|
|
35
|
-
background-position: center;
|
|
36
|
-
background-size: cover;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
55
|
&.selected {
|
|
41
56
|
|
|
42
57
|
box-shadow: 0 0 0 3px @c1;
|
|
@@ -27,17 +27,6 @@
|
|
|
27
27
|
background: var(--cBgAccent);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
.bg-cover {
|
|
31
|
-
background-color: #000;
|
|
32
|
-
background-repeat: no-repeat;
|
|
33
|
-
background-position: center;
|
|
34
|
-
background-size: cover;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
img.bg-cover {
|
|
38
|
-
object-fit: cover;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
30
|
.clickable {
|
|
42
31
|
cursor: pointer;
|
|
43
32
|
}
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
.card,
|
|
34
|
-
.input.text,
|
|
35
34
|
.btn,
|
|
36
35
|
.table,
|
|
37
36
|
i.solid {
|
|
@@ -48,9 +47,24 @@ i.solid {
|
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
49
|
|
|
50
|
+
.input.text {
|
|
51
|
+
border: solid 0.25em #eee;
|
|
52
|
+
|
|
53
|
+
&:hover {
|
|
54
|
+
border-color: #ddd;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&.focus {
|
|
58
|
+
border-color: @c1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&.error {
|
|
62
|
+
border-color: @cError;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
51
66
|
//.card.clickable:hover,
|
|
52
67
|
.btn:hover,
|
|
53
68
|
.input.text.focus {
|
|
54
|
-
|
|
55
|
-
box-shadow: 0 10px 50px fade(#000, 15%);
|
|
69
|
+
|
|
56
70
|
}
|
|
@@ -12,6 +12,61 @@
|
|
|
12
12
|
.bg {
|
|
13
13
|
background: var(--cBg);
|
|
14
14
|
color: var(--cTxtBase);
|
|
15
|
+
|
|
16
|
+
&.img {
|
|
17
|
+
background-size: cover;
|
|
18
|
+
background-repeat: no-repeat;
|
|
19
|
+
background-position: center;
|
|
20
|
+
|
|
21
|
+
.build-theme-bg(#000, #fff);
|
|
22
|
+
text-shadow: 0 0 10px fade(#000, 20%);
|
|
23
|
+
|
|
24
|
+
--cTxtDiscret: fade(#fff, 40%)
|
|
25
|
+
--cTxtDesc: fade(#fff, 60%);
|
|
26
|
+
--cTxtBase: fade(#fff, 80%);
|
|
27
|
+
--cTxtImportant: fade(#fff, 100%);
|
|
28
|
+
--cTxtAccent: fade(#fff, 20%);
|
|
29
|
+
|
|
30
|
+
&.light {
|
|
31
|
+
--cTxtDiscret: fade(#000, 40%)
|
|
32
|
+
--cTxtDesc: fade(#000, 60%);
|
|
33
|
+
--cTxtBase: fade(#000, 80%);
|
|
34
|
+
--cTxtImportant: fade(#000, 100%);
|
|
35
|
+
--cTxtAccent: fade(#000, 20%);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&.colorize {
|
|
39
|
+
position: relative;
|
|
40
|
+
|
|
41
|
+
&::after {
|
|
42
|
+
content: ' ';
|
|
43
|
+
display: block;
|
|
44
|
+
background-color: inherit;
|
|
45
|
+
opacity: 0.5;
|
|
46
|
+
border-radius: inherit;
|
|
47
|
+
|
|
48
|
+
position: absolute;
|
|
49
|
+
top: 0; right: 0; bottom: 0; left: 0;
|
|
50
|
+
z-index: 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
> * {
|
|
54
|
+
z-index: 2;
|
|
55
|
+
position: relative;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&.bg-cover {
|
|
61
|
+
background-color: #000;
|
|
62
|
+
background-repeat: no-repeat;
|
|
63
|
+
background-position: center;
|
|
64
|
+
background-size: cover;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&img.bg-cover {
|
|
68
|
+
object-fit: cover;
|
|
69
|
+
}
|
|
15
70
|
}
|
|
16
71
|
|
|
17
72
|
html {
|
|
@@ -4,25 +4,6 @@ img.img,
|
|
|
4
4
|
background: @cBgPage - #101010;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
.bg.img {
|
|
8
|
-
.build-theme-bg(#000, #fff);
|
|
9
|
-
text-shadow: 0 0 10px fade(#000, 20%);
|
|
10
|
-
|
|
11
|
-
--cTxtDiscret: fade(#fff, 40%)
|
|
12
|
-
--cTxtDesc: fade(#fff, 60%);
|
|
13
|
-
--cTxtBase: fade(#fff, 80%);
|
|
14
|
-
--cTxtImportant: fade(#fff, 100%);
|
|
15
|
-
--cTxtAccent: fade(#fff, 20%);
|
|
16
|
-
|
|
17
|
-
&.light {
|
|
18
|
-
--cTxtDiscret: fade(#000, 40%)
|
|
19
|
-
--cTxtDesc: fade(#000, 60%);
|
|
20
|
-
--cTxtBase: fade(#000, 80%);
|
|
21
|
-
--cTxtImportant: fade(#000, 100%);
|
|
22
|
-
--cTxtAccent: fade(#000, 20%);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
7
|
img {
|
|
27
8
|
max-width: 100%;
|
|
28
9
|
display: block;
|
|
@@ -36,34 +17,6 @@ img {
|
|
|
36
17
|
- IMAGE CONFIG
|
|
37
18
|
----------------------------------*/
|
|
38
19
|
|
|
39
|
-
// Default: cover
|
|
40
|
-
.bg.img {
|
|
41
|
-
background-size: cover;
|
|
42
|
-
background-repeat: no-repeat;
|
|
43
|
-
background-position: center;
|
|
44
|
-
|
|
45
|
-
&.colorize {
|
|
46
|
-
position: relative;
|
|
47
|
-
|
|
48
|
-
&::after {
|
|
49
|
-
content: ' ';
|
|
50
|
-
display: block;
|
|
51
|
-
background-color: inherit;
|
|
52
|
-
opacity: 0.5;
|
|
53
|
-
border-radius: inherit;
|
|
54
|
-
|
|
55
|
-
position: absolute;
|
|
56
|
-
top: 0; right: 0; bottom: 0; left: 0;
|
|
57
|
-
z-index: 1;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
> * {
|
|
61
|
-
z-index: 2;
|
|
62
|
-
position: relative;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
20
|
img.img {
|
|
68
21
|
object-fit: cover;
|
|
69
22
|
object-position: center;
|
|
@@ -137,10 +137,7 @@ export const createDialog = (app: Application, isToast: boolean): DialogActions
|
|
|
137
137
|
|
|
138
138
|
if (!isToast)
|
|
139
139
|
render = (
|
|
140
|
-
<div class="modal"
|
|
141
|
-
if (e.target === e.currentTarget && !paramsInit?.prison)
|
|
142
|
-
close(false);
|
|
143
|
-
}}>
|
|
140
|
+
<div class="modal">
|
|
144
141
|
{render}
|
|
145
142
|
</div>
|
|
146
143
|
)
|
|
@@ -82,8 +82,7 @@
|
|
|
82
82
|
gap: @spacing;
|
|
83
83
|
z-index: @modal-zindex;
|
|
84
84
|
|
|
85
|
-
background: fade(#
|
|
86
|
-
backdrop-filter: blur(30px) saturate(2);
|
|
85
|
+
background: fade(#000, 20%);
|
|
87
86
|
border-radius: @radius;
|
|
88
87
|
|
|
89
88
|
// Desktop = vertically center the modal
|
|
@@ -109,6 +108,7 @@
|
|
|
109
108
|
max-height: 100vh;
|
|
110
109
|
box-shadow: none;
|
|
111
110
|
padding: @spacing * 2;
|
|
111
|
+
overflow-y: auto;
|
|
112
112
|
|
|
113
113
|
// Pas d'anim quand pas card,
|
|
114
114
|
// Car peut contenir bcp d'elemnts => performance
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// npm
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
// Core
|
|
9
|
+
import type { Schema } from '@common/validation';
|
|
10
|
+
|
|
11
|
+
/*----------------------------------
|
|
12
|
+
- TYPES
|
|
13
|
+
----------------------------------*/
|
|
14
|
+
type TFormOptions<TFormData extends {}> = {
|
|
15
|
+
data?: Partial<TFormData>,
|
|
16
|
+
submit?: (data: TFormData) => Promise<void>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type FieldsAttrs<TFormData extends {}> = {
|
|
20
|
+
[fieldName in keyof TFormData]: {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type Form<TFormData extends {} = {}> = {
|
|
24
|
+
data: TFormData,
|
|
25
|
+
set: (data: Partial<TFormData>) => void,
|
|
26
|
+
submit: (additionnalData?: Partial<TFormData>) => Promise<any>,
|
|
27
|
+
fields: FieldsAttrs<TFormData>,
|
|
28
|
+
} & FormState
|
|
29
|
+
|
|
30
|
+
type FormState = {
|
|
31
|
+
isLoading: boolean,
|
|
32
|
+
errorsCount: number,
|
|
33
|
+
errors: {[fieldName: string]: string[]},
|
|
34
|
+
changed: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/*----------------------------------
|
|
38
|
+
- HOOK
|
|
39
|
+
----------------------------------*/
|
|
40
|
+
export default function useForm<TFormData extends {}>( schema: Schema<TFormData>, options: TFormOptions<TFormData> ) {
|
|
41
|
+
|
|
42
|
+
/*----------------------------------
|
|
43
|
+
- INIT
|
|
44
|
+
----------------------------------*/
|
|
45
|
+
const fields = React.useRef<FieldsAttrs<TFormData>>(null);
|
|
46
|
+
|
|
47
|
+
const [data, setData] = React.useState<TFormData>( options.data || {} );
|
|
48
|
+
const [state, setState] = React.useState<FormState>({
|
|
49
|
+
isLoading: false,
|
|
50
|
+
errorsCount: 0,
|
|
51
|
+
errors: {},
|
|
52
|
+
changed: false
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Validate data when it changes
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
state.changed && validate(data);
|
|
58
|
+
}, [data]);
|
|
59
|
+
|
|
60
|
+
/*----------------------------------
|
|
61
|
+
- ACTIONS
|
|
62
|
+
----------------------------------*/
|
|
63
|
+
const validate = (allData: TFormData) => {
|
|
64
|
+
|
|
65
|
+
const validated = schema.validate(allData, allData);
|
|
66
|
+
|
|
67
|
+
// Update errors
|
|
68
|
+
if (validated.nbErreurs !== state.errorsCount) {
|
|
69
|
+
rebuildFieldsAttrs({
|
|
70
|
+
errorsCount: validated.nbErreurs,
|
|
71
|
+
errors: validated.erreurs,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return validated;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const submit = (additionnalData: Partial<TFormData> = {}) => {
|
|
79
|
+
|
|
80
|
+
const allData = { ...data, ...additionnalData }
|
|
81
|
+
|
|
82
|
+
// Validation
|
|
83
|
+
const validated = validate(allData);
|
|
84
|
+
if (validated.nbErreurs !== 0)
|
|
85
|
+
return;
|
|
86
|
+
|
|
87
|
+
// Callback
|
|
88
|
+
if (options.submit)
|
|
89
|
+
return options.submit(validated.values);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const rebuildFieldsAttrs = (newState: Partial<FormState> = {}) => {
|
|
93
|
+
// Force rebuilding the fields definition on the next state change
|
|
94
|
+
fields.current = null;
|
|
95
|
+
// Force state change
|
|
96
|
+
setState( old => ({
|
|
97
|
+
...old,
|
|
98
|
+
...newState,
|
|
99
|
+
changed: true
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Rebuild the fields attrs when the schema changes
|
|
104
|
+
if (fields.current === null || Object.keys(schema).join(',') !== Object.keys(fields.current).join(',')){
|
|
105
|
+
fields.current = {}
|
|
106
|
+
for (const fieldName in schema.fields) {
|
|
107
|
+
fields.current[fieldName] = {
|
|
108
|
+
|
|
109
|
+
// Value control
|
|
110
|
+
value: data[fieldName],
|
|
111
|
+
onChange: (val) => {
|
|
112
|
+
setState( old => ({
|
|
113
|
+
...old,
|
|
114
|
+
changed: true
|
|
115
|
+
}));
|
|
116
|
+
setData( old => {
|
|
117
|
+
return {
|
|
118
|
+
...old,
|
|
119
|
+
[fieldName]: typeof val === 'function'
|
|
120
|
+
? val(old[fieldName])
|
|
121
|
+
: val
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// Submit on press enter
|
|
127
|
+
onKeyDown: e => {
|
|
128
|
+
if (e.key === 'Enter') {
|
|
129
|
+
submit({ [fieldName]: e.target.value } as Partial<TFormData>);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// Error
|
|
134
|
+
errors: state.errors[ fieldName ],
|
|
135
|
+
required: schema.fields[ fieldName ].options?.opt !== true,
|
|
136
|
+
validator: schema.fields[ fieldName ]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/*----------------------------------
|
|
142
|
+
- EXPOSE
|
|
143
|
+
----------------------------------*/
|
|
144
|
+
|
|
145
|
+
const form = {
|
|
146
|
+
data,
|
|
147
|
+
set: setData,
|
|
148
|
+
submit,
|
|
149
|
+
fields: fields.current,
|
|
150
|
+
...state
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return [form, fields.current]
|
|
154
|
+
}
|
|
File without changes
|
|
File without changes
|