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.
Files changed (46) hide show
  1. package/package.json +5 -3
  2. package/src/client/assets/css/components/button.less +6 -10
  3. package/src/client/assets/css/components/card.less +1 -7
  4. package/src/client/assets/css/text/icons.less +8 -5
  5. package/src/client/assets/css/text/text.less +2 -3
  6. package/src/client/assets/css/theme.less +1 -1
  7. package/src/client/assets/css/utils/layouts.less +4 -0
  8. package/src/client/components/Form.ts +2 -1
  9. package/src/client/components/Select/ChoiceElement.tsx +20 -6
  10. package/src/client/components/Select/ChoiceSelector.tsx +3 -2
  11. package/src/client/components/Select/index.tsx +53 -21
  12. package/src/client/components/containers/Popover/index.tsx +4 -1
  13. package/src/client/components/data/Time.tsx +16 -12
  14. package/src/client/components/inputv3/base.tsx +1 -0
  15. package/src/client/components/inputv3/index.tsx +3 -1
  16. package/src/client/services/router/components/router.tsx +10 -8
  17. package/src/client/services/router/index.tsx +0 -0
  18. package/src/client/services/router/request/api.ts +9 -3
  19. package/src/common/data/dates.ts +20 -4
  20. package/src/common/data/objets.ts +17 -0
  21. package/src/common/router/index.ts +1 -1
  22. package/src/common/validation/schema.ts +85 -91
  23. package/src/common/validation/validator.ts +4 -6
  24. package/src/common/validation/validators.ts +74 -72
  25. package/src/server/{services → app/container}/console/index.ts +52 -16
  26. package/src/server/app/container/index.ts +54 -0
  27. package/src/server/app/index.ts +22 -22
  28. package/src/server/app/service/index.ts +36 -11
  29. package/src/server/app.tsconfig.json +1 -1
  30. package/src/server/context.ts +1 -1
  31. package/src/server/index.ts +2 -10
  32. package/src/server/services/{users → auth}/index.ts +1 -1
  33. package/src/server/services/database/connection.ts +3 -1
  34. package/src/server/services/database/index.ts +34 -24
  35. package/src/server/services/database/model.ts +28 -0
  36. package/src/server/services/fetch/index.ts +4 -3
  37. package/src/server/services/router/index.ts +4 -1
  38. package/src/server/services/schema/request.ts +4 -11
  39. package/src/server/services/socket/index.ts +1 -1
  40. package/src/server/services/console/service.json +0 -6
  41. /package/src/server/{services → app/container}/console/html.ts +0 -0
  42. /package/src/server/services/{users → auth}/old.ts +0 -0
  43. /package/src/server/services/{users → auth}/router/index.ts +0 -0
  44. /package/src/server/services/{users → auth}/router/request.ts +0 -0
  45. /package/src/server/services/{users → auth}/router/service.json +0 -0
  46. /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.7",
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.1"
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: 600;
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: fade(@cDark, 90%);
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
- &.selected {
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
- width: @sizeComponent;
38
- flex: 0 0 @sizeComponent;
39
- height: @sizeComponent;
40
- line-height: @sizeComponent;
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: @spacing * 2 0;
183
+ margin: 1em 0;
185
184
  }
186
185
 
187
186
  h2 {
@@ -32,7 +32,7 @@
32
32
  // Flags
33
33
  @bg: @theme[background];
34
34
  @bgActive: if( @isLight,
35
- @bg - #111,
35
+ @bg - #040404,
36
36
  @bg + #111,
37
37
  );
38
38
  @fg: @theme[foreground];
@@ -37,6 +37,10 @@
37
37
  > * {
38
38
  min-width: fit-content;
39
39
  }
40
+
41
+ &.menu {
42
+ min-height: @sizeComponent;
43
+ }
40
44
  }
41
45
 
42
46
  // Avec justify-content: center, les premiers élements sont cachés
@@ -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.validate(allData, allData, {}, {
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: boolean
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 isCurrent ? (
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 SelectedItems = ( enableSearch ? currentList : choices ).map( choice => (
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: '300px' }}>
171
+ <div class="card col" style={{ width: '200px' }}>
160
172
 
161
173
  <div class="col">
162
174
 
163
- {SelectedItems.length !== 0 && (
164
- <div class="row wrap">
165
- {SelectedItems}
166
- </div>
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
- {title} {(multiple && currentList.length > 0)
177
- ? <span class="badge s bg accent">{currentList.length}</span>
178
- : null
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
- {SelectedItems}
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: [boolean, StateUpdater<boolean>],
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: IMPORTATIONS
13
+ - TYPES
13
14
  ----------------------------------*/
15
+ export type { TDateInfo } from '@common/data/dates';
14
16
 
15
-
16
-
17
- /*----------------------------------
18
- - TYPES: DECLARATIONS
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 [text, setDisplay] = React.useState(timeSince(since));
32
+ const [time, setTime] = React.useState<TDateInfo | null>( timeSince(since) );
29
33
 
30
34
  React.useEffect(() => {
31
35
 
32
- const textUpdate = setInterval(() => setDisplay(timeSince(since)), 10000);
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
  }
@@ -18,6 +18,7 @@ export type InputBaseProps<TValue> = {
18
18
  title: string, // Now mandatory
19
19
  required?: boolean,
20
20
  errors?: string[],
21
+ size?: TComponentSize,
21
22
 
22
23
  value: TValue,
23
24
  onChange?: (value: TValue) => void,
@@ -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
- const newData = await newpage.fetchData();
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
- throw viaHttpCode(
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 {
@@ -5,19 +5,35 @@ const timeAgo = new TimeAgo('en-US')
5
5
 
6
6
  import dayjs from 'dayjs';
7
7
 
8
- export const timeSince = (date: Date | number | string) => {
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 'Inconnu';
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
- return timeAgo.format(date);
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
  }
@@ -15,7 +15,7 @@ import type {
15
15
  TRouteHttpMethod
16
16
  } from '@server/services/router';
17
17
 
18
- import type { TUserRole } from '@server/services/users';
18
+ import type { TUserRole } from '@server/services/auth';
19
19
 
20
20
  import type { TAppArrowFunction } from '@common/app';
21
21