5htp-core 0.2.8 → 0.2.9
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/assets/css/components/card.less +26 -1
- package/src/client/assets/css/components.less +2 -11
- package/src/client/assets/css/text/text.less +7 -4
- package/src/client/components/Form.ts +16 -12
- package/src/client/components/Select/ChoiceSelector.tsx +1 -30
- package/src/client/components/Select/index.tsx +146 -27
- package/src/client/components/containers/Popover/index.tsx +1 -1
- package/src/client/components/input/Checkbox/index.tsx +1 -0
- package/src/client/components/input/Slider/index.less +1 -1
- package/src/client/components/input/Slider/index.tsx +73 -32
- package/src/client/components/inputv3/base.less +69 -22
- package/src/client/components/inputv3/base.tsx +4 -4
- package/src/client/components/inputv3/index.tsx +42 -37
- package/src/client/services/router/index.tsx +1 -0
- package/src/common/router/response/page.ts +3 -3
- package/src/common/validation/validator.ts +0 -3
- package/src/common/validation/validators.ts +43 -17
- package/src/server/app/index.ts +1 -2
- package/src/server/app/service.ts +7 -3
- package/src/server/services/database/connection.ts +2 -1
- package/src/server/services/database/index.ts +3 -3
- package/src/server/services/database/metas.ts +3 -3
- package/src/server/services/router/index.ts +1 -1
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.9",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -49,7 +49,32 @@
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
&.col {
|
|
52
|
-
padding: @
|
|
52
|
+
padding: @cardPadding @cardPaddingLong;
|
|
53
|
+
|
|
54
|
+
&.inside {
|
|
55
|
+
|
|
56
|
+
padding: 0;
|
|
57
|
+
gap: 0;
|
|
58
|
+
align-items: stretch;
|
|
59
|
+
|
|
60
|
+
> * {
|
|
61
|
+
padding: @cardPaddingLong @cardPadding;
|
|
62
|
+
&:first-child {
|
|
63
|
+
border-top-left-radius: inherit;
|
|
64
|
+
border-top-right-radius: inherit;
|
|
65
|
+
}
|
|
66
|
+
&:last-child {
|
|
67
|
+
border-bottom-left-radius: inherit;
|
|
68
|
+
border-bottom-right-radius: inherit;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
&.sep {
|
|
74
|
+
> * + * {
|
|
75
|
+
border-top: solid 1px #eee;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
53
78
|
}
|
|
54
79
|
|
|
55
80
|
&.selected {
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
|
|
33
33
|
.card,
|
|
34
34
|
.btn,
|
|
35
|
-
.
|
|
35
|
+
.input.text,
|
|
36
|
+
.input.select,
|
|
36
37
|
i.solid {
|
|
37
38
|
|
|
38
39
|
.build-theme-bg( #fff, #8E8E8E);
|
|
@@ -48,16 +49,6 @@ i.solid {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
.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
52
|
&.error {
|
|
62
53
|
border-color: @cError;
|
|
63
54
|
}
|
|
@@ -111,7 +111,7 @@ header > strong {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
em {
|
|
114
|
-
font-style: normal;
|
|
114
|
+
/*font-style: normal;
|
|
115
115
|
position: relative;
|
|
116
116
|
display: inline-block;
|
|
117
117
|
color: inherit;
|
|
@@ -129,7 +129,7 @@ em {
|
|
|
129
129
|
bottom: 0.05em;
|
|
130
130
|
left: -0.1em;
|
|
131
131
|
right: -0.1em;
|
|
132
|
-
}
|
|
132
|
+
}*/
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
pre {
|
|
@@ -148,6 +148,8 @@ pre {
|
|
|
148
148
|
@readingMargin: 2.4rem;
|
|
149
149
|
.reading {
|
|
150
150
|
|
|
151
|
+
--cTxtBase: #555;
|
|
152
|
+
|
|
151
153
|
.card > & {
|
|
152
154
|
|
|
153
155
|
padding: 3vh 0 !important;
|
|
@@ -165,20 +167,21 @@ pre {
|
|
|
165
167
|
max-width: var(--focusWidth);
|
|
166
168
|
margin: 0 auto;
|
|
167
169
|
width: 100%;
|
|
168
|
-
padding: 0 @readingMargin;
|
|
170
|
+
//padding: 0 @readingMargin;
|
|
169
171
|
}
|
|
170
172
|
|
|
171
173
|
&,
|
|
172
174
|
p {
|
|
173
175
|
font-family: 'Lato', sans-serif;
|
|
174
176
|
font-size: 1.1rem;
|
|
175
|
-
line-height:
|
|
177
|
+
line-height: 2em;
|
|
176
178
|
color: var(--cTxtBase);
|
|
177
179
|
text-align: justify;
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
h2, h3, h4 {
|
|
181
183
|
text-align: left;
|
|
184
|
+
margin: @spacing * 2 0;
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
h2 {
|
|
@@ -28,10 +28,13 @@ type FieldsAttrs<TFormData extends {}> = {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export type Form<TFormData extends {} = {}> = {
|
|
31
|
+
|
|
31
32
|
fields: FieldsAttrs<TFormData>,
|
|
32
33
|
data: TFormData,
|
|
33
34
|
options: TFormOptions<TFormData>,
|
|
34
|
-
|
|
35
|
+
autosavedData?: Partial<TFormData>,
|
|
36
|
+
|
|
37
|
+
validate: (data: Partial<TFormData>) => TValidationResult<{}>,
|
|
35
38
|
set: (data: Partial<TFormData>) => void,
|
|
36
39
|
submit: (additionnalData?: Partial<TFormData>) => Promise<any>,
|
|
37
40
|
} & FormState
|
|
@@ -48,30 +51,30 @@ type FormState = {
|
|
|
48
51
|
export default function useForm<TFormData extends {}>(
|
|
49
52
|
schema: Schema<TFormData>,
|
|
50
53
|
options: TFormOptions<TFormData>
|
|
51
|
-
) {
|
|
54
|
+
): [ Form, FieldsAttrs<TFormData> ] {
|
|
52
55
|
|
|
53
56
|
const context = useContext();
|
|
54
57
|
|
|
55
58
|
/*----------------------------------
|
|
56
59
|
- INIT
|
|
57
60
|
----------------------------------*/
|
|
58
|
-
let
|
|
61
|
+
let autosavedData: TFormData | undefined;
|
|
59
62
|
if (options.autoSave && typeof window !== 'undefined') {
|
|
60
63
|
const autosaved = localStorage.getItem('form.' + options.autoSave.id);
|
|
61
64
|
if (autosaved !== null) {
|
|
62
65
|
try {
|
|
63
66
|
console.log('[form] Parse autosaved from json:', autosaved);
|
|
64
|
-
|
|
67
|
+
autosavedData = JSON.parse(autosaved);
|
|
65
68
|
} catch (error) {
|
|
66
69
|
console.error('[form] Failed to decode autosaved data from json:', autosaved);
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
|
-
if (initialData === undefined)
|
|
71
|
-
initialData = options.data || {};
|
|
72
73
|
|
|
73
|
-
const
|
|
74
|
-
|
|
74
|
+
const initialData: Partial<TFormData> = options.data || {};
|
|
75
|
+
|
|
76
|
+
const fields = React.useRef<FieldsAttrs<TFormData> | null>(null);
|
|
77
|
+
const [data, setData] = React.useState< Partial<TFormData> >(initialData);
|
|
75
78
|
const [state, setState] = React.useState<FormState>({
|
|
76
79
|
isLoading: false,
|
|
77
80
|
errorsCount: 0,
|
|
@@ -93,7 +96,7 @@ export default function useForm<TFormData extends {}>(
|
|
|
93
96
|
/*----------------------------------
|
|
94
97
|
- ACTIONS
|
|
95
98
|
----------------------------------*/
|
|
96
|
-
const validate = (allData: TFormData = data, validateAll: boolean = true) => {
|
|
99
|
+
const validate = (allData: Partial<TFormData> = data, validateAll: boolean = true) => {
|
|
97
100
|
|
|
98
101
|
const validated = schema.validate(allData, allData, {}, {
|
|
99
102
|
// Ignore the fields where the vlaue has not been changed
|
|
@@ -130,7 +133,7 @@ export default function useForm<TFormData extends {}>(
|
|
|
130
133
|
// Callback
|
|
131
134
|
let submitResult: any;
|
|
132
135
|
if (options.submit)
|
|
133
|
-
submitResult = await options.submit(allData);
|
|
136
|
+
submitResult = await options.submit(allData as TFormData);
|
|
134
137
|
|
|
135
138
|
// Reset autosaved data
|
|
136
139
|
if (options.autoSave)
|
|
@@ -149,14 +152,14 @@ export default function useForm<TFormData extends {}>(
|
|
|
149
152
|
}));
|
|
150
153
|
}
|
|
151
154
|
|
|
152
|
-
const saveLocally = (data: TFormData
|
|
155
|
+
const saveLocally = (data: Partial<TFormData>, id: string) => {
|
|
153
156
|
console.log('[form] Autosave data for form:', id, ':', data);
|
|
154
157
|
localStorage.setItem('form.' + id, JSON.stringify(data));
|
|
155
158
|
}
|
|
156
159
|
|
|
157
160
|
// Rebuild the fields attrs when the schema changes
|
|
158
161
|
if (fields.current === null || Object.keys(schema).join(',') !== Object.keys(fields.current).join(',')) {
|
|
159
|
-
fields.current = {}
|
|
162
|
+
fields.current = {} as FieldsAttrs<TFormData>
|
|
160
163
|
for (const fieldName in schema.fields) {
|
|
161
164
|
|
|
162
165
|
const validator = schema.fields[fieldName];
|
|
@@ -202,6 +205,7 @@ export default function useForm<TFormData extends {}>(
|
|
|
202
205
|
validate,
|
|
203
206
|
submit,
|
|
204
207
|
options,
|
|
208
|
+
autosavedData,
|
|
205
209
|
...state
|
|
206
210
|
}
|
|
207
211
|
|
|
@@ -69,36 +69,7 @@ export default React.forwardRef<HTMLDivElement, Props>(({
|
|
|
69
69
|
...otherProps
|
|
70
70
|
}: Props, ref) => {
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
- INIT
|
|
74
|
-
----------------------------------*/
|
|
75
|
-
|
|
76
|
-
const choicesViaFunc = typeof initChoices === 'function';
|
|
77
|
-
if (choicesViaFunc && enableSearch === undefined)
|
|
78
|
-
enableSearch = true;
|
|
79
|
-
|
|
80
|
-
const [search, setSearch] = React.useState<{
|
|
81
|
-
keywords: string,
|
|
82
|
-
loading: boolean
|
|
83
|
-
}>({
|
|
84
|
-
keywords: '',
|
|
85
|
-
loading: choicesViaFunc
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const [choices, setChoices] = React.useState<Choices>( choicesViaFunc ? [] : initChoices );
|
|
89
|
-
|
|
90
|
-
/*----------------------------------
|
|
91
|
-
- ACTIONS
|
|
92
|
-
----------------------------------*/
|
|
93
|
-
|
|
94
|
-
React.useEffect(() => {
|
|
95
|
-
if (choicesViaFunc) {
|
|
96
|
-
initChoices(search.keywords).then((searchResults) => {
|
|
97
|
-
setSearch(s => ({ ...s, loading: false }))
|
|
98
|
-
setChoices(searchResults);
|
|
99
|
-
})
|
|
100
|
-
}
|
|
101
|
-
}, [initChoices, search.keywords]);
|
|
72
|
+
|
|
102
73
|
|
|
103
74
|
/*----------------------------------
|
|
104
75
|
- RENDER
|
|
@@ -7,6 +7,7 @@ import React from 'react';
|
|
|
7
7
|
|
|
8
8
|
// Core
|
|
9
9
|
import Dropdown, { TDropdownControl, Props as DropdownProps } from '@client/components/dropdown';
|
|
10
|
+
import Input from '@client/components/inputv3';
|
|
10
11
|
|
|
11
12
|
// Specific
|
|
12
13
|
import ChoiceSelector, {
|
|
@@ -23,60 +24,178 @@ export type Props = DropdownProps & SelectorProps & {
|
|
|
23
24
|
errors?: string[],
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
const ChoiceElement = ({ choice, currentList, onChange, multiple, includeCurrent }: {
|
|
28
|
+
choice: Choice,
|
|
29
|
+
currentList: Choice[],
|
|
30
|
+
includeCurrent: boolean
|
|
31
|
+
} & Pick<Props, 'onChange'|'multiple'>) => {
|
|
32
|
+
|
|
33
|
+
const isCurrent = currentList.some(c => c.value === choice.value);
|
|
34
|
+
if (isCurrent && !includeCurrent) return null;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<li class={"badge clickable " + (isCurrent ? 'bg primary' : '')} onClick={() => {
|
|
38
|
+
onChange( current => {
|
|
39
|
+
|
|
40
|
+
return multiple
|
|
41
|
+
? (isCurrent
|
|
42
|
+
? current.filter(c => c.value !== choice.value)
|
|
43
|
+
: [...(current || []), choice]
|
|
44
|
+
)
|
|
45
|
+
: (isCurrent
|
|
46
|
+
? undefined
|
|
47
|
+
: choice
|
|
48
|
+
)
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
}}>
|
|
52
|
+
{/*search.keywords ? (
|
|
53
|
+
<span>
|
|
54
|
+
|
|
55
|
+
<strong>{search.keywords}</strong>{choice.label.slice( search.keywords.length )}
|
|
56
|
+
|
|
57
|
+
</span>
|
|
58
|
+
) : */choice.label}
|
|
59
|
+
</li>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
26
63
|
/*----------------------------------
|
|
27
64
|
- COMONENT
|
|
28
65
|
----------------------------------*/
|
|
29
66
|
export default ({
|
|
67
|
+
// Input basics
|
|
30
68
|
title,
|
|
31
69
|
errors,
|
|
32
|
-
|
|
70
|
+
icon,
|
|
71
|
+
required,
|
|
72
|
+
validator,
|
|
73
|
+
|
|
74
|
+
// Choice selection
|
|
75
|
+
choices: initChoices,
|
|
76
|
+
noneSelection,
|
|
77
|
+
enableSearch,
|
|
78
|
+
value: current,
|
|
79
|
+
onChange,
|
|
80
|
+
inline,
|
|
81
|
+
multiple,
|
|
82
|
+
...otherProps
|
|
33
83
|
}: Props) => {
|
|
34
84
|
|
|
35
85
|
/*----------------------------------
|
|
36
86
|
- INIT
|
|
37
87
|
----------------------------------*/
|
|
38
88
|
|
|
39
|
-
const
|
|
89
|
+
const choicesViaFunc = typeof initChoices === 'function';
|
|
90
|
+
if (choicesViaFunc && enableSearch === undefined)
|
|
91
|
+
enableSearch = true;
|
|
92
|
+
|
|
93
|
+
const refInputSearch = React.useRef<HTMLInputElement | null>(null);
|
|
94
|
+
|
|
95
|
+
let className: string = 'input select txt-left';
|
|
96
|
+
|
|
97
|
+
const isRequired = required || validator?.options.min;
|
|
98
|
+
|
|
99
|
+
const [search, setSearch] = React.useState<{
|
|
100
|
+
keywords: string,
|
|
101
|
+
loading: boolean
|
|
102
|
+
}>({
|
|
103
|
+
keywords: '',
|
|
104
|
+
loading: choicesViaFunc
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const [choices, setChoices] = React.useState<Choice[]>( choicesViaFunc ? [] : initChoices );
|
|
40
108
|
|
|
41
109
|
/*----------------------------------
|
|
42
110
|
- ACTIONS
|
|
43
111
|
----------------------------------*/
|
|
44
112
|
|
|
45
|
-
|
|
113
|
+
React.useEffect(() => {
|
|
114
|
+
if (choicesViaFunc && (search.keywords || !enableSearch)) {
|
|
115
|
+
initChoices(search.keywords).then((searchResults) => {
|
|
116
|
+
setSearch(s => ({ ...s, loading: false }))
|
|
117
|
+
setChoices(searchResults);
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}, [initChoices, search.keywords]);
|
|
121
|
+
|
|
122
|
+
const currentList: Choice[] = current === undefined
|
|
46
123
|
? []
|
|
47
|
-
: (Array.isArray(
|
|
48
|
-
?
|
|
49
|
-
: [
|
|
124
|
+
: (Array.isArray(current)
|
|
125
|
+
? current
|
|
126
|
+
: [current]
|
|
50
127
|
);
|
|
51
128
|
|
|
52
129
|
/*----------------------------------
|
|
53
130
|
- RENDER
|
|
54
131
|
----------------------------------*/
|
|
55
132
|
return <>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
? title
|
|
65
|
-
: currentList.map(
|
|
66
|
-
choice => (
|
|
67
|
-
<span class="badge">
|
|
68
|
-
{choice.label}
|
|
69
|
-
</span>
|
|
70
|
-
)
|
|
133
|
+
|
|
134
|
+
<div class="col sp-05">
|
|
135
|
+
<div class={className} onClick={() => refInputSearch.current?.focus()}>
|
|
136
|
+
|
|
137
|
+
<div class="row al-left wrap pd-1">
|
|
138
|
+
|
|
139
|
+
{icon !== undefined && (
|
|
140
|
+
<i src={icon} />
|
|
71
141
|
)}
|
|
72
142
|
|
|
73
|
-
|
|
74
|
-
|
|
143
|
+
<div class="col al-left sp-05">
|
|
144
|
+
|
|
145
|
+
<label>{title}{required && (
|
|
146
|
+
<span class="fg error"> *</span>
|
|
147
|
+
)}</label>
|
|
148
|
+
|
|
149
|
+
<div class="row al-left wrap sp-05">
|
|
75
150
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
151
|
+
{/*!isRequired && (
|
|
152
|
+
<span class={"badge clickable " + (currentList.length === 0 ? 'bg primary' : '')}
|
|
153
|
+
onClick={() => onChange(multiple ? [] : undefined)}>
|
|
154
|
+
{noneSelection || 'None'}
|
|
155
|
+
</span>
|
|
156
|
+
)*/}
|
|
157
|
+
|
|
158
|
+
{( enableSearch ? currentList : choices ).map( choice => (
|
|
159
|
+
<ChoiceElement choice={choice}
|
|
160
|
+
currentList={currentList}
|
|
161
|
+
onChange={onChange}
|
|
162
|
+
multiple={multiple}
|
|
163
|
+
includeCurrent
|
|
164
|
+
/>
|
|
165
|
+
))}
|
|
166
|
+
|
|
167
|
+
{enableSearch && (
|
|
168
|
+
<Input
|
|
169
|
+
placeholder="Type your search here"
|
|
170
|
+
value={search.keywords}
|
|
171
|
+
onChange={keywords => setSearch(s => ({ ...s, loading: true, keywords }))}
|
|
172
|
+
inputRef={refInputSearch}
|
|
173
|
+
/>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{(enableSearch && choices.length !== 0 && search.keywords.length !== 0) && (
|
|
181
|
+
<ul class="row al-left wrap sp-05 pd-1">
|
|
182
|
+
{choices.map( choice => (
|
|
183
|
+
<ChoiceElement choice={choice}
|
|
184
|
+
currentList={currentList}
|
|
185
|
+
onChange={onChange}
|
|
186
|
+
multiple={multiple}
|
|
187
|
+
/>
|
|
188
|
+
))}
|
|
189
|
+
</ul>
|
|
190
|
+
)}
|
|
191
|
+
|
|
79
192
|
</div>
|
|
80
|
-
|
|
193
|
+
{errors?.length && (
|
|
194
|
+
<div class="fg error txt-left">
|
|
195
|
+
{errors.join('. ')}
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
|
|
81
200
|
</>
|
|
82
201
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
/*----------------------------------
|
|
2
2
|
- DEPENDANCES
|
|
3
3
|
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
4
6
|
import React from 'react';
|
|
7
|
+
import { VNode, JSX } from 'preact';
|
|
5
8
|
import Slider from 'react-slider';
|
|
6
|
-
|
|
9
|
+
|
|
10
|
+
// Core libs
|
|
11
|
+
import { useInput, InputBaseProps } from '../../inputv3/base';
|
|
12
|
+
import { default as Validator } from '@common/validation/validator';
|
|
13
|
+
import type SchemaValidators from '@common/validation/validators';
|
|
7
14
|
|
|
8
15
|
/*----------------------------------
|
|
9
16
|
- TYPES
|
|
@@ -14,44 +21,78 @@ type TValeurDefaut = typeof valeurDefaut;
|
|
|
14
21
|
type TValeurOut = string;
|
|
15
22
|
|
|
16
23
|
export type Props = {
|
|
17
|
-
valeur: TValeur,
|
|
18
|
-
step?: number,
|
|
19
24
|
|
|
25
|
+
value: TValeur,
|
|
26
|
+
|
|
27
|
+
step?: number,
|
|
20
28
|
min?: number,
|
|
21
29
|
max?: number,
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
format?: (value: number) => string
|
|
24
32
|
}
|
|
25
33
|
|
|
34
|
+
|
|
26
35
|
/*----------------------------------
|
|
27
36
|
- COMPOSANT
|
|
28
37
|
----------------------------------*/
|
|
29
38
|
import './index.less';
|
|
30
|
-
export default
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
39
|
+
export default ({
|
|
40
|
+
// Decoration
|
|
41
|
+
icon, prefix, required,
|
|
42
|
+
step, min, max,
|
|
43
|
+
// State
|
|
44
|
+
errors,
|
|
45
|
+
// Behavior
|
|
46
|
+
type,
|
|
47
|
+
...props
|
|
48
|
+
}: Props & InputBaseProps<number> & Omit<JSX.HTMLAttributes<HTMLInputElement>, 'onChange'>) => {
|
|
49
|
+
|
|
50
|
+
/*----------------------------------
|
|
51
|
+
- INIT
|
|
52
|
+
----------------------------------*/
|
|
53
|
+
|
|
54
|
+
const [{ value, focus, fieldProps }, setValue, commitValue, setState] = useInput(props, 0);
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
/*----------------------------------
|
|
58
|
+
- ATTRIBUTES
|
|
59
|
+
----------------------------------*/
|
|
60
|
+
|
|
61
|
+
let className: string = 'input slider';
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
/*----------------------------------
|
|
66
|
+
- VALIDATION
|
|
67
|
+
----------------------------------*/
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
/*----------------------------------
|
|
71
|
+
- RENDER
|
|
72
|
+
----------------------------------*/
|
|
73
|
+
return <>
|
|
74
|
+
<div class={className}>
|
|
75
|
+
<div class="contValue">
|
|
76
|
+
<Slider
|
|
77
|
+
step={step}
|
|
78
|
+
min={min}
|
|
79
|
+
max={max}
|
|
80
|
+
|
|
81
|
+
value={value}
|
|
82
|
+
onChange={(value: number) => {
|
|
83
|
+
setValue(value)
|
|
84
|
+
}}
|
|
85
|
+
|
|
86
|
+
className="champ slider"
|
|
87
|
+
thumbClassName="thumb"
|
|
88
|
+
trackClassName="track"
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
{errors?.length && (
|
|
93
|
+
<div class="fg error txt-left">
|
|
94
|
+
{errors.join('. ')}
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
</>
|
|
98
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
@hInput: @sizeComponent;
|
|
2
|
+
@labelH: 0.6em;
|
|
2
3
|
|
|
3
4
|
// TODO: Adapter textarea à input/basev2
|
|
4
5
|
.contChamp textarea {
|
|
@@ -9,56 +10,93 @@
|
|
|
9
10
|
padding-top: @spacing * 2 !important;
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
.input {
|
|
14
|
+
&.text,
|
|
15
|
+
&.select {
|
|
16
|
+
|
|
17
|
+
> i {
|
|
18
|
+
color: var(--cTxtDesc);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
label {
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
color: var(--cTxtImportant);
|
|
25
|
+
font-size: 0.9em;
|
|
26
|
+
font-weight: 600;
|
|
27
|
+
transition: all .1s ease-out;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.input.select {
|
|
33
|
+
|
|
34
|
+
padding: 0;
|
|
35
|
+
|
|
36
|
+
> * {
|
|
37
|
+
+ * {
|
|
38
|
+
border-top: solid 1px var(--cLine);;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.input.text {
|
|
43
|
+
border: none;
|
|
44
|
+
box-shadow: none;
|
|
45
|
+
padding: 0;
|
|
46
|
+
height: auto;
|
|
47
|
+
width: 150px;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
12
51
|
.input.text {
|
|
13
|
-
|
|
52
|
+
|
|
14
53
|
display: flex;
|
|
15
54
|
align-items: center;
|
|
55
|
+
align-items: flex-start;
|
|
16
56
|
padding: @spacing;
|
|
17
|
-
gap:
|
|
57
|
+
gap: 1em;
|
|
18
58
|
|
|
19
59
|
// Inputs don't have to have the same height as the buttons,
|
|
20
60
|
// Since they are not supposed to be on the same row (mobile first design)
|
|
21
61
|
height: @hInput;
|
|
22
|
-
|
|
62
|
+
align-items: center;
|
|
23
63
|
|
|
24
64
|
--cBg2: #eee;
|
|
25
65
|
//box-shadow: 1px 3px 0 fade(#000,4%), 0 1px 2px 0 fade(#000,2%);
|
|
26
66
|
|
|
27
|
-
> i {
|
|
28
|
-
color: var(--cTxtDesc);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.contValue {
|
|
32
|
-
position: relative;
|
|
33
|
-
flex: 1;
|
|
34
|
-
&, * {
|
|
35
|
-
cursor: text;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
67
|
@gapAround: (@hInput - @labelH) / 2;
|
|
40
68
|
> .btn:first-child { margin-left: 0 - @gapAround / 2; }
|
|
41
69
|
|
|
42
|
-
@labelH: 0.6em;
|
|
43
70
|
label {
|
|
44
71
|
position: absolute;
|
|
45
72
|
left: 0; width: 100%;
|
|
46
73
|
top: 0;
|
|
47
|
-
display: flex;
|
|
48
|
-
align-items: center;
|
|
49
|
-
|
|
50
74
|
height: @labelH;
|
|
51
75
|
font-size: 0.8em;
|
|
52
76
|
line-height: 0.8em;
|
|
53
|
-
|
|
54
|
-
|
|
77
|
+
|
|
78
|
+
color: var(--cTxtImportant);
|
|
55
79
|
}
|
|
56
80
|
|
|
57
81
|
&.empty:not(.focus) {
|
|
58
82
|
label {
|
|
59
83
|
height: 100%;
|
|
60
84
|
font-size: 1em;
|
|
61
|
-
|
|
85
|
+
color: var(--cTxtBase);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.contValue {
|
|
90
|
+
position: relative;
|
|
91
|
+
flex: 1;
|
|
92
|
+
|
|
93
|
+
display: flex;
|
|
94
|
+
flex-direction: column;
|
|
95
|
+
align-items: flex-start;
|
|
96
|
+
gap: @spacing / 2;
|
|
97
|
+
|
|
98
|
+
&, * {
|
|
99
|
+
cursor: text;
|
|
62
100
|
}
|
|
63
101
|
}
|
|
64
102
|
|
|
@@ -69,10 +107,19 @@
|
|
|
69
107
|
font-family: inherit;
|
|
70
108
|
width: 100%;
|
|
71
109
|
padding: @labelH 0 0 0;
|
|
110
|
+
font-size: 1rem;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
input {
|
|
72
114
|
height: 2.5em;
|
|
73
115
|
}
|
|
74
116
|
|
|
117
|
+
&.multiline {
|
|
118
|
+
height: auto;
|
|
119
|
+
}
|
|
120
|
+
|
|
75
121
|
textarea {
|
|
76
122
|
padding: @labelH + @spacing 0 @spacing 0;
|
|
123
|
+
min-height: 4em;
|
|
77
124
|
}
|
|
78
125
|
}
|
|
@@ -20,7 +20,7 @@ export type InputBaseProps<TValue> = {
|
|
|
20
20
|
errors?: string[],
|
|
21
21
|
|
|
22
22
|
value: TValue,
|
|
23
|
-
onChange?:
|
|
23
|
+
onChange?: (value: TValue) => void,
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export type TInputState<TValue> = {
|
|
@@ -35,7 +35,7 @@ export type TInputState<TValue> = {
|
|
|
35
35
|
- HOOKS
|
|
36
36
|
----------------------------------*/
|
|
37
37
|
export function useInput<TValue>(
|
|
38
|
-
{ value: externalValue, onChange }: InputBaseProps<TValue>,
|
|
38
|
+
{ value: externalValue, onChange, ...otherProps }: InputBaseProps<TValue>,
|
|
39
39
|
defaultValue: TValue,
|
|
40
40
|
autoCommit: boolean = false
|
|
41
41
|
): [
|
|
@@ -48,7 +48,7 @@ export function useInput<TValue>(
|
|
|
48
48
|
const [state, setState] = useState<TInputState<TValue>>({
|
|
49
49
|
value: externalValue !== undefined ? externalValue : defaultValue,
|
|
50
50
|
valueSource: 'external',
|
|
51
|
-
fieldProps:
|
|
51
|
+
fieldProps: otherProps,
|
|
52
52
|
focus: false,
|
|
53
53
|
changed: false
|
|
54
54
|
});
|
|
@@ -57,7 +57,7 @@ export function useInput<TValue>(
|
|
|
57
57
|
setState({ value, valueSource: 'internal', changed: true });
|
|
58
58
|
|
|
59
59
|
if (autoCommit)
|
|
60
|
-
commitValue(
|
|
60
|
+
commitValue();
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
const commitValue = () => {
|
|
@@ -71,6 +71,8 @@ export default ({
|
|
|
71
71
|
|
|
72
72
|
}, [value]);
|
|
73
73
|
|
|
74
|
+
const updateValue = v => setValue( type === 'number' ? parseFloat(v) : v );
|
|
75
|
+
|
|
74
76
|
const refInput = inputRef || React.useRef<HTMLInputElement>();
|
|
75
77
|
|
|
76
78
|
/*----------------------------------
|
|
@@ -98,7 +100,8 @@ export default ({
|
|
|
98
100
|
} else if (type === 'longtext') {
|
|
99
101
|
|
|
100
102
|
prefix = prefix || <i src="text" />;
|
|
101
|
-
Tag = 'textarea'
|
|
103
|
+
Tag = 'textarea';
|
|
104
|
+
className += ' multiline';
|
|
102
105
|
|
|
103
106
|
} else if (type === 'number') {
|
|
104
107
|
|
|
@@ -139,42 +142,44 @@ export default ({
|
|
|
139
142
|
/*----------------------------------
|
|
140
143
|
- RENDER
|
|
141
144
|
----------------------------------*/
|
|
142
|
-
return
|
|
143
|
-
<div class=
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
<
|
|
168
|
-
|
|
145
|
+
return (
|
|
146
|
+
<div class="col sp-05">
|
|
147
|
+
<div class={className} onClick={() => refInput.current?.focus()}>
|
|
148
|
+
|
|
149
|
+
{prefix}
|
|
150
|
+
|
|
151
|
+
<div class="contValue">
|
|
152
|
+
|
|
153
|
+
<Tag {...fieldProps}
|
|
154
|
+
// @ts-ignore: Property 'ref' does not exist on type 'IntrinsicAttributes'
|
|
155
|
+
ref={refInput}
|
|
156
|
+
value={value}
|
|
157
|
+
|
|
158
|
+
onFocus={() => setState({ focus: true })}
|
|
159
|
+
onBlur={() => setState({ focus: false })}
|
|
160
|
+
onChange={(e) => updateValue(e.target.value)}
|
|
161
|
+
|
|
162
|
+
onKeyDown={(e) => {
|
|
163
|
+
if (onPressEnter && e.key === 'Enter' && value !== undefined) {
|
|
164
|
+
commitValue();
|
|
165
|
+
onPressEnter(value)
|
|
166
|
+
}
|
|
167
|
+
}}
|
|
168
|
+
/>
|
|
169
|
+
|
|
170
|
+
<label>{props.title}{required && (
|
|
171
|
+
<span class="fg error"> *</span>
|
|
172
|
+
)}</label>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
{suffix}
|
|
176
|
+
|
|
169
177
|
</div>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
178
|
+
{errors?.length && (
|
|
179
|
+
<div class="fg error txt-left">
|
|
180
|
+
{errors.join('. ')}
|
|
181
|
+
</div>
|
|
182
|
+
)}
|
|
173
183
|
</div>
|
|
174
|
-
|
|
175
|
-
<div class="fg error txt-left">
|
|
176
|
-
{errors.join('. ')}
|
|
177
|
-
</div>
|
|
178
|
-
)}
|
|
179
|
-
</>
|
|
184
|
+
)
|
|
180
185
|
}
|
|
@@ -17,8 +17,6 @@ import { history } from '@client/services/router/request/history';
|
|
|
17
17
|
export type TDataProvider<TProvidedData extends TFetcherList = {}> =
|
|
18
18
|
(context: TClientOrServerContext/* & TUrlData */) => TProvidedData
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
20
|
// The function that renders routes
|
|
23
21
|
export type TFrontRenderer<
|
|
24
22
|
TProvidedData extends TFetcherList = {},
|
|
@@ -43,6 +41,8 @@ export type TPageResource = {
|
|
|
43
41
|
preload?: boolean
|
|
44
42
|
})
|
|
45
43
|
|
|
44
|
+
const debug = false;
|
|
45
|
+
|
|
46
46
|
/*----------------------------------
|
|
47
47
|
- CLASS
|
|
48
48
|
----------------------------------*/
|
|
@@ -81,7 +81,7 @@ export default class PageResponse<TRouter extends ClientOrServerRouter = ClientO
|
|
|
81
81
|
this.fetchers = this.dataProvider({ ...this.context, ...this.context.request.data });
|
|
82
82
|
|
|
83
83
|
// Execute the fetchers for missing data
|
|
84
|
-
console.log(`[router][page] Fetching api data:` + Object.keys(this.fetchers));
|
|
84
|
+
debug && console.log(`[router][page] Fetching api data:` + Object.keys(this.fetchers));
|
|
85
85
|
this.data = await this.context.request.api.fetchSync( this.fetchers, this.data );
|
|
86
86
|
return this.data;
|
|
87
87
|
}
|
|
@@ -23,9 +23,6 @@ export type TValidator<TValue> = {
|
|
|
23
23
|
activer?: (donnees: TObjetDonnees) => boolean,
|
|
24
24
|
onglet?: string, // Sert juste d'identifiant secondaire. Ex: nom onglet correspondant
|
|
25
25
|
|
|
26
|
-
// Restrict to a specific set of values
|
|
27
|
-
in?: TValue[],
|
|
28
|
-
|
|
29
26
|
// Executé après le validateur propre au type
|
|
30
27
|
dependances?: string[],
|
|
31
28
|
opt?: true,
|
|
@@ -15,6 +15,7 @@ import { InputError } from '@common/errors';
|
|
|
15
15
|
import FileToUpload from '@client/components/inputv3/file/FileToUpload';
|
|
16
16
|
|
|
17
17
|
// Speciific
|
|
18
|
+
import Schema from './schema'
|
|
18
19
|
import Validator, { TValidator } from './validator'
|
|
19
20
|
|
|
20
21
|
// Components
|
|
@@ -63,15 +64,14 @@ export default class SchemaValidators {
|
|
|
63
64
|
return val;
|
|
64
65
|
}, opts)
|
|
65
66
|
|
|
66
|
-
public array = (subtype?: Validator<any
|
|
67
|
+
public array = ( subtype?: Validator<any> | Schema<{}>, {
|
|
68
|
+
choice, min, max, ...opts
|
|
69
|
+
}: TValidator<any[]> & {
|
|
67
70
|
choice?: any[],
|
|
68
71
|
min?: number,
|
|
69
72
|
max?: number
|
|
70
73
|
} = {}) => {
|
|
71
74
|
|
|
72
|
-
if (subtype !== undefined)
|
|
73
|
-
subtype.options.in = choice;
|
|
74
|
-
|
|
75
75
|
return new Validator<any[]>('array', (items, input, output, corriger) => {
|
|
76
76
|
|
|
77
77
|
// Type
|
|
@@ -86,32 +86,41 @@ export default class SchemaValidators {
|
|
|
86
86
|
|
|
87
87
|
// Verif each item
|
|
88
88
|
if (subtype !== undefined) {
|
|
89
|
-
if (
|
|
89
|
+
if (subtype instanceof Schema) {
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
items = items.map( item =>
|
|
92
|
+
subtype.validate( item, item, item, { }, []).values
|
|
93
|
+
)
|
|
92
94
|
|
|
93
95
|
} else {
|
|
94
96
|
|
|
95
97
|
items = items.map( item =>
|
|
96
98
|
subtype.validate( item, items, items, corriger )
|
|
97
99
|
)
|
|
100
|
+
|
|
98
101
|
}
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
return items;
|
|
102
105
|
}, {
|
|
103
106
|
...opts,
|
|
104
|
-
in: choice,
|
|
105
107
|
//multiple: true, // Sélection multiple
|
|
106
108
|
//subtype
|
|
107
109
|
})
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
public choice = (values
|
|
112
|
+
public choice = (values?: any[], opts: TValidator<any> & {} = {}) =>
|
|
111
113
|
new Validator<any>('choice', (val, input, output) => {
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
// Choice object
|
|
116
|
+
if (typeof val === 'object' && ('value' in val) && typeof val.value !== 'object')
|
|
117
|
+
val = val.value;
|
|
118
|
+
|
|
119
|
+
if (values !== undefined) {
|
|
120
|
+
const isValid = values.some(v => v.value === val);
|
|
121
|
+
if (!isValid)
|
|
122
|
+
throw new InputError("Invalid value. Must be: " + values.map(v => v.value).join(', '));
|
|
123
|
+
}
|
|
115
124
|
|
|
116
125
|
return val;
|
|
117
126
|
|
|
@@ -123,10 +132,6 @@ export default class SchemaValidators {
|
|
|
123
132
|
public string = ({ min, max, ...opts }: TValidator<string> & { min?: number, max?: number } = {}) =>
|
|
124
133
|
new Validator<string>('string', (val, input, output, corriger?: boolean) => {
|
|
125
134
|
|
|
126
|
-
// Choice value from Select component
|
|
127
|
-
if (typeof val === 'object' && val.value)
|
|
128
|
-
val = val.value;
|
|
129
|
-
|
|
130
135
|
if (val === '')
|
|
131
136
|
return undefined;
|
|
132
137
|
else if (typeof val === 'number')
|
|
@@ -173,9 +178,30 @@ export default class SchemaValidators {
|
|
|
173
178
|
if (!isEmail(val))
|
|
174
179
|
throw new InputError("Please enter a valid email address.");
|
|
175
180
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
181
|
+
// Disable normalzation !!! We should keep the email as it was entered by the user
|
|
182
|
+
/*const normalizedEmail = normalizeEmail(val, {
|
|
183
|
+
all_lowercase: true,
|
|
184
|
+
gmail_lowercase: true,
|
|
185
|
+
gmail_remove_dots: false,
|
|
186
|
+
gmail_remove_subaddress: true,
|
|
187
|
+
gmail_convert_googlemaildotcom: true,
|
|
188
|
+
|
|
189
|
+
outlookdotcom_lowercase: true,
|
|
190
|
+
outlookdotcom_remove_subaddress: true,
|
|
191
|
+
|
|
192
|
+
yahoo_lowercase: true,
|
|
193
|
+
yahoo_remove_subaddress: true,
|
|
194
|
+
|
|
195
|
+
yandex_lowercase: true,
|
|
196
|
+
|
|
197
|
+
icloud_lowercase: true,
|
|
198
|
+
icloud_remove_subaddress: true,
|
|
199
|
+
});*/
|
|
200
|
+
|
|
201
|
+
const normalizedEmail = val.toLowerCase();
|
|
202
|
+
console.log("validate email, inou", val, normalizedEmail);
|
|
203
|
+
|
|
204
|
+
return normalizedEmail;
|
|
179
205
|
}, opts)
|
|
180
206
|
|
|
181
207
|
/*----------------------------------
|
package/src/server/app/index.ts
CHANGED
|
@@ -94,8 +94,7 @@ export default abstract class Application extends Service<Config, Hooks, /* TODO
|
|
|
94
94
|
|
|
95
95
|
public constructor() {
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
super();
|
|
97
|
+
super({}, {});
|
|
99
98
|
|
|
100
99
|
// Gestion crash
|
|
101
100
|
process.on('unhandledRejection', (error: any, promise: any) => {
|
|
@@ -24,6 +24,10 @@ type THookOptions = {
|
|
|
24
24
|
|
|
25
25
|
export type TPriority = -2 | -1 | 0 | 1 | 2
|
|
26
26
|
|
|
27
|
+
type TServiceConfig = {
|
|
28
|
+
debug?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
27
31
|
/*----------------------------------
|
|
28
32
|
- CONFIG
|
|
29
33
|
----------------------------------*/
|
|
@@ -34,7 +38,7 @@ const LogPrefix = '[service]';
|
|
|
34
38
|
- CLASS
|
|
35
39
|
----------------------------------*/
|
|
36
40
|
export default abstract class Service<
|
|
37
|
-
TConfig extends
|
|
41
|
+
TConfig extends TServiceConfig,
|
|
38
42
|
THooks extends THooksList,
|
|
39
43
|
TApplication extends Application
|
|
40
44
|
> {
|
|
@@ -88,7 +92,7 @@ export default abstract class Service<
|
|
|
88
92
|
if (!callbacks)
|
|
89
93
|
return console.info(LogPrefix, `No ${name} hook defined in the current service instance.`);
|
|
90
94
|
|
|
91
|
-
console.info(`[hook] Run all ${name} hook (${callbacks.length}).`);
|
|
95
|
+
this.config.debug && console.info(`[hook] Run all ${name} hook (${callbacks.length}).`);
|
|
92
96
|
return Promise.all(
|
|
93
97
|
callbacks.map(
|
|
94
98
|
cb => cb(...args).catch(e => {
|
|
@@ -98,7 +102,7 @@ export default abstract class Service<
|
|
|
98
102
|
})
|
|
99
103
|
)
|
|
100
104
|
).then(() => {
|
|
101
|
-
console.info(`[hook] Hooks ${name} executed with success.`);
|
|
105
|
+
this.config.debug && console.info(`[hook] Hooks ${name} executed with success.`);
|
|
102
106
|
})
|
|
103
107
|
}
|
|
104
108
|
|
|
@@ -37,6 +37,7 @@ type ConnectionConfig = {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export type DatabaseServiceConfig = {
|
|
40
|
+
debug: boolean,
|
|
40
41
|
connections: ConnectionConfig[]
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -207,7 +208,7 @@ export default class DatabaseManager extends Service<DatabaseServiceConfig, THoo
|
|
|
207
208
|
|
|
208
209
|
let jsTypeName = mysqlToJs[ mysqlType.name ];
|
|
209
210
|
if (jsTypeName === undefined) {
|
|
210
|
-
console.warn(`The mySQL data type « ${mysqlType.name} » has not been associated with a JS equivalent in mysqlToJs. Using any instead.`);
|
|
211
|
+
this.config.debug && console.warn(`Column "${field.table}.${field.name}": The mySQL data type « ${mysqlType.name} » has not been associated with a JS equivalent in mysqlToJs. Using any instead.`);
|
|
211
212
|
jsTypeName = mysqlToJs['UNKNOWN'];
|
|
212
213
|
}
|
|
213
214
|
|
|
@@ -201,9 +201,9 @@ export default class SQL extends Service<Config, Hooks, Application> {
|
|
|
201
201
|
|
|
202
202
|
// SQL query
|
|
203
203
|
} else if (typeof value === 'function' && value.string !== undefined)
|
|
204
|
-
value = value.string;
|
|
204
|
+
value = ' ' + value.string;
|
|
205
205
|
else
|
|
206
|
-
value = mysql.escape(value);
|
|
206
|
+
value = ' ' + mysql.escape(value);
|
|
207
207
|
|
|
208
208
|
stringBefore += value;
|
|
209
209
|
|
|
@@ -310,7 +310,7 @@ export default class SQL extends Service<Config, Hooks, Application> {
|
|
|
310
310
|
return this.database.query(
|
|
311
311
|
data.map( record =>
|
|
312
312
|
this.update(tableName, record, where, { ...opts, returnQuery: true })
|
|
313
|
-
).join('
|
|
313
|
+
).join('\n')
|
|
314
314
|
)
|
|
315
315
|
|
|
316
316
|
// Automatic where based on pks
|
|
@@ -298,7 +298,7 @@ export default class MySQLMetasParser {
|
|
|
298
298
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
private parseJsType(
|
|
301
|
+
private parseJsType( colName: string, mysqlType: TMySQLType, comment: string | null, isOptional?: boolean ): TJsType {
|
|
302
302
|
|
|
303
303
|
let typeName: TJsType["name"] | undefined;
|
|
304
304
|
let params: TJsType["params"];
|
|
@@ -324,7 +324,7 @@ export default class MySQLMetasParser {
|
|
|
324
324
|
|
|
325
325
|
// Equivalent not found
|
|
326
326
|
if (typeName === undefined) {
|
|
327
|
-
console.warn(`The mySQL data type « ${mysqlType.name} » has not been associated with a JS equivalent in mysqlToJs. Using any instead.`);
|
|
327
|
+
this.database.config.debug && console.warn(`Column "${colName}": The mySQL data type « ${mysqlType.name} » has not been associated with a JS equivalent in mysqlToJs. Using any instead.`);
|
|
328
328
|
typeName = mysqlToJs['UNKNOWN'];
|
|
329
329
|
}
|
|
330
330
|
}
|
|
@@ -334,7 +334,7 @@ export default class MySQLMetasParser {
|
|
|
334
334
|
if (!jsTypeUtils)
|
|
335
335
|
throw new Error(`Unable to find the typescript print funvction for js type "${typeName}"`);
|
|
336
336
|
|
|
337
|
-
const raw =
|
|
337
|
+
const raw = colName + (isOptional ? '?' : '') + ': ' + jsTypeUtils.print( mysqlType.params );
|
|
338
338
|
|
|
339
339
|
return { name: typeName, params, raw }
|
|
340
340
|
}
|
|
@@ -564,7 +564,7 @@ declare type Routes = {
|
|
|
564
564
|
e.message = "We encountered an internal error, and our team has just been notified. Sorry for the inconvenience.";
|
|
565
565
|
|
|
566
566
|
// Pour déboguer les erreurs HTTP
|
|
567
|
-
} else if (this.app.env.profile === "dev")
|
|
567
|
+
} else if (code !== 404 && this.app.env.profile === "dev")
|
|
568
568
|
console.warn(e);
|
|
569
569
|
|
|
570
570
|
if (request.accepts("html"))
|