@strictly/react-form 0.0.10 → 0.0.12
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/.out/core/mobx/field_adapter_builder.js +18 -6
- package/.out/core/mobx/{form_presenter.d.ts → form_model.d.ts} +15 -21
- package/.out/core/mobx/form_model.js +513 -0
- package/.out/core/mobx/hooks.d.ts +6 -25
- package/.out/core/mobx/hooks.js +14 -50
- package/.out/core/mobx/merge_field_adapters_with_validators.js +1 -5
- package/.out/core/mobx/specs/fixtures.js +2 -1
- package/.out/core/mobx/specs/{form_presenter.tests.js → form_model.tests.js} +52 -43
- package/.out/core/mobx/specs/sub_form_field_adapters.tests.js +2 -1
- package/.out/core/mobx/types.d.ts +4 -4
- package/.out/field_converters/integer_to_string_converter.js +12 -4
- package/.out/field_converters/maybe_identity_converter.js +12 -4
- package/.out/field_converters/nullable_to_boolean_converter.js +24 -7
- package/.out/field_converters/select_value_type_converter.js +36 -12
- package/.out/index.d.ts +1 -1
- package/.out/index.js +1 -1
- package/.out/mantine/create_checkbox.js +8 -4
- package/.out/mantine/create_fields_view.js +7 -4
- package/.out/mantine/create_form.js +1 -1
- package/.out/mantine/create_radio_group.js +8 -4
- package/.out/mantine/create_text_input.js +8 -4
- package/.out/mantine/create_value_input.js +8 -4
- package/.out/mantine/hooks.js +218 -92
- package/.out/mantine/specs/checkbox_hooks.stories.js +13 -1
- package/.out/mantine/specs/checkbox_hooks.tests.js +22 -9
- package/.out/mantine/specs/fields_view_hooks.stories.js +15 -2
- package/.out/mantine/specs/fields_view_hooks.tests.js +12 -3
- package/.out/mantine/specs/radio_group_hooks.stories.js +13 -1
- package/.out/mantine/specs/radio_group_hooks.tests.js +23 -10
- package/.out/mantine/specs/select_hooks.stories.js +13 -1
- package/.out/mantine/specs/text_input_hooks.stories.js +13 -1
- package/.out/mantine/specs/text_input_hooks.tests.js +18 -7
- package/.out/mantine/specs/value_input_hooks.stories.js +14 -2
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.out/tsup.config.js +2 -9
- package/.out/types/merge_validators.js +1 -4
- package/.out/util/partial.js +5 -5
- package/.out/vitest.workspace.js +2 -10
- package/.turbo/turbo-build.log +9 -9
- package/.turbo/turbo-check-types.log +1 -1
- package/.turbo/turbo-release$colon$exports.log +1 -1
- package/core/mobx/{form_presenter.ts → form_model.ts} +287 -329
- package/core/mobx/hooks.tsx +26 -123
- package/core/mobx/specs/{form_presenter.tests.ts → form_model.tests.ts} +101 -94
- package/core/mobx/types.ts +12 -12
- package/dist/index.cjs +639 -600
- package/dist/index.d.cts +51 -73
- package/dist/index.d.ts +51 -73
- package/dist/index.js +644 -601
- package/index.ts +1 -1
- package/mantine/hooks.tsx +2 -0
- package/package.json +1 -1
- package/.out/core/mobx/form_presenter.js +0 -422
- /package/.out/core/mobx/specs/{form_presenter.tests.d.ts → form_model.tests.d.ts} +0 -0
|
@@ -102,7 +102,7 @@ export type ValuePathsToAdaptersOf<
|
|
|
102
102
|
}
|
|
103
103
|
: never
|
|
104
104
|
|
|
105
|
-
export
|
|
105
|
+
export class FormModel<
|
|
106
106
|
T extends Type,
|
|
107
107
|
ValueToTypePaths extends Readonly<Record<string, string>>,
|
|
108
108
|
TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<
|
|
@@ -114,10 +114,173 @@ export abstract class FormPresenter<
|
|
|
114
114
|
ValueToTypePaths
|
|
115
115
|
>,
|
|
116
116
|
> {
|
|
117
|
+
@observable.ref
|
|
118
|
+
accessor value: MobxValueOfType<T>
|
|
119
|
+
@observable.shallow
|
|
120
|
+
accessor fieldOverrides: FlattenedFieldOverrides<ValuePathsToAdapters>
|
|
121
|
+
@observable.shallow
|
|
122
|
+
accessor errors: FlattenedErrors<ValuePathsToAdapters> = {}
|
|
123
|
+
|
|
124
|
+
private readonly flattenedTypeDefs: Readonly<Record<string, Type>>
|
|
125
|
+
|
|
117
126
|
constructor(
|
|
118
127
|
readonly type: T,
|
|
128
|
+
value: ValueOfType<ReadonlyTypeOfType<T>>,
|
|
119
129
|
protected readonly adapters: TypePathsToAdapters,
|
|
120
130
|
) {
|
|
131
|
+
this.value = mobxCopy(type, value)
|
|
132
|
+
this.flattenedTypeDefs = flattenTypesOfType(type)
|
|
133
|
+
// pre-populate field overrides for consistent behavior when default information is overwritten
|
|
134
|
+
// then returned to
|
|
135
|
+
const conversions = flattenValueTo(
|
|
136
|
+
type,
|
|
137
|
+
this.value,
|
|
138
|
+
() => {},
|
|
139
|
+
(
|
|
140
|
+
_t: StrictTypeDef,
|
|
141
|
+
value: AnyValueType,
|
|
142
|
+
_setter,
|
|
143
|
+
typePath,
|
|
144
|
+
valuePath,
|
|
145
|
+
): AnnotatedFieldConversion<FieldOverride> | undefined => {
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
147
|
+
const adapter = this.adapters[typePath as keyof TypePathsToAdapters]
|
|
148
|
+
if (adapter == null) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
const {
|
|
152
|
+
convert,
|
|
153
|
+
revert,
|
|
154
|
+
} = adapter
|
|
155
|
+
if (revert == null) {
|
|
156
|
+
// no need to store a temporary value if the value cannot be written back
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
return convert(value, valuePath, this.value)
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
163
|
+
this.fieldOverrides = map(conversions, function (_k, v) {
|
|
164
|
+
return v && [v.value]
|
|
165
|
+
}) as FlattenedFieldOverrides<ValuePathsToAdapters>
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@computed
|
|
169
|
+
get fields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>> {
|
|
170
|
+
return new Proxy<SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>>>(
|
|
171
|
+
this.knownFields,
|
|
172
|
+
{
|
|
173
|
+
get: (target, prop) => {
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
175
|
+
const field = (target as any)[prop]
|
|
176
|
+
if (field != null) {
|
|
177
|
+
return field
|
|
178
|
+
}
|
|
179
|
+
if (typeof prop === 'string') {
|
|
180
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
181
|
+
return this.maybeSynthesizeFieldByValuePath(prop as keyof ValuePathsToAdapters)
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@computed
|
|
189
|
+
private get knownFields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>> {
|
|
190
|
+
return flattenValueTo(
|
|
191
|
+
this.type,
|
|
192
|
+
this.value,
|
|
193
|
+
() => {},
|
|
194
|
+
// TODO swap these to valuePath, typePath in flatten
|
|
195
|
+
(_t: StrictTypeDef, _v: AnyValueType, _setter, typePath, valuePath): Field | undefined => {
|
|
196
|
+
return this.synthesizeFieldByPaths(
|
|
197
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
198
|
+
valuePath as keyof ValuePathsToAdapters,
|
|
199
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
200
|
+
typePath as keyof TypePathsToAdapters,
|
|
201
|
+
)
|
|
202
|
+
},
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private maybeSynthesizeFieldByValuePath(valuePath: keyof ValuePathsToAdapters): Field | undefined {
|
|
207
|
+
let typePath: keyof TypePathsToAdapters
|
|
208
|
+
try {
|
|
209
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
210
|
+
typePath = valuePathToTypePath<ValueToTypePaths, keyof ValueToTypePaths>(
|
|
211
|
+
this.type,
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
213
|
+
valuePath as keyof ValueToTypePaths,
|
|
214
|
+
true,
|
|
215
|
+
) as keyof TypePathsToAdapters
|
|
216
|
+
} catch (e) {
|
|
217
|
+
// TODO make jsonValuePathToTypePath return null in the event of an invalid
|
|
218
|
+
// value path instead of throwing an exception
|
|
219
|
+
// assume that the path was invalid
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
return this.synthesizeFieldByPaths(valuePath, typePath)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private synthesizeFieldByPaths(valuePath: keyof ValuePathsToAdapters, typePath: keyof TypePathsToAdapters) {
|
|
226
|
+
const adapter = this.adapters[typePath]
|
|
227
|
+
if (adapter == null) {
|
|
228
|
+
// invalid path, which can happen
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
const {
|
|
232
|
+
convert,
|
|
233
|
+
create,
|
|
234
|
+
} = adapter
|
|
235
|
+
|
|
236
|
+
const fieldOverride = this.fieldOverrides[valuePath]
|
|
237
|
+
const accessor = this.getAccessorForValuePath(valuePath)
|
|
238
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
239
|
+
const fieldTypeDef = this.flattenedTypeDefs[typePath as string]
|
|
240
|
+
const {
|
|
241
|
+
value,
|
|
242
|
+
required,
|
|
243
|
+
readonly,
|
|
244
|
+
} = convert(
|
|
245
|
+
accessor != null
|
|
246
|
+
? accessor.value
|
|
247
|
+
: fieldTypeDef != null
|
|
248
|
+
? mobxCopy(
|
|
249
|
+
fieldTypeDef,
|
|
250
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
251
|
+
create(valuePath as string, this.value),
|
|
252
|
+
)
|
|
253
|
+
// fake values can't be copied
|
|
254
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
255
|
+
: create(valuePath as string, this.value),
|
|
256
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
257
|
+
valuePath as string,
|
|
258
|
+
this.value,
|
|
259
|
+
)
|
|
260
|
+
const error = this.errors[valuePath]
|
|
261
|
+
return {
|
|
262
|
+
value: fieldOverride != null ? fieldOverride[0] : value,
|
|
263
|
+
error,
|
|
264
|
+
readonly,
|
|
265
|
+
required,
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
getAccessorForValuePath(valuePath: keyof ValuePathsToAdapters): Accessor | undefined {
|
|
270
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
271
|
+
return this.accessors[valuePath as string]
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
@computed
|
|
275
|
+
// should only be referenced internally, so loosely typed
|
|
276
|
+
get accessors(): Readonly<Record<string, Accessor>> {
|
|
277
|
+
return flattenAccessorsOfType<T, Readonly<Record<string, Accessor>>>(
|
|
278
|
+
this.type,
|
|
279
|
+
this.value,
|
|
280
|
+
(value: ValueOfType<T>): void => {
|
|
281
|
+
this.value = mobxCopy(this.type, value)
|
|
282
|
+
},
|
|
283
|
+
)
|
|
121
284
|
}
|
|
122
285
|
|
|
123
286
|
private maybeGetAdapterForValuePath(valuePath: keyof ValuePathsToAdapters) {
|
|
@@ -140,23 +303,20 @@ export abstract class FormPresenter<
|
|
|
140
303
|
}
|
|
141
304
|
|
|
142
305
|
setFieldValueAndValidate<K extends keyof ValuePathsToAdapters>(
|
|
143
|
-
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
144
306
|
valuePath: K,
|
|
145
307
|
value: ToOfFieldAdapter<ValuePathsToAdapters[K]>,
|
|
146
308
|
): boolean {
|
|
147
|
-
return this.internalSetFieldValue(
|
|
309
|
+
return this.internalSetFieldValue(valuePath, value, true)
|
|
148
310
|
}
|
|
149
311
|
|
|
150
312
|
setFieldValue<K extends keyof ValuePathsToAdapters>(
|
|
151
|
-
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
152
313
|
valuePath: K,
|
|
153
314
|
value: ToOfFieldAdapter<ValuePathsToAdapters[K]>,
|
|
154
315
|
): boolean {
|
|
155
|
-
return this.internalSetFieldValue(
|
|
316
|
+
return this.internalSetFieldValue(valuePath, value, false)
|
|
156
317
|
}
|
|
157
318
|
|
|
158
319
|
addListItem<K extends keyof FlattenedListTypesOfType<T>>(
|
|
159
|
-
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
160
320
|
valuePath: K,
|
|
161
321
|
// TODO can this type be simplified?
|
|
162
322
|
elementValue: Maybe<ElementOfArray<FlattenedValuesOfType<T>[K]>> = null,
|
|
@@ -164,7 +324,7 @@ export abstract class FormPresenter<
|
|
|
164
324
|
) {
|
|
165
325
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
166
326
|
const listValuePath = valuePath as string
|
|
167
|
-
const accessor =
|
|
327
|
+
const accessor = this.accessors[valuePath]
|
|
168
328
|
const listTypePath = this.typePath(valuePath)
|
|
169
329
|
const definedIndex = index ?? accessor.value.length
|
|
170
330
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -181,7 +341,7 @@ export abstract class FormPresenter<
|
|
|
181
341
|
: elementAdapter.create(
|
|
182
342
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
183
343
|
elementTypePath as string,
|
|
184
|
-
|
|
344
|
+
this.value,
|
|
185
345
|
)
|
|
186
346
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
187
347
|
const originalList: any[] = accessor.value
|
|
@@ -192,7 +352,7 @@ export abstract class FormPresenter<
|
|
|
192
352
|
]
|
|
193
353
|
// shuffle the overrides around to account for new indices
|
|
194
354
|
// to so this we need to sort the array indices in descending order
|
|
195
|
-
const targetPaths = Object.keys(
|
|
355
|
+
const targetPaths = Object.keys(this.fieldOverrides).filter(function (v) {
|
|
196
356
|
return v.startsWith(`${listValuePath}.`)
|
|
197
357
|
}).map(function (v) {
|
|
198
358
|
const parts = v.substring(listValuePath.length + 1).split('.')
|
|
@@ -207,11 +367,11 @@ export abstract class FormPresenter<
|
|
|
207
367
|
// descending
|
|
208
368
|
return b - a
|
|
209
369
|
})
|
|
210
|
-
runInAction(
|
|
211
|
-
targetPaths.forEach(
|
|
370
|
+
runInAction(() => {
|
|
371
|
+
targetPaths.forEach(([
|
|
212
372
|
index,
|
|
213
373
|
postfix,
|
|
214
|
-
]) {
|
|
374
|
+
]) => {
|
|
215
375
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
216
376
|
const fromJsonPath = [
|
|
217
377
|
listValuePath,
|
|
@@ -224,99 +384,99 @@ export abstract class FormPresenter<
|
|
|
224
384
|
`${index + 1}`,
|
|
225
385
|
...postfix,
|
|
226
386
|
].join('.') as keyof ValuePathsToAdapters
|
|
227
|
-
const fieldOverride =
|
|
228
|
-
delete
|
|
229
|
-
|
|
230
|
-
const error =
|
|
231
|
-
delete
|
|
232
|
-
|
|
387
|
+
const fieldOverride = this.fieldOverrides[fromJsonPath]
|
|
388
|
+
delete this.fieldOverrides[fromJsonPath]
|
|
389
|
+
this.fieldOverrides[toJsonPath] = fieldOverride
|
|
390
|
+
const error = this.errors[fromJsonPath]
|
|
391
|
+
delete this.errors[fromJsonPath]
|
|
392
|
+
this.errors[toJsonPath] = error
|
|
233
393
|
})
|
|
234
394
|
accessor.set(newList)
|
|
235
395
|
// delete any value overrides so the new list isn't shadowed
|
|
236
396
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
237
|
-
delete
|
|
397
|
+
delete this.fieldOverrides[listValuePath as keyof ValuePathsToAdapters]
|
|
238
398
|
})
|
|
239
399
|
}
|
|
240
400
|
|
|
241
|
-
removeListItem<K extends keyof FlattenedListTypesOfType<T>>(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
elementIndexString,
|
|
248
|
-
] = assertExistsAndReturn(
|
|
249
|
-
jsonPathPop(elementValuePath),
|
|
250
|
-
'expected a path with two or more segments {}',
|
|
251
|
-
elementValuePath,
|
|
252
|
-
)
|
|
253
|
-
const accessor = model.accessors[listValuePath]
|
|
254
|
-
const elementIndex = checkValidNumber(
|
|
255
|
-
parseInt(elementIndexString),
|
|
256
|
-
'unexpected index {} ({})',
|
|
257
|
-
elementIndexString,
|
|
258
|
-
elementValuePath,
|
|
259
|
-
)
|
|
260
|
-
const newList = [...accessor.value]
|
|
261
|
-
assertState(
|
|
262
|
-
elementIndex >= 0 && elementIndex < newList.length,
|
|
263
|
-
'invalid index from path {} ({})',
|
|
264
|
-
elementIndex,
|
|
265
|
-
elementValuePath,
|
|
266
|
-
)
|
|
267
|
-
newList.splice(elementIndex, 1)
|
|
268
|
-
|
|
269
|
-
// shuffle the overrides around to account for new indices
|
|
270
|
-
// to so this we need to sort the array indices in descending order
|
|
271
|
-
const targetPaths = Object.keys(model.fieldOverrides).filter(function (v) {
|
|
272
|
-
return v.startsWith(`${listValuePath}.`)
|
|
273
|
-
}).map(function (v) {
|
|
274
|
-
const parts = v.substring(listValuePath.length + 1).split('.')
|
|
275
|
-
const index = parseInt(parts[0])
|
|
276
|
-
return [
|
|
277
|
-
index,
|
|
278
|
-
parts.slice(1),
|
|
279
|
-
] as const
|
|
280
|
-
}).filter(function ([index]) {
|
|
281
|
-
return index > elementIndex
|
|
282
|
-
}).sort(function ([a], [b]) {
|
|
283
|
-
// ascending
|
|
284
|
-
return a - b
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
runInAction(function () {
|
|
288
|
-
targetPaths.forEach(function ([
|
|
289
|
-
index,
|
|
290
|
-
postfix,
|
|
291
|
-
]) {
|
|
292
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
293
|
-
const fromJsonPath = [
|
|
401
|
+
removeListItem<K extends keyof FlattenedListTypesOfType<T>>(...elementValuePaths: readonly `${K}.${number}`[]) {
|
|
402
|
+
// sort and reverse so we delete last to first so indices of sequential deletions are preserved
|
|
403
|
+
const orderedElementValuePaths = elementValuePaths.toSorted().reverse()
|
|
404
|
+
runInAction(() => {
|
|
405
|
+
orderedElementValuePaths.forEach(elementValuePath => {
|
|
406
|
+
const [
|
|
294
407
|
listValuePath,
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
408
|
+
elementIndexString,
|
|
409
|
+
] = assertExistsAndReturn(
|
|
410
|
+
jsonPathPop(elementValuePath),
|
|
411
|
+
'expected a path with two or more segments {}',
|
|
412
|
+
elementValuePath,
|
|
413
|
+
)
|
|
414
|
+
const accessor = this.accessors[listValuePath]
|
|
415
|
+
const elementIndex = checkValidNumber(
|
|
416
|
+
parseInt(elementIndexString),
|
|
417
|
+
'unexpected index {} ({})',
|
|
418
|
+
elementIndexString,
|
|
419
|
+
elementValuePath,
|
|
420
|
+
)
|
|
421
|
+
const newList = [...accessor.value]
|
|
422
|
+
assertState(
|
|
423
|
+
elementIndex >= 0 && elementIndex < newList.length,
|
|
424
|
+
'invalid index from path {} ({})',
|
|
425
|
+
elementIndex,
|
|
426
|
+
elementValuePath,
|
|
427
|
+
)
|
|
428
|
+
newList.splice(elementIndex, 1)
|
|
429
|
+
|
|
430
|
+
// shuffle the overrides around to account for new indices
|
|
431
|
+
// to so this we need to sort the array indices in descending order
|
|
432
|
+
const targetPaths = Object.keys(this.fieldOverrides).filter(function (v) {
|
|
433
|
+
return v.startsWith(`${listValuePath}.`)
|
|
434
|
+
}).map(function (v) {
|
|
435
|
+
const parts = v.substring(listValuePath.length + 1).split('.')
|
|
436
|
+
const index = parseInt(parts[0])
|
|
437
|
+
return [
|
|
438
|
+
index,
|
|
439
|
+
parts.slice(1),
|
|
440
|
+
] as const
|
|
441
|
+
}).filter(function ([index]) {
|
|
442
|
+
return index > elementIndex
|
|
443
|
+
}).sort(function ([a], [b]) {
|
|
444
|
+
// descending
|
|
445
|
+
return a - b
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
targetPaths.forEach(([
|
|
449
|
+
index,
|
|
450
|
+
postfix,
|
|
451
|
+
]) => {
|
|
452
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
453
|
+
const fromJsonPath = [
|
|
454
|
+
listValuePath,
|
|
455
|
+
`${index}`,
|
|
456
|
+
...postfix,
|
|
457
|
+
].join('.') as keyof ValuePathsToAdapters
|
|
458
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
459
|
+
const toJsonPath = [
|
|
460
|
+
listValuePath,
|
|
461
|
+
`${index - 1}`,
|
|
462
|
+
...postfix,
|
|
463
|
+
].join('.') as keyof ValuePathsToAdapters
|
|
464
|
+
const fieldOverride = this.fieldOverrides[fromJsonPath]
|
|
465
|
+
delete this.fieldOverrides[fromJsonPath]
|
|
466
|
+
this.fieldOverrides[toJsonPath] = fieldOverride
|
|
467
|
+
const error = this.errors[fromJsonPath]
|
|
468
|
+
delete this.errors[fromJsonPath]
|
|
469
|
+
this.errors[toJsonPath] = error
|
|
470
|
+
})
|
|
471
|
+
accessor.set(newList)
|
|
472
|
+
// delete any value overrides so the new list isn't shadowed
|
|
298
473
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
299
|
-
|
|
300
|
-
listValuePath,
|
|
301
|
-
`${index - 1}`,
|
|
302
|
-
...postfix,
|
|
303
|
-
].join('.') as keyof ValuePathsToAdapters
|
|
304
|
-
const fieldOverride = model.fieldOverrides[fromJsonPath]
|
|
305
|
-
delete model.fieldOverrides[fromJsonPath]
|
|
306
|
-
model.fieldOverrides[toJsonPath] = fieldOverride
|
|
307
|
-
const error = model.errors[fromJsonPath]
|
|
308
|
-
delete model.errors[fromJsonPath]
|
|
309
|
-
model.errors[toJsonPath] = error
|
|
474
|
+
delete this.fieldOverrides[listValuePath as keyof ValuePathsToAdapters]
|
|
310
475
|
})
|
|
311
|
-
accessor.set(newList)
|
|
312
|
-
// delete any value overrides so the new list isn't shadowed
|
|
313
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
314
|
-
delete model.fieldOverrides[listValuePath as keyof ValuePathsToAdapters]
|
|
315
476
|
})
|
|
316
477
|
}
|
|
317
478
|
|
|
318
479
|
private internalSetFieldValue<K extends keyof ValuePathsToAdapters>(
|
|
319
|
-
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
320
480
|
valuePath: K,
|
|
321
481
|
value: ToOfFieldAdapter<ValuePathsToAdapters[K]>,
|
|
322
482
|
displayValidation: boolean,
|
|
@@ -326,21 +486,21 @@ export abstract class FormPresenter<
|
|
|
326
486
|
assertExists(revert, 'setting value not supported {}', valuePath)
|
|
327
487
|
|
|
328
488
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
329
|
-
const conversion = revert(value, valuePath as any,
|
|
330
|
-
const accessor =
|
|
489
|
+
const conversion = revert(value, valuePath as any, this.value)
|
|
490
|
+
const accessor = this.getAccessorForValuePath(valuePath)
|
|
331
491
|
return runInAction(() => {
|
|
332
|
-
|
|
492
|
+
this.fieldOverrides[valuePath] = [value]
|
|
333
493
|
switch (conversion.type) {
|
|
334
494
|
case UnreliableFieldConversionType.Failure:
|
|
335
495
|
if (displayValidation) {
|
|
336
|
-
|
|
496
|
+
this.errors[valuePath] = conversion.error
|
|
337
497
|
}
|
|
338
498
|
if (conversion.value != null && accessor != null) {
|
|
339
499
|
accessor.set(conversion.value[0])
|
|
340
500
|
}
|
|
341
501
|
return false
|
|
342
502
|
case UnreliableFieldConversionType.Success:
|
|
343
|
-
delete
|
|
503
|
+
delete this.errors[valuePath]
|
|
344
504
|
accessor?.set(conversion.value)
|
|
345
505
|
return true
|
|
346
506
|
default:
|
|
@@ -349,22 +509,16 @@ export abstract class FormPresenter<
|
|
|
349
509
|
})
|
|
350
510
|
}
|
|
351
511
|
|
|
352
|
-
clearFieldError<K extends keyof ValuePathsToAdapters>(
|
|
353
|
-
|
|
354
|
-
valuePath: K,
|
|
355
|
-
) {
|
|
356
|
-
const fieldOverride = model.fieldOverrides[valuePath]
|
|
512
|
+
clearFieldError<K extends keyof ValuePathsToAdapters>(valuePath: K) {
|
|
513
|
+
const fieldOverride = this.fieldOverrides[valuePath]
|
|
357
514
|
if (fieldOverride != null) {
|
|
358
|
-
runInAction(
|
|
359
|
-
delete
|
|
515
|
+
runInAction(() => {
|
|
516
|
+
delete this.errors[valuePath]
|
|
360
517
|
})
|
|
361
518
|
}
|
|
362
519
|
}
|
|
363
520
|
|
|
364
|
-
clearFieldValue<K extends StringKeyOf<ValueToTypePaths>>(
|
|
365
|
-
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
366
|
-
valuePath: K,
|
|
367
|
-
) {
|
|
521
|
+
clearFieldValue<K extends StringKeyOf<ValueToTypePaths>>(valuePath: K) {
|
|
368
522
|
const typePath = this.typePath(valuePath)
|
|
369
523
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
370
524
|
const adapter = this.adapters[typePath as keyof TypePathsToAdapters]
|
|
@@ -375,41 +529,34 @@ export abstract class FormPresenter<
|
|
|
375
529
|
convert,
|
|
376
530
|
create,
|
|
377
531
|
} = adapter
|
|
378
|
-
const value = create(valuePath,
|
|
532
|
+
const value = create(valuePath, this.value)
|
|
379
533
|
const {
|
|
380
534
|
value: displayValue,
|
|
381
|
-
} = convert(value, valuePath,
|
|
535
|
+
} = convert(value, valuePath, this.value)
|
|
382
536
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
383
537
|
const key = valuePath as unknown as keyof ValuePathsToAdapters
|
|
384
|
-
runInAction(
|
|
385
|
-
|
|
538
|
+
runInAction(() => {
|
|
539
|
+
this.fieldOverrides[key] = [displayValue]
|
|
386
540
|
})
|
|
387
541
|
}
|
|
388
542
|
|
|
389
|
-
clearAll(
|
|
390
|
-
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
391
|
-
value: ValueOfType<T>,
|
|
392
|
-
): void {
|
|
543
|
+
clearAll(value: ValueOfType<T>): void {
|
|
393
544
|
runInAction(() => {
|
|
394
|
-
|
|
545
|
+
this.errors = {}
|
|
395
546
|
// TODO this isn't correct, should reload from value
|
|
396
|
-
|
|
397
|
-
|
|
547
|
+
this.fieldOverrides = {}
|
|
548
|
+
this.value = mobxCopy(this.type, value)
|
|
398
549
|
})
|
|
399
550
|
}
|
|
400
551
|
|
|
401
|
-
isValuePathActive<K extends keyof ValuePathsToAdapters>(
|
|
402
|
-
|
|
403
|
-
valuePath: K,
|
|
404
|
-
): boolean {
|
|
405
|
-
const values = flattenValuesOfType(this.type, model.value)
|
|
552
|
+
isValuePathActive<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean {
|
|
553
|
+
const values = flattenValuesOfType(this.type, this.value)
|
|
406
554
|
const keys = new Set(Object.keys(values))
|
|
407
555
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
408
556
|
return keys.has(valuePath as string)
|
|
409
557
|
}
|
|
410
558
|
|
|
411
559
|
validateField<K extends keyof ValuePathsToAdapters>(
|
|
412
|
-
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
413
560
|
valuePath: K,
|
|
414
561
|
ignoreDefaultValue = false,
|
|
415
562
|
): boolean {
|
|
@@ -418,18 +565,18 @@ export abstract class FormPresenter<
|
|
|
418
565
|
revert,
|
|
419
566
|
create,
|
|
420
567
|
} = this.getAdapterForValuePath(valuePath)
|
|
421
|
-
const fieldOverride =
|
|
422
|
-
const accessor =
|
|
568
|
+
const fieldOverride = this.fieldOverrides[valuePath]
|
|
569
|
+
const accessor = this.getAccessorForValuePath(valuePath)
|
|
423
570
|
const {
|
|
424
571
|
value: storedValue,
|
|
425
572
|
} = convert(
|
|
426
573
|
accessor != null
|
|
427
574
|
? accessor.value
|
|
428
575
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
429
|
-
: create(valuePath as string,
|
|
576
|
+
: create(valuePath as string, this.value),
|
|
430
577
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
431
578
|
valuePath as string,
|
|
432
|
-
|
|
579
|
+
this.value,
|
|
433
580
|
)
|
|
434
581
|
const value = fieldOverride != null
|
|
435
582
|
? fieldOverride[0]
|
|
@@ -439,23 +586,23 @@ export abstract class FormPresenter<
|
|
|
439
586
|
if (ignoreDefaultValue) {
|
|
440
587
|
const {
|
|
441
588
|
value: defaultDisplayValue,
|
|
442
|
-
} = convert(create(valuePath,
|
|
589
|
+
} = convert(create(valuePath, this.value), valuePath, this.value)
|
|
443
590
|
if (defaultDisplayValue === value) {
|
|
444
591
|
return true
|
|
445
592
|
}
|
|
446
593
|
}
|
|
447
594
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
448
|
-
const conversion = revert(value, valuePath as string,
|
|
449
|
-
return runInAction(
|
|
595
|
+
const conversion = revert(value, valuePath as string, this.value)
|
|
596
|
+
return runInAction(() => {
|
|
450
597
|
switch (conversion.type) {
|
|
451
598
|
case UnreliableFieldConversionType.Failure:
|
|
452
|
-
|
|
599
|
+
this.errors[valuePath] = conversion.error
|
|
453
600
|
if (conversion.value != null && accessor != null && dirty) {
|
|
454
601
|
accessor.set(conversion.value[0])
|
|
455
602
|
}
|
|
456
603
|
return false
|
|
457
604
|
case UnreliableFieldConversionType.Success:
|
|
458
|
-
delete
|
|
605
|
+
delete this.errors[valuePath]
|
|
459
606
|
if (accessor != null && dirty) {
|
|
460
607
|
accessor.set(conversion.value)
|
|
461
608
|
}
|
|
@@ -466,9 +613,9 @@ export abstract class FormPresenter<
|
|
|
466
613
|
})
|
|
467
614
|
}
|
|
468
615
|
|
|
469
|
-
validateAll(
|
|
616
|
+
validateAll(): boolean {
|
|
470
617
|
// sort keys shortest to longest so parent changes don't overwrite child changes
|
|
471
|
-
const accessors = toArray(
|
|
618
|
+
const accessors = toArray(this.accessors).toSorted(function ([a], [b]) {
|
|
472
619
|
return a.length - b.length
|
|
473
620
|
})
|
|
474
621
|
return runInAction(() => {
|
|
@@ -495,20 +642,20 @@ export abstract class FormPresenter<
|
|
|
495
642
|
// no convert method means this field is immutable
|
|
496
643
|
return success
|
|
497
644
|
}
|
|
498
|
-
const fieldOverride =
|
|
645
|
+
const fieldOverride = this.fieldOverrides[adapterPath]
|
|
499
646
|
const {
|
|
500
647
|
value: storedValue,
|
|
501
|
-
} = convert(accessor.value, valuePath,
|
|
648
|
+
} = convert(accessor.value, valuePath, this.value)
|
|
502
649
|
const value = fieldOverride != null
|
|
503
650
|
? fieldOverride[0]
|
|
504
651
|
: storedValue
|
|
505
652
|
// TODO more nuanced comparison
|
|
506
653
|
const dirty = fieldOverride != null && fieldOverride[0] !== storedValue
|
|
507
654
|
|
|
508
|
-
const conversion = revert(value, valuePath,
|
|
655
|
+
const conversion = revert(value, valuePath, this.value)
|
|
509
656
|
switch (conversion.type) {
|
|
510
657
|
case UnreliableFieldConversionType.Failure:
|
|
511
|
-
|
|
658
|
+
this.errors[adapterPath] = conversion.error
|
|
512
659
|
if (conversion.value != null && dirty) {
|
|
513
660
|
accessor.set(conversion.value[0])
|
|
514
661
|
}
|
|
@@ -517,7 +664,7 @@ export abstract class FormPresenter<
|
|
|
517
664
|
if (dirty) {
|
|
518
665
|
accessor.set(conversion.value)
|
|
519
666
|
}
|
|
520
|
-
delete
|
|
667
|
+
delete this.errors[adapterPath]
|
|
521
668
|
return success
|
|
522
669
|
default:
|
|
523
670
|
throw new UnreachableError(conversion)
|
|
@@ -527,193 +674,4 @@ export abstract class FormPresenter<
|
|
|
527
674
|
)
|
|
528
675
|
})
|
|
529
676
|
}
|
|
530
|
-
|
|
531
|
-
abstract createModel(value: ValueOfType<ReadonlyTypeOfType<T>>): FormModel<
|
|
532
|
-
T,
|
|
533
|
-
ValueToTypePaths,
|
|
534
|
-
TypePathsToAdapters,
|
|
535
|
-
ValuePathsToAdapters
|
|
536
|
-
>
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
export class FormModel<
|
|
540
|
-
T extends Type,
|
|
541
|
-
ValueToTypePaths extends Readonly<Record<string, string>>,
|
|
542
|
-
TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<
|
|
543
|
-
FlattenedValuesOfType<T, '*'>,
|
|
544
|
-
ValueOfType<ReadonlyTypeOfType<T>>
|
|
545
|
-
>,
|
|
546
|
-
ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<
|
|
547
|
-
TypePathsToAdapters,
|
|
548
|
-
ValueToTypePaths
|
|
549
|
-
>,
|
|
550
|
-
> {
|
|
551
|
-
@observable.ref
|
|
552
|
-
accessor value: MobxValueOfType<T>
|
|
553
|
-
@observable.shallow
|
|
554
|
-
accessor fieldOverrides: FlattenedFieldOverrides<ValuePathsToAdapters>
|
|
555
|
-
@observable.shallow
|
|
556
|
-
accessor errors: FlattenedErrors<ValuePathsToAdapters> = {}
|
|
557
|
-
|
|
558
|
-
private readonly flattenedTypeDefs: Readonly<Record<string, Type>>
|
|
559
|
-
|
|
560
|
-
constructor(
|
|
561
|
-
private readonly type: T,
|
|
562
|
-
value: ValueOfType<ReadonlyTypeOfType<T>>,
|
|
563
|
-
private readonly adapters: TypePathsToAdapters,
|
|
564
|
-
) {
|
|
565
|
-
this.value = mobxCopy(type, value)
|
|
566
|
-
this.flattenedTypeDefs = flattenTypesOfType(type)
|
|
567
|
-
// pre-populate field overrides for consistent behavior when default information is overwritten
|
|
568
|
-
// then returned to
|
|
569
|
-
const conversions = flattenValueTo(
|
|
570
|
-
type,
|
|
571
|
-
this.value,
|
|
572
|
-
() => {},
|
|
573
|
-
(
|
|
574
|
-
_t: StrictTypeDef,
|
|
575
|
-
value: AnyValueType,
|
|
576
|
-
_setter,
|
|
577
|
-
typePath,
|
|
578
|
-
valuePath,
|
|
579
|
-
): AnnotatedFieldConversion<FieldOverride> | undefined => {
|
|
580
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
581
|
-
const adapter = this.adapters[typePath as keyof TypePathsToAdapters]
|
|
582
|
-
if (adapter == null) {
|
|
583
|
-
return
|
|
584
|
-
}
|
|
585
|
-
const {
|
|
586
|
-
convert,
|
|
587
|
-
revert,
|
|
588
|
-
} = adapter
|
|
589
|
-
if (revert == null) {
|
|
590
|
-
// no need to store a temporary value if the value cannot be written back
|
|
591
|
-
return
|
|
592
|
-
}
|
|
593
|
-
return convert(value, valuePath, this.value)
|
|
594
|
-
},
|
|
595
|
-
)
|
|
596
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
597
|
-
this.fieldOverrides = map(conversions, function (_k, v) {
|
|
598
|
-
return v && [v.value]
|
|
599
|
-
}) as FlattenedFieldOverrides<ValuePathsToAdapters>
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
@computed
|
|
603
|
-
get fields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>> {
|
|
604
|
-
return new Proxy<SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>>>(
|
|
605
|
-
this.knownFields,
|
|
606
|
-
{
|
|
607
|
-
get: (target, prop) => {
|
|
608
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
609
|
-
const field = (target as any)[prop]
|
|
610
|
-
if (field != null) {
|
|
611
|
-
return field
|
|
612
|
-
}
|
|
613
|
-
if (typeof prop === 'string') {
|
|
614
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
615
|
-
return this.maybeSynthesizeFieldByValuePath(prop as keyof ValuePathsToAdapters)
|
|
616
|
-
}
|
|
617
|
-
},
|
|
618
|
-
},
|
|
619
|
-
)
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
@computed
|
|
623
|
-
private get knownFields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>> {
|
|
624
|
-
return flattenValueTo(
|
|
625
|
-
this.type,
|
|
626
|
-
this.value,
|
|
627
|
-
() => {},
|
|
628
|
-
// TODO swap these to valuePath, typePath in flatten
|
|
629
|
-
(_t: StrictTypeDef, _v: AnyValueType, _setter, typePath, valuePath): Field | undefined => {
|
|
630
|
-
return this.synthesizeFieldByPaths(
|
|
631
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
632
|
-
valuePath as keyof ValuePathsToAdapters,
|
|
633
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
634
|
-
typePath as keyof TypePathsToAdapters,
|
|
635
|
-
)
|
|
636
|
-
},
|
|
637
|
-
)
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
private maybeSynthesizeFieldByValuePath(valuePath: keyof ValuePathsToAdapters): Field | undefined {
|
|
641
|
-
let typePath: keyof TypePathsToAdapters
|
|
642
|
-
try {
|
|
643
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
644
|
-
typePath = valuePathToTypePath<ValueToTypePaths, keyof ValueToTypePaths>(
|
|
645
|
-
this.type,
|
|
646
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
647
|
-
valuePath as keyof ValueToTypePaths,
|
|
648
|
-
true,
|
|
649
|
-
) as keyof TypePathsToAdapters
|
|
650
|
-
} catch (e) {
|
|
651
|
-
// TODO make jsonValuePathToTypePath return null in the event of an invalid
|
|
652
|
-
// value path instead of throwing an exception
|
|
653
|
-
// assume that the path was invalid
|
|
654
|
-
return
|
|
655
|
-
}
|
|
656
|
-
return this.synthesizeFieldByPaths(valuePath, typePath)
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
private synthesizeFieldByPaths(valuePath: keyof ValuePathsToAdapters, typePath: keyof TypePathsToAdapters) {
|
|
660
|
-
const adapter = this.adapters[typePath]
|
|
661
|
-
if (adapter == null) {
|
|
662
|
-
// invalid path, which can happen
|
|
663
|
-
return
|
|
664
|
-
}
|
|
665
|
-
const {
|
|
666
|
-
convert,
|
|
667
|
-
create,
|
|
668
|
-
} = adapter
|
|
669
|
-
|
|
670
|
-
const fieldOverride = this.fieldOverrides[valuePath]
|
|
671
|
-
const accessor = this.getAccessorForValuePath(valuePath)
|
|
672
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
673
|
-
const fieldTypeDef = this.flattenedTypeDefs[typePath as string]
|
|
674
|
-
const {
|
|
675
|
-
value,
|
|
676
|
-
required,
|
|
677
|
-
readonly,
|
|
678
|
-
} = convert(
|
|
679
|
-
accessor != null
|
|
680
|
-
? accessor.value
|
|
681
|
-
: fieldTypeDef != null
|
|
682
|
-
? mobxCopy(
|
|
683
|
-
fieldTypeDef,
|
|
684
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
685
|
-
create(valuePath as string, this.value),
|
|
686
|
-
)
|
|
687
|
-
// fake values can't be copied
|
|
688
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
689
|
-
: create(valuePath as string, this.value),
|
|
690
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
691
|
-
valuePath as string,
|
|
692
|
-
this.value,
|
|
693
|
-
)
|
|
694
|
-
const error = this.errors[valuePath]
|
|
695
|
-
return {
|
|
696
|
-
value: fieldOverride != null ? fieldOverride[0] : value,
|
|
697
|
-
error,
|
|
698
|
-
readonly,
|
|
699
|
-
required,
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
getAccessorForValuePath(valuePath: keyof ValuePathsToAdapters): Accessor | undefined {
|
|
704
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
705
|
-
return this.accessors[valuePath as string]
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
@computed
|
|
709
|
-
// should only be referenced internally, so loosely typed
|
|
710
|
-
get accessors(): Readonly<Record<string, Accessor>> {
|
|
711
|
-
return flattenAccessorsOfType<T, Readonly<Record<string, Accessor>>>(
|
|
712
|
-
this.type,
|
|
713
|
-
this.value,
|
|
714
|
-
(value: ValueOfType<T>): void => {
|
|
715
|
-
this.value = mobxCopy(this.type, value)
|
|
716
|
-
},
|
|
717
|
-
)
|
|
718
|
-
}
|
|
719
677
|
}
|