5htp-core 0.3.6 → 0.3.7

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 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.6",
4
+ "version": "0.3.7",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -12,6 +12,7 @@
12
12
  // Layout
13
13
  position: relative;
14
14
  gap: @spacing;
15
+ min-width: 0/*fit-content*/; // Fit content, even when flexbox parent width < content width
15
16
 
16
17
  // Dimensions
17
18
  font-size: 1em;
@@ -62,12 +63,16 @@
62
63
 
63
64
  > .label {
64
65
  text-align: left;
65
- white-space: nowrap; // Autrement, si plusieurs mots, affiché sur plusieurs ligne
66
66
  gap: @spacing / 2;
67
67
  font-size: 1rem;
68
68
  line-height: 1.5em;
69
69
  z-index: 1; // Make the label on top of ::before for example
70
70
 
71
+ // Handle overflow
72
+ white-space: nowrap;
73
+ text-overflow: ellipsis;
74
+ overflow: hidden;
75
+
71
76
  li > & {
72
77
  flex: 1;
73
78
  }
@@ -1,3 +1,9 @@
1
+ .table {
2
+ &.card {
3
+ padding: 1em 0;
4
+ }
5
+ }
6
+
1
7
  table {
2
8
 
3
9
  width: 100%;
@@ -135,6 +135,8 @@ html {
135
135
  font-size: 1em;
136
136
  padding: 0;
137
137
  margin: 0;
138
+ // Avoids flexbox childrens to overflw parent
139
+ min-width: 0;
138
140
 
139
141
  &, &:hover, &:focus {
140
142
  -webkit-tap-highlight-color: transparent;
@@ -30,9 +30,13 @@
30
30
  display: flex;
31
31
  flex-wrap: nowrap;
32
32
 
33
- > * {
34
- // Empêche les enfants de dépasser du conteneur
35
- min-width: 0;
33
+ &.scrollable {
34
+
35
+ overflow: auto;
36
+
37
+ > * {
38
+ min-width: fit-content;
39
+ }
36
40
  }
37
41
 
38
42
  // Avec justify-content: center, les premiers élements sont cachés
@@ -126,6 +130,20 @@
126
130
  &.sep-1 > * + * { border-top: solid 1px var(--cLine); }
127
131
 
128
132
  > .col-1 { align-self: stretch; }
133
+
134
+ &.unibody {
135
+
136
+ gap: 0;
137
+
138
+ > :not(:first-child) {
139
+ border-top-left-radius: 0;
140
+ border-top-right-radius: 0;
141
+ }
142
+ > :not(:last-child) {
143
+ border-bottom-left-radius: 0;
144
+ border-bottom-right-radius: 0;
145
+ }
146
+ }
129
147
  }
130
148
 
131
149
  .row,
@@ -11,6 +11,9 @@ import type { Schema } from '@common/validation';
11
11
  import type { TValidationResult } from '@common/validation/schema';
12
12
  import useContext from '@/client/context';
13
13
 
14
+ // Exports
15
+ export type { TValidationResult, TSchemaData } from '@common/validation/schema';
16
+
14
17
  /*----------------------------------
15
18
  - TYPES
16
19
  ----------------------------------*/
@@ -23,7 +26,7 @@ type TFormOptions<TFormData extends {}> = {
23
26
  }
24
27
  }
25
28
 
26
- type FieldsAttrs<TFormData extends {}> = {
29
+ export type FieldsAttrs<TFormData extends {}> = {
27
30
  [fieldName in keyof TFormData]: {}
28
31
  }
29
32
 
@@ -23,7 +23,7 @@ export type Props<TRow> = {
23
23
  columns: (row: TRow, rows: TRow[], index: number) => TColumn[];
24
24
 
25
25
  setData?: (rows: TRow[]) => void,
26
- empty?: ComponentChild,
26
+ empty?: ComponentChild | false,
27
27
  className?: string,
28
28
 
29
29
  actions?: TAction<TRow>[]
@@ -40,15 +40,17 @@ export type TColumn = {
40
40
  - COMPOSANTS
41
41
  ----------------------------------*/
42
42
  export default function Liste<TRow extends TDonneeInconnue>({
43
- data: rows, setData, empty ,
43
+ data: rows, setData, empty,
44
44
  columns, actions, ...props
45
45
  }: Props<TRow>) {
46
46
 
47
47
  if (rows.length === 0)
48
- return (
49
- <div class="card pd-2 col al-center">
50
- <i src="meh-rolling-eyes" class="xl" />
51
- Uh ... No rows here.
48
+ return empty === false ? null : (
49
+ <div class="pd-2 col al-center">
50
+ {empty || <>
51
+ <i src="meh-rolling-eyes" class="xl" />
52
+ Uh ... No rows here.
53
+ </>}
52
54
  </div>
53
55
  );
54
56
 
@@ -159,16 +159,20 @@ export default class ClientRouter<
159
159
  public url = (path: string, params: {} = {}, absolute: boolean = true) =>
160
160
  buildUrl(path, params, this.config.domains, absolute);
161
161
 
162
- public go( url: string ) {
162
+ public go( url: string, opt: {
163
+ newTab?: boolean
164
+ } = {}) {
163
165
 
164
166
  url = this.url(url, {}, false);
165
167
 
168
+ if (opt.newTab)
169
+ window.open(url)
166
170
  // Same domain = history url replacement
167
- if (url[0] === '/')
171
+ else if (url[0] === '/')
168
172
  history?.replace( url );
169
173
  // Different domain = hard navigation
170
174
  else
171
- windows.location.href = url;
175
+ window.location.href = url;
172
176
  }
173
177
 
174
178
  /*----------------------------------
@@ -274,7 +278,11 @@ export default class ClientRouter<
274
278
  return route;
275
279
  }
276
280
 
277
- public error(code: number, options: TRoute["options"], renderer: TFrontRenderer<{}, { message: string }>) {
281
+ public error(
282
+ code: number,
283
+ options: Partial<TRoute["options"]>,
284
+ renderer: TFrontRenderer<{}, { message: string }>
285
+ ) {
278
286
 
279
287
  // Automatic layout form the nearest _layout folder
280
288
  const layout = getLayout('Error ' + code, options);
@@ -345,7 +353,7 @@ export default class ClientRouter<
345
353
 
346
354
  } catch (e) {
347
355
  console.error(`Failed to fetch the route ${route.chunk}`, e);
348
- this.app.handleError(new Error("Failed to load content. Please make sure you're connected to Internet."));
356
+ this.app.handleError(new Error("Failed to load content. Please reload the page and try again."));
349
357
  throw e;
350
358
  }
351
359
 
@@ -12,7 +12,7 @@ import ApiClientService, {
12
12
  TApiFetchOptions, TFetcherList, TFetcherArgs, TFetcher,
13
13
  TDataReturnedByFetchers
14
14
  } from '@common/router/request/api';
15
- import { instancierViaCode, NetworkError } from '@common/errors';
15
+ import { viaHttpCode, NetworkError } from '@common/errors';
16
16
  import type ClientApplication from '@client/app';
17
17
 
18
18
  import { toMultipart } from './multipart';
@@ -233,7 +233,7 @@ export default class ApiClient implements ApiClientService {
233
233
  if (e.response !== undefined) {
234
234
 
235
235
  console.warn(`[api] Failure:`, e);
236
- throw instancierViaCode(
236
+ throw viaHttpCode(
237
237
  e.response.status || 500,
238
238
  e.response.data
239
239
  );
@@ -169,12 +169,13 @@ export class NetworkError extends Error {
169
169
  }
170
170
 
171
171
 
172
- export const instancierViaCode = (
172
+ export const viaHttpCode = (
173
173
  code: number,
174
174
  message?: string | TListeErreursSaisie,
175
175
  details?: TDetailsErreur
176
176
  ): CoreError => {
177
177
 
178
+ // TODO: more reliablme detection of form errors
178
179
  if (typeof message === 'object')
179
180
  return new InputErrorSchema(message, details);
180
181
 
@@ -33,6 +33,9 @@ export type TValidationResult<TFields extends TSchemaFields> = {
33
33
  erreurs: TListeErreursSaisie
34
34
  }
35
35
 
36
+ export type TSchemaData<TSchema extends Schema<{}>> =
37
+ TValidationResult<TSchema["fields"]>
38
+
36
39
  export type TValidatedData<TFields extends TSchemaFields> = {
37
40
  // For each field, the values returned by validator.validate()
38
41
  [name in keyof TFields]: ReturnType<TFields[name]["validate"]>
@@ -22,7 +22,6 @@ import Validator, { TValidator } from './validator'
22
22
 
23
23
  // Components
24
24
  import NumberInput from '@client/components/input/Number';
25
- import Dropdown from '@client/components/dropdown.old';
26
25
 
27
26
  /*----------------------------------
28
27
  - TYPES
@@ -146,11 +145,13 @@ export default class SchemaValidators {
146
145
  /*----------------------------------
147
146
  - CHAINES
148
147
  ----------------------------------*/
149
- public string = ({ min, max, ...opts }: TValidator<string> & {
148
+ public string = ({ min, max, include, ...opts }: TValidator<string> & {
150
149
  min?: number,
151
- max?: number
150
+ max?: number,
151
+ include?: string
152
152
  } = {}) => new Validator<string>('string', (val, input, output, corriger?: boolean) => {
153
153
 
154
+ // Check type
154
155
  if (val === '')
155
156
  return undefined;
156
157
  else if (typeof val === 'number')
@@ -158,14 +159,14 @@ export default class SchemaValidators {
158
159
  else if (typeof val !== 'string')
159
160
  throw new InputError("This value must be a string.");
160
161
 
161
- // Espaces blancs
162
+ // Whitespace
162
163
  val = trim(val);
163
164
 
164
- // Taille min
165
+ // Min size
165
166
  if (min !== undefined && val.length < min)
166
167
  throw new InputError(`Must be at least ` + min + ' characters');
167
168
 
168
- // Taille max
169
+ // Max size
169
170
  if (max !== undefined && val.length > max)
170
171
  if (corriger)
171
172
  val = val.substring(0, max);
@@ -193,8 +194,6 @@ export default class SchemaValidators {
193
194
  if (opts.normalize !== undefined)
194
195
  val = normalizeUrl(val, opts.normalize);
195
196
 
196
- console.log("@@@@@@@@@@@@@NORMALISZE URL", opts.normalize, val);
197
-
198
197
  return val;
199
198
  }, opts)
200
199
 
@@ -170,14 +170,19 @@ export default class SQL extends Service<Config, Hooks, Application, Services> {
170
170
  return query;
171
171
  }
172
172
 
173
- public esc(data: any) {
173
+ public esc( data: any, forStorage: boolean = false ) {
174
174
 
175
175
  // JSON object
176
176
  // TODO: do it via datatypes.ts
177
177
  if (typeof data === 'object' && data !== null) {
178
178
 
179
+ // Object: stringify in JSON
179
180
  if (data.constructor.name === "Object")
180
181
  data = safeStringify(data);
182
+ // Array: if for storage, reparate items with a comma
183
+ else if (forStorage && Array.isArray( data )) {
184
+ data = data.join(',')
185
+ }
181
186
  }
182
187
 
183
188
  return mysql.escape(data);
@@ -474,7 +479,7 @@ export default class SQL extends Service<Config, Hooks, Application, Services> {
474
479
  const values: string[] = [];
475
480
  for (const col of colNames)
476
481
  if (col in entry)
477
- values.push( this.esc( entry[col] ));
482
+ values.push( this.esc( entry[col], true));
478
483
  else
479
484
  values.push("DEFAULT");
480
485
 
@@ -528,7 +533,7 @@ export default class SQL extends Service<Config, Hooks, Application, Services> {
528
533
  const { '*': updateAll, ...customValuesToUpdate } = colsToUpdate;
529
534
 
530
535
  for (const colKey in customValuesToUpdate)
531
- valuesToUpdate[ colKey ] = this.esc(customValuesToUpdate[ colKey ]);
536
+ valuesToUpdate[ colKey ] = this.esc(customValuesToUpdate[ colKey ], true);
532
537
 
533
538
  if (updateAll)
534
539
  valuesNamesToUpdate = Object.keys(table.colonnes);//table.columnNamesButPk;
@@ -5,6 +5,7 @@
5
5
  // Npm
6
6
  import sharp from 'sharp';
7
7
  import fs from 'fs-extra';
8
+ import got, { Method, Options } from 'got';
8
9
 
9
10
  // Node
10
11
  import request from 'request';
@@ -12,6 +13,10 @@ import request from 'request';
12
13
  // Core: general
13
14
  import type { Application } from '@server/app';
14
15
  import Service, { AnyService } from '@server/app/service';
16
+ import { viaHttpCode } from '@common/errors';
17
+
18
+ // Local
19
+ import type RouterService from '../router';
15
20
  import type DisksManager from '../disks';
16
21
  import type FsDriver from '../disks/driver';
17
22
 
@@ -29,7 +34,8 @@ export type Hooks = {
29
34
  }
30
35
 
31
36
  export type Services = {
32
- disks: DisksManager
37
+ disks: DisksManager,
38
+ router?: RouterService
33
39
  }
34
40
 
35
41
  /*----------------------------------
@@ -89,7 +95,63 @@ export default class FetchService extends Service<Config, Hooks, Application, Se
89
95
  }
90
96
 
91
97
  /*----------------------------------
92
- - ACTIONS
98
+ - EXTERNAL API REQUESTS
99
+ ----------------------------------*/
100
+
101
+ public post(
102
+ url: string,
103
+ data: {[k: string]: any},
104
+ options: {} = {}
105
+ ) {
106
+
107
+ return this.request('POST', url, data, options);
108
+
109
+ }
110
+
111
+ public async request(
112
+ method: Method,
113
+ url: string,
114
+ data: {[k: string]: any},
115
+ options: Options = {}
116
+ ) {
117
+
118
+ // Parse url if router service is provided
119
+ if (this.services.router !== undefined)
120
+ url = this.services.router.url(url);
121
+
122
+ // Send request
123
+ const res = await got(url, {
124
+ throwHttpErrors: false,
125
+ headers: {
126
+ 'Accept': 'application/json',
127
+ },
128
+ method,
129
+ ...(method === 'GET' ? {
130
+ searchParams: data
131
+ } : {
132
+ json: data
133
+ })
134
+ })
135
+
136
+ // Handle errors
137
+ if (res.statusCode !== 200) {
138
+
139
+ // Instanciate error from HTTP code
140
+ const error = viaHttpCode( res.statusCode, res.body );
141
+ if (error)
142
+ throw error;
143
+
144
+ // Not catched via viaHttpCode
145
+ console.log("RESPONSE", res.body);
146
+ throw new Error("Error while contacting the API");
147
+ }
148
+
149
+ // Format & return response
150
+ return JSON.parse( res.body );
151
+ }
152
+
153
+ /*----------------------------------
154
+ - IMAGES
93
155
  ----------------------------------*/
94
156
 
95
157
  public toBuffer( uri: string ): Promise<Buffer> {
@@ -93,7 +93,7 @@ export default abstract class UsersManagementService<
93
93
  ----------------------------------*/
94
94
 
95
95
  public abstract login( ...args: any[] ): Promise<{ user: TUser, token: string }>;
96
- public abstract decodeSession( jwt: TJwtSession, req: THttpRequest ): Promise<TUser>;
96
+ public abstract decodeSession( jwt: TJwtSession, req: THttpRequest ): Promise<TUser | null>;
97
97
 
98
98
  protected abstract displayName(user?: TUser | null): string;
99
99
  protected abstract displaySessionName(session: TJwtSession): string;
@@ -134,9 +134,17 @@ export default abstract class UsersManagementService<
134
134
  // Deserialize full user data
135
135
  this.config.debug && console.log(LogPrefix, `Deserialize user ${sessionName}`);
136
136
  const user = await this.decodeSession(session, req);
137
+
138
+ // User not found
139
+ if (user === null)
140
+ return null;
141
+
137
142
  this.config.debug && console.log(LogPrefix, `Deserialized user ${sessionName}:`, this.displayName(user));
138
143
 
139
- return user;
144
+ return {
145
+ ...user,
146
+ _token: token
147
+ };
140
148
  }
141
149
 
142
150
  public unauthorized( req: THttpRequest ) {
@@ -1,37 +0,0 @@
1
- /*----------------------------------
2
- - DEPENDANCES
3
- ----------------------------------*/
4
-
5
- // Npm
6
- import React from 'react';
7
-
8
- // Core
9
- import { Router } from '@app';
10
- import { Button } from '@client/components';
11
-
12
- // App
13
- import useHeader from '@client/pages/useHeader';
14
-
15
- /*----------------------------------
16
- - CONTROLEUR
17
- ----------------------------------*/
18
- Router.error( 400, ({ message, modal }) => {
19
-
20
- useHeader({
21
- title: 'Bad request',
22
- subtitle: message
23
- });
24
-
25
- return (
26
- <div class="card w-3-4 col al-center pd-2">
27
-
28
- <i src="times-circle" class="fg error xxl" />
29
-
30
- <h1>Bad Request</h1>
31
-
32
- <p>{message}</p>
33
-
34
- <Button type="primary" link="/">Go Home</Button>
35
- </div>
36
- )
37
- });
@@ -1,49 +0,0 @@
1
- /*----------------------------------
2
- - DEPENDANCES
3
- ----------------------------------*/
4
-
5
- // Npm
6
- import React from 'react';
7
-
8
- // Core
9
- import { Router } from '@app';
10
- import { Button } from '@client/components';
11
-
12
- // App
13
- import useHeader from '@client/pages/useHeader';
14
-
15
- /*----------------------------------
16
- - RESSOURCES
17
- ----------------------------------*/
18
-
19
- /*----------------------------------
20
- - CONTROLEUR
21
- ----------------------------------*/
22
- Router.error( 401, ({ message, request, page }) => {
23
-
24
- request.response?.redirect('https://becrosspath.com');
25
-
26
- useHeader({
27
- title: 'Authentication Required',
28
- subtitle: message
29
- });
30
-
31
- React.useEffect(() => {
32
-
33
- page?.go('/');
34
-
35
- }, []);
36
-
37
- return (
38
- <div class="card w-3-4 col al-center pd-2">
39
-
40
- <i src="times-circle" class="fg error xxl" />
41
-
42
- <h1>Authentication Required</h1>
43
-
44
- <p>{message}</p>
45
-
46
- <Button type="primary" link="/">Go Home</Button>
47
- </div>
48
- )
49
- });
@@ -1,37 +0,0 @@
1
- /*----------------------------------
2
- - DEPENDANCES
3
- ----------------------------------*/
4
-
5
- // Npm
6
- import React from 'react';
7
-
8
- // Core
9
- import { Router } from '@app';
10
- import { Button } from '@client/components';
11
-
12
- // App
13
- import useHeader from '@client/pages/useHeader';
14
-
15
- /*----------------------------------
16
- - CONTROLEUR
17
- ----------------------------------*/
18
- Router.error( 403, ({ message, modal }) => {
19
-
20
- useHeader({
21
- title: 'Access Denied.',
22
- subtitle: message
23
- });
24
-
25
- return (
26
- <div class="card w-3-4 col al-center pd-2">
27
-
28
- <i src="times-circle" class="fg error xxl" />
29
-
30
- <h1>Access Denied.</h1>
31
-
32
- <p>{message}</p>
33
-
34
- <Button type="primary" link="/">Go Home</Button>
35
- </div>
36
- )
37
- });
@@ -1,37 +0,0 @@
1
- /*----------------------------------
2
- - DEPENDANCES
3
- ----------------------------------*/
4
-
5
- // Npm
6
- import React from 'react';
7
-
8
- // Core
9
- import { Router } from '@app';
10
- import { Button } from '@client/components';
11
-
12
- // App
13
- import useHeader from '@client/pages/useHeader';
14
-
15
- /*----------------------------------
16
- - CONTROLEUR
17
- ----------------------------------*/
18
- Router.error( 404, ({ message, modal }) => {
19
-
20
- useHeader({
21
- title: 'Page Not Found',
22
- subtitle: message
23
- });
24
-
25
- return (
26
- <div class="card w-3-4 col al-center pd-2">
27
-
28
- <i src="times-circle" class="fg error xxl" />
29
-
30
- <h1>Page Not Found</h1>
31
-
32
- <p>{message}</p>
33
-
34
- <Button type="primary" link="/">Go Home</Button>
35
- </div>
36
- )
37
- });
@@ -1,37 +0,0 @@
1
- /*----------------------------------
2
- - DEPENDANCES
3
- ----------------------------------*/
4
-
5
- // Npm
6
- import React from 'react';
7
-
8
- // Core
9
- import { Router } from '@app';
10
- import { Button } from '@client/components';
11
-
12
- // App
13
- import useHeader from '@client/pages/useHeader';
14
-
15
- /*----------------------------------
16
- - CONTROLEUR
17
- ----------------------------------*/
18
- Router.error( 500, ({ message }) => {
19
-
20
- useHeader({
21
- title: 'Technical Error',
22
- subtitle: message
23
- });
24
-
25
- return (
26
- <div class="card w-3-4 col al-center pd-2">
27
-
28
- <i src="times-circle" class="fg error xxl" />
29
-
30
- <h1>Technical Error</h1>
31
-
32
- <p>{message}</p>
33
-
34
- <Button type="primary" link="/">Go Home</Button>
35
- </div>
36
- )
37
- });