5htp-core 0.3.7 → 0.3.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 +5 -3
- package/src/client/assets/css/components/button.less +6 -10
- package/src/client/assets/css/components/card.less +1 -7
- package/src/client/assets/css/text/icons.less +8 -5
- package/src/client/assets/css/text/text.less +2 -3
- package/src/client/assets/css/theme.less +1 -1
- package/src/client/assets/css/utils/layouts.less +4 -0
- package/src/client/components/Form.ts +2 -1
- package/src/client/components/Select/ChoiceElement.tsx +20 -6
- package/src/client/components/Select/ChoiceSelector.tsx +3 -2
- package/src/client/components/Select/index.tsx +53 -21
- package/src/client/components/containers/Popover/index.tsx +4 -1
- package/src/client/components/data/Time.tsx +16 -12
- package/src/client/components/inputv3/base.tsx +1 -0
- package/src/client/components/inputv3/index.tsx +3 -1
- package/src/client/services/router/components/router.tsx +10 -8
- package/src/client/services/router/index.tsx +0 -0
- package/src/client/services/router/request/api.ts +9 -3
- package/src/common/data/dates.ts +20 -4
- package/src/common/data/objets.ts +17 -0
- package/src/common/router/index.ts +1 -1
- package/src/common/validation/schema.ts +85 -91
- package/src/common/validation/validator.ts +4 -6
- package/src/common/validation/validators.ts +74 -72
- package/src/server/{services → app/container}/console/index.ts +52 -16
- package/src/server/app/container/index.ts +54 -0
- package/src/server/app/index.ts +22 -22
- package/src/server/app/service/index.ts +36 -11
- package/src/server/app.tsconfig.json +1 -1
- package/src/server/context.ts +1 -1
- package/src/server/index.ts +2 -10
- package/src/server/services/{users → auth}/index.ts +1 -1
- package/src/server/services/database/connection.ts +3 -1
- package/src/server/services/database/index.ts +34 -24
- package/src/server/services/database/model.ts +28 -0
- package/src/server/services/fetch/index.ts +4 -3
- package/src/server/services/router/index.ts +4 -1
- package/src/server/services/schema/request.ts +4 -11
- package/src/server/services/socket/index.ts +1 -1
- package/src/server/services/console/service.json +0 -6
- /package/src/server/{services → app/container}/console/html.ts +0 -0
- /package/src/server/services/{users → auth}/old.ts +0 -0
- /package/src/server/services/{users → auth}/router/index.ts +0 -0
- /package/src/server/services/{users → auth}/router/request.ts +0 -0
- /package/src/server/services/{users → auth}/router/service.json +0 -0
- /package/src/server/services/{users → auth}/service.json +0 -0
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.3.
|
|
4
|
+
"version": "0.3.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",
|
|
@@ -80,7 +80,9 @@
|
|
|
80
80
|
"validator": "^13.7.0",
|
|
81
81
|
"ws": "^8.2.2",
|
|
82
82
|
"yaml": "^1.10.2",
|
|
83
|
-
"yargs-parser": "^21.1.1"
|
|
83
|
+
"yargs-parser": "^21.1.1",
|
|
84
|
+
"youch": "^3.3.3",
|
|
85
|
+
"youch-terminal": "^2.2.3"
|
|
84
86
|
},
|
|
85
87
|
"devDependencies": {
|
|
86
88
|
"@types/cookie": "^0.4.1",
|
|
@@ -97,6 +99,6 @@
|
|
|
97
99
|
"babel-plugin-glob-import": "^0.0.7"
|
|
98
100
|
},
|
|
99
101
|
"peerDependencies": {
|
|
100
|
-
"5htp": "0.3.
|
|
102
|
+
"5htp": "0.3.8"
|
|
101
103
|
}
|
|
102
104
|
}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
// Text
|
|
27
27
|
text-decoration: none;
|
|
28
|
-
font-weight:
|
|
28
|
+
font-weight: 500;
|
|
29
29
|
|
|
30
30
|
// Colors
|
|
31
31
|
background: var(--cBg);
|
|
@@ -90,12 +90,6 @@
|
|
|
90
90
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
> .pastille {
|
|
94
|
-
position: absolute;
|
|
95
|
-
right: 10px;
|
|
96
|
-
bottom: 10px;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
93
|
/*----------------------------------
|
|
100
94
|
- THEME
|
|
101
95
|
----------------------------------*/
|
|
@@ -230,9 +224,12 @@ ul.row {
|
|
|
230
224
|
color: var(--cTxtImportant);
|
|
231
225
|
}
|
|
232
226
|
|
|
233
|
-
// All the list items label must be aligned
|
|
234
227
|
> .label {
|
|
228
|
+
// All the list items label must be aligned
|
|
235
229
|
justify-content: flex-start;
|
|
230
|
+
// Since they're all horizontally aligned,
|
|
231
|
+
// Label = max width, so icon right are also aligned to right
|
|
232
|
+
flex: 1;
|
|
236
233
|
}
|
|
237
234
|
|
|
238
235
|
&.icon {
|
|
@@ -244,8 +241,7 @@ ul.row {
|
|
|
244
241
|
display: none;
|
|
245
242
|
position: absolute;
|
|
246
243
|
|
|
247
|
-
background:
|
|
248
|
-
backdrop-filter: blur(20px) saturate(180%);
|
|
244
|
+
background: #111;
|
|
249
245
|
|
|
250
246
|
height: @sizeComponent;
|
|
251
247
|
line-height: @sizeComponent;
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
&.
|
|
80
|
+
&.active {
|
|
81
81
|
|
|
82
82
|
box-shadow: 0 0 0 3px @c1;
|
|
83
83
|
|
|
@@ -86,12 +86,6 @@
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
&.minimal {
|
|
90
|
-
background:transparent;
|
|
91
|
-
box-shadow: none;
|
|
92
|
-
border: solid 1px var(--cLine);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
89
|
/*----------------------------------
|
|
96
90
|
- VARIANTS
|
|
97
91
|
----------------------------------*/
|
|
@@ -33,11 +33,14 @@ i {
|
|
|
33
33
|
|
|
34
34
|
&.solid {
|
|
35
35
|
color: var(--cAccent2);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
background: var(--cBg);
|
|
37
|
+
border-radius: @radius;
|
|
38
|
+
|
|
39
|
+
// Normla size must fit inside a normal fit element
|
|
40
|
+
width: @sizeComponent * 0.75;
|
|
41
|
+
flex: 0 0 @sizeComponent * 0.75;
|
|
42
|
+
height: @sizeComponent * 0.75;
|
|
43
|
+
line-height: @sizeComponent * 0.75;
|
|
41
44
|
//font-size: 0.9em;
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -103,8 +103,7 @@ strong {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
strong.number
|
|
107
|
-
header > strong {
|
|
106
|
+
strong.number {
|
|
108
107
|
//font-family: 'Montserrat';
|
|
109
108
|
font-size: 1.3em;
|
|
110
109
|
line-height: 1em;
|
|
@@ -181,7 +180,7 @@ pre {
|
|
|
181
180
|
|
|
182
181
|
h2, h3, h4 {
|
|
183
182
|
text-align: left;
|
|
184
|
-
margin:
|
|
183
|
+
margin: 1em 0;
|
|
185
184
|
}
|
|
186
185
|
|
|
187
186
|
h2 {
|
|
@@ -109,7 +109,7 @@ export default function useForm<TFormData extends {}>(
|
|
|
109
109
|
----------------------------------*/
|
|
110
110
|
const validate = (allData: Partial<TFormData> = data, validateAll: boolean = true) => {
|
|
111
111
|
|
|
112
|
-
const validated = schema.
|
|
112
|
+
const validated = schema.validateWithDetails(allData, allData, {}, {
|
|
113
113
|
// Ignore the fields where the vlaue has not been changed
|
|
114
114
|
// if the validation was triggered via onChange
|
|
115
115
|
ignoreMissing: !validateAll,
|
|
@@ -138,6 +138,7 @@ export default function useForm<TFormData extends {}>(
|
|
|
138
138
|
context.app.handleError(
|
|
139
139
|
new InputError("You have " + validated.errorsCount + " errors in the form.")
|
|
140
140
|
);
|
|
141
|
+
console.log("validated", validated.erreurs);
|
|
141
142
|
return;
|
|
142
143
|
}
|
|
143
144
|
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
// Npm
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
|
+
// Cpre
|
|
9
|
+
import { Button } from '@client/components';
|
|
10
|
+
|
|
8
11
|
// Specific
|
|
9
|
-
import type {
|
|
10
|
-
Choice,
|
|
11
|
-
} from './ChoiceSelector';
|
|
12
|
+
import type { Choice } from './ChoiceSelector';
|
|
12
13
|
|
|
13
14
|
import type { Props } from '.';
|
|
14
15
|
|
|
@@ -19,10 +20,11 @@ import type { Props } from '.';
|
|
|
19
20
|
/*----------------------------------
|
|
20
21
|
- COMPONENT
|
|
21
22
|
----------------------------------*/
|
|
22
|
-
export default ({ choice, currentList, onChange, multiple, includeCurrent }: {
|
|
23
|
+
export default ({ choice, currentList, onChange, multiple, includeCurrent, format = 'badge' }: {
|
|
23
24
|
choice: Choice,
|
|
24
25
|
currentList: Choice[],
|
|
25
|
-
includeCurrent
|
|
26
|
+
includeCurrent?: boolean,
|
|
27
|
+
format?: 'list' | 'badge'
|
|
26
28
|
} & Pick<Props, 'onChange'|'multiple'>) => {
|
|
27
29
|
|
|
28
30
|
const isCurrent = currentList.some(c => c.value === choice.value);
|
|
@@ -30,7 +32,19 @@ export default ({ choice, currentList, onChange, multiple, includeCurrent }: {
|
|
|
30
32
|
|
|
31
33
|
const showRemoveButton = multiple;
|
|
32
34
|
|
|
33
|
-
return
|
|
35
|
+
return format === 'list' ? (
|
|
36
|
+
<li>
|
|
37
|
+
<Button icon={isCurrent ? 'check-circle' : undefined} onClick={() => onChange( current => multiple
|
|
38
|
+
? (isCurrent
|
|
39
|
+
? currentList.filter(item => item.value !== choice.value)
|
|
40
|
+
: [...(current || []), choice]
|
|
41
|
+
)
|
|
42
|
+
: isCurrent ? undefined : choice
|
|
43
|
+
)}>
|
|
44
|
+
{choice.label}
|
|
45
|
+
</Button>
|
|
46
|
+
</li>
|
|
47
|
+
) : isCurrent ? (
|
|
34
48
|
<li class={"badge bg primary"+ (showRemoveButton ? ' pdr-05' : '')}>
|
|
35
49
|
{choice.label}
|
|
36
50
|
|
|
@@ -25,14 +25,14 @@ type ChoicesFunc = (search: string) => Promise<Choices>
|
|
|
25
25
|
export type Props = (
|
|
26
26
|
{
|
|
27
27
|
multiple: true,
|
|
28
|
-
value?: Choice[],
|
|
28
|
+
value?: Choice[] | Choice["value"][],
|
|
29
29
|
onChange: StateUpdater<Choice[]>,
|
|
30
30
|
validator?: ArrayValidator
|
|
31
31
|
}
|
|
32
32
|
|
|
|
33
33
|
{
|
|
34
34
|
multiple?: false,
|
|
35
|
-
value?: Choice,
|
|
35
|
+
value?: Choice | Choice["value"],
|
|
36
36
|
onChange: StateUpdater<Choice>,
|
|
37
37
|
validator?: StringValidator
|
|
38
38
|
}
|
|
@@ -53,6 +53,7 @@ export type Props = (
|
|
|
53
53
|
- we don't want the selector to be rendered before the dropdown content is dhown
|
|
54
54
|
- this component is called multiple time
|
|
55
55
|
*/
|
|
56
|
+
// ! OBSOLETE
|
|
56
57
|
export default React.forwardRef<HTMLDivElement, Props>(({
|
|
57
58
|
choices: initChoices,
|
|
58
59
|
validator,
|
|
@@ -29,6 +29,25 @@ export type Props = DropdownProps & SelectorProps & {
|
|
|
29
29
|
|
|
30
30
|
export type { Choice } from './ChoiceSelector';
|
|
31
31
|
|
|
32
|
+
const ensureChoice = (choice: Choice | string, choices: Choice[]): Choice => {
|
|
33
|
+
|
|
34
|
+
// Allready a choice
|
|
35
|
+
if (typeof choice === 'object' && choice.label) {
|
|
36
|
+
return choice;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Find the choice
|
|
40
|
+
const found = choices.find( c => c.value === choice);
|
|
41
|
+
if (found)
|
|
42
|
+
return found;
|
|
43
|
+
|
|
44
|
+
// Create a new choice
|
|
45
|
+
return {
|
|
46
|
+
label: choice,
|
|
47
|
+
value: choice
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
32
51
|
/*----------------------------------
|
|
33
52
|
- COMONENT
|
|
34
53
|
----------------------------------*/
|
|
@@ -95,8 +114,8 @@ export default ({
|
|
|
95
114
|
const currentList: Choice[] = current === undefined
|
|
96
115
|
? []
|
|
97
116
|
: (Array.isArray(current)
|
|
98
|
-
? current
|
|
99
|
-
: [current]
|
|
117
|
+
? current.map( c => ensureChoice(c, choices))
|
|
118
|
+
: [ensureChoice(current, choices)]
|
|
100
119
|
);
|
|
101
120
|
|
|
102
121
|
/*----------------------------------
|
|
@@ -121,14 +140,7 @@ export default ({
|
|
|
121
140
|
- RENDER
|
|
122
141
|
----------------------------------*/
|
|
123
142
|
|
|
124
|
-
const
|
|
125
|
-
<ChoiceElement choice={choice}
|
|
126
|
-
currentList={currentList}
|
|
127
|
-
onChange={onChange}
|
|
128
|
-
multiple={multiple}
|
|
129
|
-
includeCurrent
|
|
130
|
-
/>
|
|
131
|
-
))
|
|
143
|
+
const selectedItems = enableSearch ? currentList : choices
|
|
132
144
|
|
|
133
145
|
const Search = enableSearch && (
|
|
134
146
|
<Input
|
|
@@ -145,7 +157,7 @@ export default ({
|
|
|
145
157
|
overflowY: 'auto'
|
|
146
158
|
}}>
|
|
147
159
|
{choices.map( choice => (
|
|
148
|
-
<ChoiceElement choice={choice}
|
|
160
|
+
<ChoiceElement format='badge' choice={choice}
|
|
149
161
|
currentList={currentList}
|
|
150
162
|
onChange={onChange}
|
|
151
163
|
multiple={multiple}
|
|
@@ -156,14 +168,21 @@ export default ({
|
|
|
156
168
|
|
|
157
169
|
return dropdown ? (
|
|
158
170
|
<Popover content={(
|
|
159
|
-
<div class="card col" style={{ width: '
|
|
171
|
+
<div class="card col" style={{ width: '200px' }}>
|
|
160
172
|
|
|
161
173
|
<div class="col">
|
|
162
174
|
|
|
163
|
-
{
|
|
164
|
-
<
|
|
165
|
-
{
|
|
166
|
-
|
|
175
|
+
{selectedItems.length !== 0 && (
|
|
176
|
+
<ul class="menu col">
|
|
177
|
+
{selectedItems.map( choice => (
|
|
178
|
+
<ChoiceElement format='list' choice={choice}
|
|
179
|
+
currentList={currentList}
|
|
180
|
+
onChange={onChange}
|
|
181
|
+
multiple={multiple}
|
|
182
|
+
includeCurrent
|
|
183
|
+
/>
|
|
184
|
+
))}
|
|
185
|
+
</ul>
|
|
167
186
|
)}
|
|
168
187
|
|
|
169
188
|
{Search}
|
|
@@ -173,10 +192,15 @@ export default ({
|
|
|
173
192
|
</div>
|
|
174
193
|
)} state={popoverState}>
|
|
175
194
|
<Button icon={icon} iconR="chevron-down" {...otherProps}>
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
195
|
+
|
|
196
|
+
{currentList.length === 0 ? <>
|
|
197
|
+
{title}
|
|
198
|
+
</> : multiple ? <>
|
|
199
|
+
{title} <span class="badge s bg accent">{currentList.length}</span>
|
|
200
|
+
</> : <>
|
|
201
|
+
{currentList[0].label}
|
|
202
|
+
</>}
|
|
203
|
+
|
|
180
204
|
</Button>
|
|
181
205
|
</Popover>
|
|
182
206
|
) : (
|
|
@@ -197,7 +221,15 @@ export default ({
|
|
|
197
221
|
)}</label>
|
|
198
222
|
|
|
199
223
|
<div class="row al-left wrap sp-05">
|
|
200
|
-
|
|
224
|
+
|
|
225
|
+
{selectedItems.map( choice => (
|
|
226
|
+
<ChoiceElement format='badge' choice={choice}
|
|
227
|
+
currentList={currentList}
|
|
228
|
+
onChange={onChange}
|
|
229
|
+
multiple={multiple}
|
|
230
|
+
includeCurrent
|
|
231
|
+
/>
|
|
232
|
+
))}
|
|
201
233
|
|
|
202
234
|
{Search}
|
|
203
235
|
</div>
|
|
@@ -21,7 +21,7 @@ export type Props = JSX.HTMLAttributes<HTMLDivElement> & {
|
|
|
21
21
|
|
|
22
22
|
// Display
|
|
23
23
|
content?: JSX.Element,
|
|
24
|
-
state
|
|
24
|
+
state?: [boolean, StateUpdater<boolean>],
|
|
25
25
|
width?: number | string,
|
|
26
26
|
disable?: boolean
|
|
27
27
|
// Position
|
|
@@ -58,6 +58,9 @@ export default (props: Props) => {
|
|
|
58
58
|
const refCont = React.useRef<HTMLElement>(null);
|
|
59
59
|
const refContent = React.useRef<HTMLElement>(null);
|
|
60
60
|
|
|
61
|
+
if (state === undefined)
|
|
62
|
+
state = React.useState(false);
|
|
63
|
+
|
|
61
64
|
const [shown, show] = state;
|
|
62
65
|
|
|
63
66
|
// Màj visibilite
|
|
@@ -4,36 +4,40 @@
|
|
|
4
4
|
|
|
5
5
|
// Npm
|
|
6
6
|
import React from 'react';
|
|
7
|
+
import { ComponentChild } from 'preact';
|
|
7
8
|
|
|
8
9
|
// Libs
|
|
9
|
-
import { timeSince } from '@common/data/dates';
|
|
10
|
+
import { timeSince, TDateInfo } from '@common/data/dates';
|
|
10
11
|
|
|
11
12
|
/*----------------------------------
|
|
12
|
-
- TYPES
|
|
13
|
+
- TYPES
|
|
13
14
|
----------------------------------*/
|
|
15
|
+
export type { TDateInfo } from '@common/data/dates';
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
export const interval = {
|
|
18
|
+
day: 24 * 60 * 60,
|
|
19
|
+
hour: 60 * 60,
|
|
20
|
+
minute: 60,
|
|
21
|
+
second: 1
|
|
22
|
+
}
|
|
20
23
|
|
|
21
24
|
/*----------------------------------
|
|
22
25
|
- COMPOSANT
|
|
23
26
|
----------------------------------*/
|
|
24
|
-
export default ({ since }: {
|
|
25
|
-
since: Parameters<typeof timeSince>[0]
|
|
27
|
+
export default ({ since, render }: {
|
|
28
|
+
since: Parameters<typeof timeSince>[0],
|
|
29
|
+
render?: (dateInfo: TDateInfo) => ComponentChild
|
|
26
30
|
}) => {
|
|
27
31
|
|
|
28
|
-
const [
|
|
32
|
+
const [time, setTime] = React.useState<TDateInfo | null>( timeSince(since) );
|
|
29
33
|
|
|
30
34
|
React.useEffect(() => {
|
|
31
35
|
|
|
32
|
-
const textUpdate = setInterval(() =>
|
|
36
|
+
const textUpdate = setInterval(() => setTime(timeSince(since)), 10000);
|
|
33
37
|
return () => clearInterval(textUpdate);
|
|
34
38
|
|
|
35
39
|
}, []);
|
|
36
40
|
|
|
37
|
-
return <>{text}</>;
|
|
41
|
+
return <>{time === null ? "?" : render ? render(time) : time.text}</>;
|
|
38
42
|
|
|
39
43
|
}
|
|
@@ -44,7 +44,7 @@ export type Props = {
|
|
|
44
44
|
----------------------------------*/
|
|
45
45
|
export default ({
|
|
46
46
|
// Decoration
|
|
47
|
-
icon, prefix, suffix, iconR, required,
|
|
47
|
+
icon, prefix, suffix, iconR, required, size,
|
|
48
48
|
// State
|
|
49
49
|
inputRef, errors,
|
|
50
50
|
// Behavior
|
|
@@ -142,6 +142,8 @@ export default ({
|
|
|
142
142
|
className += ' empty';
|
|
143
143
|
if (focus)
|
|
144
144
|
className += ' focus';
|
|
145
|
+
if (size !== undefined)
|
|
146
|
+
className += ' ' + size;
|
|
145
147
|
if (errors?.length)
|
|
146
148
|
className += ' error';
|
|
147
149
|
|
|
@@ -85,13 +85,8 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
|
|
|
85
85
|
// WARNING: Don"t try to play with pages here, since the object will not be updated
|
|
86
86
|
// If needed to play with pages, do it in the setPages callback below
|
|
87
87
|
// Unchanged path
|
|
88
|
-
if (request.path === currentRequest.path) {
|
|
89
|
-
|
|
90
|
-
// Scroll to component
|
|
91
|
-
if (request.hash) {
|
|
92
|
-
scrollToElement(request.hash);
|
|
93
|
-
}
|
|
94
|
-
|
|
88
|
+
if (request.path === currentRequest.path && request.hash !== currentRequest.hash) {
|
|
89
|
+
scrollToElement(request.hash);
|
|
95
90
|
return;
|
|
96
91
|
}
|
|
97
92
|
|
|
@@ -110,7 +105,14 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
|
|
|
110
105
|
}
|
|
111
106
|
|
|
112
107
|
// Fetch API data to hydrate the page
|
|
113
|
-
|
|
108
|
+
let newData;
|
|
109
|
+
try {
|
|
110
|
+
newData = await newpage.fetchData();
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(LogPrefix, "Unable to fetch data:", error);
|
|
113
|
+
clientRouter.setLoading(false);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
114
116
|
|
|
115
117
|
// Add page container
|
|
116
118
|
setPages( pages => {
|
|
File without changes
|
|
@@ -163,7 +163,7 @@ export default class ApiClient implements ApiClientService {
|
|
|
163
163
|
|
|
164
164
|
return data;
|
|
165
165
|
|
|
166
|
-
})
|
|
166
|
+
})
|
|
167
167
|
|
|
168
168
|
// Errors will be catched in the caller
|
|
169
169
|
|
|
@@ -229,14 +229,20 @@ export default class ApiClient implements ApiClientService {
|
|
|
229
229
|
|
|
230
230
|
})
|
|
231
231
|
.catch((e: AxiosError) => {
|
|
232
|
-
|
|
232
|
+
|
|
233
233
|
if (e.response !== undefined) {
|
|
234
234
|
|
|
235
|
+
// Transmiss error
|
|
235
236
|
console.warn(`[api] Failure:`, e);
|
|
236
|
-
|
|
237
|
+
const error = viaHttpCode(
|
|
237
238
|
e.response.status || 500,
|
|
238
239
|
e.response.data
|
|
239
240
|
);
|
|
241
|
+
|
|
242
|
+
// API Error hook
|
|
243
|
+
this.app.handleError(error, e.response.status);
|
|
244
|
+
|
|
245
|
+
throw error;
|
|
240
246
|
|
|
241
247
|
// Erreur réseau: l'utilisateur n'ets probablement plus connecté à internet
|
|
242
248
|
} else {
|
package/src/common/data/dates.ts
CHANGED
|
@@ -5,19 +5,35 @@ const timeAgo = new TimeAgo('en-US')
|
|
|
5
5
|
|
|
6
6
|
import dayjs from 'dayjs';
|
|
7
7
|
|
|
8
|
-
export
|
|
8
|
+
export type TDateInfo = {
|
|
9
|
+
isPast: boolean,
|
|
10
|
+
delta: number,
|
|
11
|
+
text: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const timeSince = (date: Date | number | string): TDateInfo | null => {
|
|
9
15
|
|
|
10
16
|
if (date === undefined)
|
|
11
|
-
return
|
|
17
|
+
return null;
|
|
12
18
|
|
|
13
19
|
// Timeago ne prend que des dates et des timestamp
|
|
14
20
|
if (typeof date === 'string') {
|
|
15
21
|
date = Date.parse(date);
|
|
16
22
|
if (isNaN(date))
|
|
17
|
-
return
|
|
23
|
+
return null;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
// Get metas
|
|
27
|
+
const now = Date.now()
|
|
28
|
+
const timestamp = date instanceof Date ? date.getTime() : date;
|
|
29
|
+
const deltaSeconds = Math.abs( Math.round( (now - timestamp) / 1000 ));
|
|
30
|
+
const isPast = now > timestamp;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
text: timeAgo.format(date),
|
|
34
|
+
isPast,
|
|
35
|
+
delta: deltaSeconds
|
|
36
|
+
};
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
export const tempsRelatif = (time: number, nbChiffresInit?: number) => {
|
|
@@ -117,4 +117,21 @@ export const chemin = {
|
|
|
117
117
|
valA[ brancheVal ] = val;
|
|
118
118
|
|
|
119
119
|
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const groupBy = <TObj extends TObjetDonnees>(
|
|
123
|
+
items: TObj[],
|
|
124
|
+
key: keyof TObj
|
|
125
|
+
): {[key: string]: TObj[]} => {
|
|
126
|
+
|
|
127
|
+
const grouped: {[key: string]: TObj[]} = {};
|
|
128
|
+
|
|
129
|
+
for (const item of items) {
|
|
130
|
+
const indexValue = item[key] as any;
|
|
131
|
+
if (grouped[ indexValue ] === undefined)
|
|
132
|
+
grouped[ indexValue ] = [];
|
|
133
|
+
grouped[ indexValue ].push(item);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return grouped;
|
|
120
137
|
}
|