5htp-core 0.6.1-6 → 0.6.2
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/client/components/File/index.tsx +4 -19
- package/client/components/Input.tsx +2 -2
- package/client/services/router/request/api.ts +1 -2
- package/client/services/router/request/multipart.ts +0 -5
- package/common/router/index.ts +1 -1
- package/common/router/request/api.ts +1 -3
- package/package.json +1 -1
- package/server/app/index.ts +2 -0
- package/server/services/router/http/multipart.ts +5 -22
- package/server/services/router/request/index.ts +1 -2
- package/{common → server/services/router/request}/validation/validators.ts +3 -4
- package/server/services/schema/index.ts +2 -4
- package/server/services/schema/request.ts +1 -1
- package/types/icons.d.ts +1 -1
- package/client/components/File/FileToUpload.ts +0 -34
- package/client/components/Form.ts +0 -275
- /package/{common → server/services/router/request}/validation/index.ts +0 -0
- /package/{common → server/services/router/request}/validation/schema.ts +0 -0
- /package/{common → server/services/router/request}/validation/validator.ts +0 -0
|
@@ -12,16 +12,12 @@ import Button, { Props as BtnProps } from '@client/components/Button';
|
|
|
12
12
|
import { InputWrapper } from '../utils';
|
|
13
13
|
import useContext from '@/client/context';
|
|
14
14
|
|
|
15
|
-
// specific
|
|
16
|
-
import FileToUpload from './FileToUpload';
|
|
17
|
-
|
|
18
15
|
// Ressources
|
|
19
16
|
import './index.less';
|
|
20
17
|
|
|
21
18
|
/*----------------------------------
|
|
22
19
|
- OUTILS
|
|
23
20
|
----------------------------------*/
|
|
24
|
-
export { default as FileToUpload } from './FileToUpload';
|
|
25
21
|
|
|
26
22
|
export const createImagePreview = (file: Blob) => new Promise((resolve, reject) => {
|
|
27
23
|
|
|
@@ -44,15 +40,6 @@ export const createImagePreview = (file: Blob) => new Promise((resolve, reject)
|
|
|
44
40
|
reader.readAsDataURL(file);
|
|
45
41
|
});
|
|
46
42
|
|
|
47
|
-
// Instanciate FileToUpload from browser side File
|
|
48
|
-
const normalizeFile = (file: File) => new FileToUpload({
|
|
49
|
-
name: file.name,
|
|
50
|
-
type: file.type,
|
|
51
|
-
size: file.size,
|
|
52
|
-
data: file,
|
|
53
|
-
//original: file
|
|
54
|
-
})
|
|
55
|
-
|
|
56
43
|
/*----------------------------------
|
|
57
44
|
- TYPES
|
|
58
45
|
----------------------------------*/
|
|
@@ -61,7 +48,7 @@ export type Props = {
|
|
|
61
48
|
|
|
62
49
|
// Input
|
|
63
50
|
title: string,
|
|
64
|
-
value?: string |
|
|
51
|
+
value?: string | File, // string = already registered
|
|
65
52
|
|
|
66
53
|
// Display
|
|
67
54
|
emptyText?: ComponentChild,
|
|
@@ -70,7 +57,7 @@ export type Props = {
|
|
|
70
57
|
button?: boolean | BtnProps,
|
|
71
58
|
|
|
72
59
|
// Actions
|
|
73
|
-
onChange: (file:
|
|
60
|
+
onChange: (file: File | undefined) => void
|
|
74
61
|
remove?: () => Promise<void>,
|
|
75
62
|
}
|
|
76
63
|
|
|
@@ -115,9 +102,7 @@ export default (props: Props) => {
|
|
|
115
102
|
const selectedfile = fileSelectEvent.target.files[0] as File;
|
|
116
103
|
if (selectedfile) {
|
|
117
104
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
onChange(fileToUpload);
|
|
105
|
+
onChange(selectedfile);
|
|
121
106
|
}
|
|
122
107
|
}
|
|
123
108
|
|
|
@@ -125,7 +110,7 @@ export default (props: Props) => {
|
|
|
125
110
|
|
|
126
111
|
// Image = decode & display preview
|
|
127
112
|
if (file !== undefined && typeof file === 'object' && file.type.startsWith('image/'))
|
|
128
|
-
createImagePreview(file
|
|
113
|
+
createImagePreview(file).then(setPreviewUrl);
|
|
129
114
|
else
|
|
130
115
|
setPreviewUrl(undefined);
|
|
131
116
|
|
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
|
|
14
14
|
// Core libs
|
|
15
15
|
import { InputBaseProps, useMantineInput } from './utils';
|
|
16
|
-
import { default as Validator } from '../../
|
|
17
|
-
import type { SchemaValidators } from '@
|
|
16
|
+
import { default as Validator } from '../../server/services/router/request/validation/validator';
|
|
17
|
+
import type { SchemaValidators } from '@server/services/router/request/validation/validators';
|
|
18
18
|
|
|
19
19
|
/*----------------------------------
|
|
20
20
|
- TYPES
|
|
@@ -15,7 +15,6 @@ import ApiClientService, {
|
|
|
15
15
|
// Specific
|
|
16
16
|
import type { default as Router, Request } from '..';
|
|
17
17
|
import { toMultipart } from './multipart';
|
|
18
|
-
import FileToUpload from '@client/components/File/FileToUpload';
|
|
19
18
|
|
|
20
19
|
/*----------------------------------
|
|
21
20
|
- TYPES
|
|
@@ -226,7 +225,7 @@ export default class ApiClient implements ApiClientService {
|
|
|
226
225
|
|
|
227
226
|
// If file included in data, need to use multipart
|
|
228
227
|
// TODO: deep check
|
|
229
|
-
const hasFile = Object.values(data).some((value) => value instanceof
|
|
228
|
+
const hasFile = Object.values(data).some((value) => value instanceof File);
|
|
230
229
|
if (hasFile) {
|
|
231
230
|
// GET request = Can't send files
|
|
232
231
|
if (method === "GET")
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
// Core
|
|
6
6
|
import { TPostData } from '@common/router/request/api';
|
|
7
|
-
import { FileToUpload } from '@client/components/File';
|
|
8
7
|
|
|
9
8
|
/*----------------------------------
|
|
10
9
|
- TYPES
|
|
@@ -75,10 +74,6 @@ function convertRecursively(
|
|
|
75
74
|
}
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
// Exract the file object from value
|
|
79
|
-
if (typeof value === 'object' && value instanceof FileToUpload)
|
|
80
|
-
value = value.data;
|
|
81
|
-
|
|
82
77
|
if (isArray(value) || isJsonObject(value)) {
|
|
83
78
|
|
|
84
79
|
convertRecursively(value, options, formData, propName);
|
package/common/router/index.ts
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import type { HttpMethod } from '@server/services/router';
|
|
6
6
|
|
|
7
|
-
import type { FileToUpload } from '@client/components/File';
|
|
8
|
-
|
|
9
7
|
/*----------------------------------
|
|
10
8
|
- TYPES
|
|
11
9
|
----------------------------------*/
|
|
@@ -44,7 +42,7 @@ export type TApiFetchOptions = {
|
|
|
44
42
|
|
|
45
43
|
export type TPostData = TPostDataWithFile
|
|
46
44
|
|
|
47
|
-
export type TPostDataWithFile = { [key: string]: PrimitiveValue
|
|
45
|
+
export type TPostDataWithFile = { [key: string]: PrimitiveValue }
|
|
48
46
|
|
|
49
47
|
export type TPostDataWithoutFile = { [key: string]: PrimitiveValue }
|
|
50
48
|
|
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.6.
|
|
4
|
+
"version": "0.6.2",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
package/server/app/index.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import mime from 'mime-types';
|
|
7
7
|
|
|
8
8
|
// Core
|
|
9
|
-
import FileToUpload from '@client/components/File/FileToUpload';
|
|
10
9
|
import { InputError } from '@common/errors';
|
|
11
10
|
|
|
12
11
|
/*----------------------------------
|
|
@@ -82,7 +81,11 @@ export const traiterMultipart = (...canaux: any[]) => {
|
|
|
82
81
|
&&
|
|
83
82
|
donnee.data instanceof Buffer
|
|
84
83
|
){
|
|
85
|
-
donnee =
|
|
84
|
+
donnee = new File(donnee.data, donnee.name, {
|
|
85
|
+
type: donnee.mimetype,
|
|
86
|
+
lastModified: Date.now(),
|
|
87
|
+
//size: donnee.size,
|
|
88
|
+
});
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
brancheA[ cle ] = donnee;
|
|
@@ -91,24 +94,4 @@ export const traiterMultipart = (...canaux: any[]) => {
|
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
return sortie;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const normalizeFile = (file: UploadedFile) => {
|
|
97
|
-
|
|
98
|
-
const ext = mime.extension(file.mimetype);
|
|
99
|
-
|
|
100
|
-
if (ext === false)
|
|
101
|
-
throw new InputError(`We couldn't determine the type of the CV file you sent. Please encure it's not corrupted and try again.`);
|
|
102
|
-
|
|
103
|
-
return new FileToUpload({
|
|
104
|
-
|
|
105
|
-
name: file.name,
|
|
106
|
-
type: file.mimetype,
|
|
107
|
-
size: file.size,
|
|
108
|
-
|
|
109
|
-
data: file.data,
|
|
110
|
-
|
|
111
|
-
md5: file.md5,
|
|
112
|
-
ext: ext
|
|
113
|
-
})
|
|
114
97
|
}
|
|
@@ -10,7 +10,6 @@ import Bowser from "bowser";
|
|
|
10
10
|
|
|
11
11
|
// Core
|
|
12
12
|
import BaseRequest from '@common/router/request';
|
|
13
|
-
import type FileToUpload from '@client/components/File/FileToUpload';
|
|
14
13
|
|
|
15
14
|
// Specific
|
|
16
15
|
import type {
|
|
@@ -40,7 +39,7 @@ const localeFilter = (input: any) => {
|
|
|
40
39
|
return lang.toUpperCase();
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
export type UploadedFile =
|
|
42
|
+
export type UploadedFile = File
|
|
44
43
|
|
|
45
44
|
/*----------------------------------
|
|
46
45
|
- CONTEXTE
|
|
@@ -13,7 +13,6 @@ import normalizeUrl, { Options as NormalizeUrlOptions } from 'normalize-url';
|
|
|
13
13
|
|
|
14
14
|
// Core
|
|
15
15
|
import { InputError } from '@common/errors';
|
|
16
|
-
import FileToUpload from '@client/components/File/FileToUpload';
|
|
17
16
|
|
|
18
17
|
// Speciific
|
|
19
18
|
import Schema, { TSchemaFields } from './schema'
|
|
@@ -23,7 +22,7 @@ import Validator, { TValidatorOptions, EXCLUDE_VALUE, TValidatorDefinition } fro
|
|
|
23
22
|
- TYPES
|
|
24
23
|
----------------------------------*/
|
|
25
24
|
|
|
26
|
-
export type TFileValidator = TValidatorOptions<
|
|
25
|
+
export type TFileValidator = TValidatorOptions<File> & {
|
|
27
26
|
type?: string[], // Raccourci, ou liste de mimetype
|
|
28
27
|
taille?: number,
|
|
29
28
|
disk?: string, // Disk to upload files to
|
|
@@ -447,14 +446,14 @@ export class SchemaValidators {
|
|
|
447
446
|
----------------------------------*/
|
|
448
447
|
public file = ({ type, taille, ...opts }: TFileValidator & {
|
|
449
448
|
|
|
450
|
-
} = {}) => new Validator<
|
|
449
|
+
} = {}) => new Validator<File>('file', (val, options, path) => {
|
|
451
450
|
|
|
452
451
|
// Chaine = url ancien fichier = exclusion de la valeur pour conserver l'ancien fichier
|
|
453
452
|
// NOTE: Si la valeur est présente mais undefined, alors on supprimera le fichier
|
|
454
453
|
if (typeof val === 'string')
|
|
455
454
|
return EXCLUDE_VALUE;
|
|
456
455
|
|
|
457
|
-
if (!(val instanceof
|
|
456
|
+
if (!(val instanceof File))
|
|
458
457
|
throw new InputError(`Must be a File (${typeof val} received)`);
|
|
459
458
|
|
|
460
459
|
// MIME
|
|
@@ -6,10 +6,8 @@
|
|
|
6
6
|
import type { Application } from '@server/app';
|
|
7
7
|
|
|
8
8
|
// Specific
|
|
9
|
-
import { SchemaValidators, TFileValidator } from '@
|
|
10
|
-
import Validator, { TValidatorOptions } from '@
|
|
11
|
-
|
|
12
|
-
import type FileToUpload from '@client/components/File/FileToUpload';
|
|
9
|
+
import { SchemaValidators, TFileValidator } from '@server/services/router/request/validation/validators';
|
|
10
|
+
import Validator, { TValidatorOptions } from '@server/services/router/request/validation/validator';
|
|
13
11
|
|
|
14
12
|
/*----------------------------------
|
|
15
13
|
- TYPES
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
default as Router, RequestService, Request as ServerRequest
|
|
8
8
|
} from '@server/services/router';
|
|
9
9
|
|
|
10
|
-
import Schema, { TSchemaFields, TValidatedData } from '@
|
|
10
|
+
import Schema, { TSchemaFields, TValidatedData } from '@server/services/router/request/validation/schema';
|
|
11
11
|
|
|
12
12
|
// Specific
|
|
13
13
|
import ServerSchemaValidator from '.';
|
package/types/icons.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type TIcones = "solid/spinner-third"|"long-arrow-right"|"times-circle"|"brands/whatsapp"|"times"|"search"|"user"|"rocket"|"globe"|"bullhorn"|"briefcase"|"chart-line"|"handshake"|"ellipsis-h"|"brands/google"|"brands/reddit-alien"|"brands/linkedin-in"|"brands/github"|"robot"|"comments"|"user-friends"|"angle-down"|"mouse-pointer"|"thumbs-up"|"dollar-sign"|"info-circle"|"check-circle"|"exclamation-circle"|"
|
|
1
|
+
export type TIcones = "solid/spinner-third"|"long-arrow-right"|"times-circle"|"brands/whatsapp"|"times"|"search"|"user"|"rocket"|"globe"|"bullhorn"|"briefcase"|"chart-line"|"handshake"|"ellipsis-h"|"brands/google"|"brands/reddit-alien"|"brands/linkedin-in"|"brands/github"|"robot"|"comments"|"user-friends"|"angle-down"|"mouse-pointer"|"thumbs-up"|"dollar-sign"|"info-circle"|"check-circle"|"exclamation-circle"|"bars"|"font"|"tag"|"compress"|"bolt"|"puzzle-piece"|"planet-ringed"|"chart-bar"|"power-off"|"heart"|"lock"|"eye"|"credit-card"|"at"|"brands/linkedin"|"key"|"database"|"solid/fire"|"usd-circle"|"lightbulb"|"solid/dollar-sign"|"download"|"code"|"solid/clock"|"exclamation"|"solid/download"|"seedling"|"palette"|"car"|"plane"|"university"|"hard-hat"|"graduation-cap"|"cogs"|"film"|"leaf"|"tshirt"|"utensils"|"map-marked-alt"|"dumbbell"|"stethoscope"|"concierge-bell"|"book"|"shield-alt"|"gavel"|"industry"|"square-root-alt"|"newspaper"|"pills"|"medal"|"capsules"|"balance-scale"|"home"|"praying-hands"|"shopping-cart"|"flask"|"futbol"|"microchip"|"satellite-dish"|"shipping-fast"|"passport"|"tools"|"user-circle"|"plus-circle"|"brands/twitter"|"brands/facebook"|"comment-alt"|"check"|"angle-left"|"angle-right"|"paper-plane"|"long-arrow-left"|"meh-rolling-eyes"|"arrow-left"|"arrow-right"|"trash"|"bold"|"italic"|"underline"|"strikethrough"|"subscript"|"superscript"|"link"|"unlink"|"pen"|"file"|"plus"|"empty-set"|"horizontal-rule"|"page-break"|"image"|"table"|"poll"|"columns"|"sticky-note"|"caret-right"|"list-ul"|"check-square"|"h1"|"h2"|"h3"|"h4"|"list-ol"|"paragraph"|"quote-left"|"align-left"|"align-center"|"align-right"|"align-justify"|"indent"|"outdent"
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// Normalize file between browser and nodejs side
|
|
2
|
-
export default class FileToUpload {
|
|
3
|
-
|
|
4
|
-
public name: string;
|
|
5
|
-
public size: number;
|
|
6
|
-
public type: string;
|
|
7
|
-
|
|
8
|
-
public data: File;
|
|
9
|
-
|
|
10
|
-
// Retrieved on backend only
|
|
11
|
-
public md5?: string;
|
|
12
|
-
public ext?: string;
|
|
13
|
-
|
|
14
|
-
public constructor(opts: {
|
|
15
|
-
name: string,
|
|
16
|
-
size: number,
|
|
17
|
-
type: string,
|
|
18
|
-
|
|
19
|
-
data: File,
|
|
20
|
-
|
|
21
|
-
md5?: string,
|
|
22
|
-
ext?: string,
|
|
23
|
-
}) {
|
|
24
|
-
|
|
25
|
-
this.name = opts.name;
|
|
26
|
-
this.size = opts.size;
|
|
27
|
-
this.type = opts.type;
|
|
28
|
-
|
|
29
|
-
this.data = opts.data;
|
|
30
|
-
|
|
31
|
-
this.md5 = opts.md5;
|
|
32
|
-
this.ext = opts.ext;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
/*----------------------------------
|
|
2
|
-
- DEPENDANCES
|
|
3
|
-
----------------------------------*/
|
|
4
|
-
|
|
5
|
-
// npm
|
|
6
|
-
import React from 'react';
|
|
7
|
-
|
|
8
|
-
// Core
|
|
9
|
-
import { InputErrorSchema } from '@common/errors';
|
|
10
|
-
import type { Schema } from '@common/validation';
|
|
11
|
-
import type { TValidationResult, TValidateOptions } from '@common/validation/schema';
|
|
12
|
-
import useContext from '@/client/context';
|
|
13
|
-
|
|
14
|
-
// Exports
|
|
15
|
-
export type { TValidationResult, TSchemaData } from '@common/validation/schema';
|
|
16
|
-
|
|
17
|
-
/*----------------------------------
|
|
18
|
-
- TYPES
|
|
19
|
-
----------------------------------*/
|
|
20
|
-
export type TFormOptions<TFormData extends {}> = {
|
|
21
|
-
data?: Partial<TFormData>,
|
|
22
|
-
submit?: (data: TFormData) => Promise<void>,
|
|
23
|
-
autoValidateOnly?: (keyof TFormData)[],
|
|
24
|
-
autoSave?: {
|
|
25
|
-
id: string
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export type FieldsAttrs<TFormData extends {}> = {
|
|
30
|
-
[fieldName in keyof TFormData]: {}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type Form<TFormData extends {} = {}> = {
|
|
34
|
-
|
|
35
|
-
// Data
|
|
36
|
-
fields: FieldsAttrs<TFormData>,
|
|
37
|
-
data: TFormData,
|
|
38
|
-
options: TFormOptions<TFormData>,
|
|
39
|
-
backup?: Partial<TFormData>,
|
|
40
|
-
|
|
41
|
-
// Actions
|
|
42
|
-
setBackup: (backup: Partial<TFormData>) => void,
|
|
43
|
-
validate: (data: Partial<TFormData>, validateAll?: boolean) => TValidationResult<{}>,
|
|
44
|
-
set: (data: Partial<TFormData>, merge?: boolean) => void,
|
|
45
|
-
submit: (additionnalData?: Partial<TFormData>) => Promise<any>,
|
|
46
|
-
|
|
47
|
-
} & FormState
|
|
48
|
-
|
|
49
|
-
type FormState<TFormData extends {} = {}> = {
|
|
50
|
-
isLoading: boolean,
|
|
51
|
-
hasChanged: boolean,
|
|
52
|
-
errorsCount: number,
|
|
53
|
-
errors: { [fieldName: string]: string[] },
|
|
54
|
-
backup?: Partial<TFormData>,
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/*----------------------------------
|
|
58
|
-
- HOOK
|
|
59
|
-
----------------------------------*/
|
|
60
|
-
export default function useForm<TFormData extends {}>(
|
|
61
|
-
schema: Schema<TFormData>,
|
|
62
|
-
options: TFormOptions<TFormData> = {}
|
|
63
|
-
): [ Form<TFormData>, FieldsAttrs<TFormData> ] {
|
|
64
|
-
|
|
65
|
-
const context = useContext();
|
|
66
|
-
|
|
67
|
-
/*----------------------------------
|
|
68
|
-
- INIT
|
|
69
|
-
----------------------------------*/
|
|
70
|
-
|
|
71
|
-
const [state, setState] = React.useState<FormState<TFormData>>({
|
|
72
|
-
hasChanged: false,//options.data !== undefined,
|
|
73
|
-
isLoading: false,
|
|
74
|
-
errorsCount: 0,
|
|
75
|
-
errors: {},
|
|
76
|
-
backup: undefined
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const initialData: Partial<TFormData> = options.data || {};
|
|
80
|
-
|
|
81
|
-
// States
|
|
82
|
-
const fields = React.useRef<FieldsAttrs<TFormData> | null>(null);
|
|
83
|
-
const [data, setData] = React.useState< Partial<TFormData> >(initialData);
|
|
84
|
-
|
|
85
|
-
// When typed data changes
|
|
86
|
-
React.useEffect(() => {
|
|
87
|
-
|
|
88
|
-
// Validate
|
|
89
|
-
validate(data, { ignoreMissing: true });
|
|
90
|
-
|
|
91
|
-
// Autosave
|
|
92
|
-
if (options.autoSave !== undefined && state.hasChanged) {
|
|
93
|
-
saveLocally(data, options.autoSave.id);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
}, [data]);
|
|
97
|
-
|
|
98
|
-
// On start
|
|
99
|
-
React.useEffect(() => {
|
|
100
|
-
|
|
101
|
-
// Restore backup
|
|
102
|
-
if (options.autoSave !== undefined && !state.hasChanged) {
|
|
103
|
-
|
|
104
|
-
const autosaved = localStorage.getItem('form.' + options.autoSave.id);
|
|
105
|
-
if (autosaved !== null) {
|
|
106
|
-
try {
|
|
107
|
-
console.log('[form] Parse autosaved from json:', autosaved);
|
|
108
|
-
setState(c => ({
|
|
109
|
-
...c,
|
|
110
|
-
backup: JSON.parse(autosaved)
|
|
111
|
-
}));
|
|
112
|
-
} catch (error) {
|
|
113
|
-
console.error('[form] Failed to decode autosaved data from json:', autosaved);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
}, []);
|
|
119
|
-
|
|
120
|
-
/*----------------------------------
|
|
121
|
-
- ACTIONS
|
|
122
|
-
----------------------------------*/
|
|
123
|
-
const validate = (allData: Partial<TFormData> = data, opts: TValidateOptions<TFormData> = {}) => {
|
|
124
|
-
|
|
125
|
-
const validated = schema.validateWithDetails(allData, allData, {}, {
|
|
126
|
-
// Ignore the fields where the vlaue has not been changed
|
|
127
|
-
// if the validation was triggered via onChange
|
|
128
|
-
ignoreMissing: false,
|
|
129
|
-
// The list of fields we should only validate
|
|
130
|
-
only: options.autoValidateOnly,
|
|
131
|
-
// Custom options
|
|
132
|
-
...opts
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Update errors
|
|
136
|
-
if (validated.errorsCount !== state.errorsCount) {
|
|
137
|
-
rebuildFieldsAttrs({
|
|
138
|
-
errorsCount: validated.errorsCount,
|
|
139
|
-
errors: validated.erreurs,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return validated;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const submit = (additionnalData: Partial<TFormData> = {}) => {
|
|
147
|
-
|
|
148
|
-
const allData = { ...data, ...additionnalData }
|
|
149
|
-
|
|
150
|
-
// Validation
|
|
151
|
-
const validated = validate(allData);
|
|
152
|
-
if (validated.errorsCount !== 0) {
|
|
153
|
-
throw new InputErrorSchema(validated.erreurs);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const afterSubmit = (responseData?: any) => {
|
|
157
|
-
|
|
158
|
-
// Reset autosaved data
|
|
159
|
-
if (options.autoSave)
|
|
160
|
-
localStorage.removeItem('form.' + options.autoSave.id);
|
|
161
|
-
|
|
162
|
-
// Update state
|
|
163
|
-
setState( current => ({
|
|
164
|
-
...current,
|
|
165
|
-
hasChanged: false
|
|
166
|
-
}));
|
|
167
|
-
|
|
168
|
-
return responseData;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Callback
|
|
172
|
-
if (options.submit)
|
|
173
|
-
return options.submit(allData as TFormData).then(afterSubmit);
|
|
174
|
-
else {
|
|
175
|
-
afterSubmit();
|
|
176
|
-
return undefined;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const rebuildFieldsAttrs = (newState: Partial<FormState> = {}) => {
|
|
181
|
-
// Force rebuilding the fields definition on the next state change
|
|
182
|
-
fields.current = null;
|
|
183
|
-
// Force state change
|
|
184
|
-
setState(old => ({
|
|
185
|
-
...old,
|
|
186
|
-
...newState
|
|
187
|
-
}));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const saveLocally = (data: Partial<TFormData>, id: string) => {
|
|
191
|
-
console.log('[form] Autosave data for form:', id, ':', data);
|
|
192
|
-
localStorage.setItem('form.' + id, JSON.stringify(data));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Rebuild the fields attrs when the schema changes
|
|
196
|
-
if (fields.current === null || Object.keys(schema).join(',') !== Object.keys(fields.current).join(',')) {
|
|
197
|
-
fields.current = {} as FieldsAttrs<TFormData>
|
|
198
|
-
for (const fieldName in schema.fields) {
|
|
199
|
-
|
|
200
|
-
const validator = schema.getFieldValidator(fieldName);
|
|
201
|
-
|
|
202
|
-
fields.current[fieldName] = {
|
|
203
|
-
|
|
204
|
-
// Value control
|
|
205
|
-
value: data[fieldName],
|
|
206
|
-
onChange: (val) => {
|
|
207
|
-
setData(old => {
|
|
208
|
-
return {
|
|
209
|
-
...old,
|
|
210
|
-
[fieldName]: typeof val === 'function'
|
|
211
|
-
? val(old[fieldName])
|
|
212
|
-
: val
|
|
213
|
-
}
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
setState(current => ({
|
|
217
|
-
...current,
|
|
218
|
-
hasChanged: true
|
|
219
|
-
}));
|
|
220
|
-
},
|
|
221
|
-
|
|
222
|
-
// Submit on press enter
|
|
223
|
-
onKeyDown: e => {
|
|
224
|
-
if (e.key === 'Enter' || (e.keyCode || e.which) === 13) {
|
|
225
|
-
submit({ [fieldName]: e.target.value } as Partial<TFormData>);
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
// Error
|
|
230
|
-
errors: state.errors[fieldName],
|
|
231
|
-
|
|
232
|
-
// Component attributes
|
|
233
|
-
...validator.componentAttributes
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/*----------------------------------
|
|
239
|
-
- EXPOSE
|
|
240
|
-
----------------------------------*/
|
|
241
|
-
|
|
242
|
-
const form: Form<TFormData> = {
|
|
243
|
-
|
|
244
|
-
fields: fields.current,
|
|
245
|
-
data,
|
|
246
|
-
set: (data, merge = true) => {
|
|
247
|
-
|
|
248
|
-
setState( current => ({
|
|
249
|
-
...current,
|
|
250
|
-
hasChanged: true
|
|
251
|
-
}));
|
|
252
|
-
|
|
253
|
-
setData( merge
|
|
254
|
-
? c => ({ ...c, ...data })
|
|
255
|
-
: data
|
|
256
|
-
);
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
validate,
|
|
260
|
-
submit,
|
|
261
|
-
options,
|
|
262
|
-
|
|
263
|
-
setBackup: (backup: Partial<TFormData>) => {
|
|
264
|
-
|
|
265
|
-
setState(c => ({ ...c, backup }));
|
|
266
|
-
|
|
267
|
-
if (options.autoSave)
|
|
268
|
-
localStorage.setItem('form.' + options.autoSave.id, JSON.stringify(backup));
|
|
269
|
-
},
|
|
270
|
-
|
|
271
|
-
...state
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return [form, fields.current]
|
|
275
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|