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.
@@ -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 | FileToUpload, // string = already registered
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: FileToUpload | undefined) => void
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
- const fileToUpload = normalizeFile(selectedfile);
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.data).then(setPreviewUrl);
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 '../../common/validation/validator';
17
- import type { SchemaValidators } from '@common/validation/validators';
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 FileToUpload);
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);
@@ -3,7 +3,7 @@
3
3
  ----------------------------------*/
4
4
 
5
5
  // Npm
6
- import zod from 'zod';
6
+ import type zod from 'zod';
7
7
 
8
8
  // types
9
9
  import type {
@@ -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 | FileToUpload }
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.1-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",
@@ -208,6 +208,8 @@ export abstract class Application<
208
208
  ? route.schema.parse( context.request.data )
209
209
  : {};
210
210
 
211
+ console.log('-----data', data);
212
+
211
213
  // Run controller
212
214
  return origController.bind( service )(
213
215
  data,
@@ -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 = normalizeFile(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 = With<FileToUpload, 'md5'|'ext'>
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<FileToUpload> & {
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<FileToUpload>('file', (val, options, path) => {
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 FileToUpload))
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 '@common/validation/validators';
10
- import Validator, { TValidatorOptions } from '@common/validation/validator';
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 '@common/validation/schema';
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"|"home"|"user-circle"|"newspaper"|"plus-circle"|"brands/linkedin"|"brands/twitter"|"brands/facebook"|"comment-alt"|"bars"|"font"|"tag"|"compress"|"bolt"|"puzzle-piece"|"planet-ringed"|"chart-bar"|"power-off"|"heart"|"lock"|"eye"|"credit-card"|"at"|"key"|"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"|"pills"|"medal"|"capsules"|"balance-scale"|"praying-hands"|"shopping-cart"|"flask"|"futbol"|"microchip"|"satellite-dish"|"shipping-fast"|"passport"|"tools"|"database"|"solid/fire"|"usd-circle"|"lightbulb"|"solid/dollar-sign"|"download"|"code"|"solid/clock"|"exclamation"|"solid/download"|"angle-left"|"angle-right"|"check"|"paper-plane"|"long-arrow-left"|"trash"|"meh-rolling-eyes"|"arrow-left"|"arrow-right"|"bold"|"italic"|"underline"|"link"|"strikethrough"|"subscript"|"superscript"|"empty-set"|"horizontal-rule"|"page-break"|"image"|"table"|"poll"|"columns"|"sticky-note"|"caret-right"|"file"|"unlink"|"pen"|"plus"|"align-left"|"align-center"|"align-right"|"align-justify"|"indent"|"outdent"|"list-ul"|"check-square"|"h1"|"h2"|"h3"|"h4"|"list-ol"|"paragraph"|"quote-left"
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
- }