@projectcaluma/ember-form 10.0.1 → 11.0.0-beta.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/CHANGELOG.md +1099 -0
- package/addon/components/cf-content.hbs +36 -39
- package/addon/components/cf-content.js +48 -29
- package/addon/components/cf-field/input/action-button.hbs +1 -1
- package/addon/components/cf-field/input/action-button.js +9 -7
- package/addon/components/cf-field/input/checkbox.hbs +6 -2
- package/addon/components/cf-field/input/checkbox.js +9 -29
- package/addon/components/cf-field/input/file.js +8 -9
- package/addon/components/cf-field/input/float.hbs +4 -4
- package/addon/components/cf-field/input/integer.hbs +5 -5
- package/addon/components/cf-field/input/radio.hbs +4 -1
- package/addon/components/cf-field/input/table.hbs +2 -2
- package/addon/components/cf-field/input/table.js +12 -10
- package/addon/components/cf-field/input/text.hbs +5 -5
- package/addon/components/cf-field/input/textarea.hbs +6 -5
- package/addon/components/cf-field/input.hbs +10 -1
- package/addon/components/cf-field/input.js +1 -1
- package/addon/components/cf-field/label.hbs +1 -1
- package/addon/components/cf-field-value.js +8 -13
- package/addon/components/cf-field.hbs +2 -2
- package/addon/components/cf-field.js +2 -3
- package/addon/components/cf-navigation-item.hbs +2 -2
- package/addon/components/cf-navigation.hbs +4 -1
- package/addon/components/document-validity.js +1 -1
- package/addon/gql/fragments/field.graphql +27 -0
- package/addon/gql/mutations/save-document-table-answer.graphql +1 -1
- package/addon/gql/mutations/save-document.graphql +1 -0
- package/addon/gql/queries/{get-document-answers.graphql → document-answers.graphql} +2 -1
- package/addon/gql/queries/{get-document-forms.graphql → document-forms.graphql} +2 -1
- package/addon/gql/queries/{get-document-used-dynamic-options.graphql → document-used-dynamic-options.graphql} +2 -1
- package/addon/gql/queries/{get-dynamic-options.graphql → dynamic-options.graphql} +2 -1
- package/addon/gql/queries/{get-fileanswer-info.graphql → fileanswer-info.graphql} +2 -1
- package/addon/helpers/get-widget.js +50 -0
- package/addon/lib/answer.js +108 -72
- package/addon/lib/base.js +32 -23
- package/addon/lib/dependencies.js +36 -71
- package/addon/lib/document.js +92 -96
- package/addon/lib/field.js +334 -401
- package/addon/lib/fieldset.js +46 -47
- package/addon/lib/form.js +27 -15
- package/addon/lib/navigation.js +211 -192
- package/addon/lib/question.js +103 -94
- package/addon/services/caluma-store.js +10 -6
- package/app/helpers/get-widget.js +4 -0
- package/blueprints/@projectcaluma/ember-form/index.js +1 -0
- package/package.json +26 -22
- package/addon/components/cf-navigation.js +0 -9
package/addon/lib/navigation.js
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
import { getOwner } from "@ember/application";
|
2
2
|
import { assert } from "@ember/debug";
|
3
|
-
import {
|
4
|
-
|
5
|
-
|
3
|
+
import {
|
4
|
+
associateDestroyableChild,
|
5
|
+
registerDestructor,
|
6
|
+
} from "@ember/destroyable";
|
7
|
+
import { action } from "@ember/object";
|
8
|
+
import { next, cancel, once } from "@ember/runloop";
|
6
9
|
import { inject as service } from "@ember/service";
|
10
|
+
import { cached } from "tracked-toolbox";
|
7
11
|
|
8
12
|
import Base from "@projectcaluma/ember-form/lib/base";
|
9
13
|
|
@@ -19,87 +23,112 @@ const STATES = {
|
|
19
23
|
*
|
20
24
|
* @class NavigationItem
|
21
25
|
*/
|
22
|
-
export
|
23
|
-
router
|
26
|
+
export class NavigationItem extends Base {
|
27
|
+
@service router;
|
24
28
|
|
25
|
-
|
26
|
-
assert("
|
27
|
-
assert("
|
29
|
+
constructor({ fieldset, navigation, ...args }) {
|
30
|
+
assert("`fieldset` must be passed as an argument", fieldset);
|
31
|
+
assert("`navigation` must be passed as an argument", navigation);
|
28
32
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
+
super({ ...args });
|
34
|
+
|
35
|
+
this.fieldset = fieldset;
|
36
|
+
this.navigation = navigation;
|
37
|
+
|
38
|
+
this.pushIntoStore();
|
39
|
+
}
|
40
|
+
|
41
|
+
/**
|
42
|
+
* The fieldset to build the navigation item for
|
43
|
+
*
|
44
|
+
* @property {Fieldset} fieldset
|
45
|
+
*/
|
46
|
+
fieldset = null;
|
33
47
|
|
34
|
-
|
35
|
-
|
48
|
+
/**
|
49
|
+
* The navigation object this item originates from. This is used to determine
|
50
|
+
* the items child and parent items.
|
51
|
+
*
|
52
|
+
* @property {Navigation} navigation
|
53
|
+
*/
|
54
|
+
navigation = null;
|
55
|
+
|
56
|
+
/**
|
57
|
+
* The primary key of the navigation item.
|
58
|
+
*
|
59
|
+
* @property {String} pk
|
60
|
+
*/
|
61
|
+
@cached
|
62
|
+
get pk() {
|
63
|
+
return `NavigationItem:${this.fieldset.pk}`;
|
64
|
+
}
|
36
65
|
|
37
66
|
/**
|
38
67
|
* The parent navigation item
|
39
68
|
*
|
40
69
|
* @property {NavigationItem} parent
|
41
|
-
* @accessor
|
42
70
|
*/
|
43
|
-
|
71
|
+
@cached
|
72
|
+
get parent() {
|
44
73
|
return this.navigation.items.find((item) => item.slug === this._parentSlug);
|
45
|
-
}
|
74
|
+
}
|
46
75
|
|
47
76
|
/**
|
48
77
|
* The children of this navigation item
|
49
78
|
*
|
50
79
|
* @property {NavigationItem[]} children
|
51
|
-
* @accessor
|
52
80
|
*/
|
53
|
-
|
81
|
+
@cached
|
82
|
+
get children() {
|
54
83
|
return this.navigation.items.filter(
|
55
84
|
(item) => item._parentSlug === this.slug
|
56
85
|
);
|
57
|
-
}
|
86
|
+
}
|
58
87
|
|
59
88
|
/**
|
60
89
|
* The visible children of this navigation item
|
61
90
|
*
|
62
91
|
* @property {NavigationItem[]} visibleChildren
|
63
|
-
* @accessor
|
64
92
|
*/
|
65
|
-
|
93
|
+
@cached
|
94
|
+
get visibleChildren() {
|
66
95
|
return this.children.filter((child) => child.visible);
|
67
|
-
}
|
68
|
-
|
69
|
-
/**
|
70
|
-
* The fieldset to build the navigation item for
|
71
|
-
*
|
72
|
-
* @property {Fieldset} fieldset
|
73
|
-
*/
|
74
|
-
fieldset: null,
|
96
|
+
}
|
75
97
|
|
76
98
|
/**
|
77
99
|
* The label displayed in the navigation
|
78
100
|
*
|
79
101
|
* @property {String} label
|
80
|
-
* @accessor
|
81
102
|
*/
|
82
|
-
|
103
|
+
@cached
|
104
|
+
get label() {
|
105
|
+
return (
|
106
|
+
this.fieldset.field?.question.raw.label ?? this.fieldset.form.raw.name
|
107
|
+
);
|
108
|
+
}
|
83
109
|
|
84
110
|
/**
|
85
111
|
* The slug of the items form
|
86
112
|
*
|
87
113
|
* @property {String} slug
|
88
|
-
* @accessor
|
89
114
|
*/
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
115
|
+
@cached
|
116
|
+
get slug() {
|
117
|
+
return (
|
118
|
+
this.fieldset.field?.question.raw.subForm.slug ?? this.fieldset.form.slug
|
119
|
+
);
|
120
|
+
}
|
94
121
|
|
95
122
|
/**
|
96
123
|
* The slug of the parent items form
|
97
124
|
*
|
98
125
|
* @property {String} _parentSlug
|
99
|
-
* @accessor
|
100
126
|
* @private
|
101
127
|
*/
|
102
|
-
|
128
|
+
@cached
|
129
|
+
get _parentSlug() {
|
130
|
+
return this.fieldset.field?.fieldset.field?.question.raw.subForm.slug;
|
131
|
+
}
|
103
132
|
|
104
133
|
/**
|
105
134
|
* The item is active if the query param `displayedForm` is equal to the
|
@@ -107,24 +136,20 @@ export const NavigationItem = Base.extend({
|
|
107
136
|
*
|
108
137
|
* @property {Boolean} active
|
109
138
|
*/
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
return (
|
115
|
-
this.slug === this.get("router.currentRoute.queryParams.displayedForm")
|
116
|
-
);
|
117
|
-
}
|
118
|
-
),
|
139
|
+
@cached
|
140
|
+
get active() {
|
141
|
+
return this.slug === this.router.currentRoute?.queryParams.displayedForm;
|
142
|
+
}
|
119
143
|
|
120
144
|
/**
|
121
145
|
* Whether the item has active children
|
122
146
|
*
|
123
147
|
* @property {Boolean} childrenActive
|
124
148
|
*/
|
125
|
-
|
149
|
+
@cached
|
150
|
+
get childrenActive() {
|
126
151
|
return this.children.some((child) => child.active);
|
127
|
-
}
|
152
|
+
}
|
128
153
|
|
129
154
|
/**
|
130
155
|
* The item is navigable if it is not hidden and its fieldset contains at
|
@@ -132,27 +157,25 @@ export const NavigationItem = Base.extend({
|
|
132
157
|
*
|
133
158
|
* @property {Boolean} navigable
|
134
159
|
*/
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
(
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
);
|
145
|
-
}
|
146
|
-
),
|
160
|
+
@cached
|
161
|
+
get navigable() {
|
162
|
+
return (
|
163
|
+
(this.fieldset.field === undefined || !this.fieldset.field.hidden) &&
|
164
|
+
this.fieldset.fields.some(
|
165
|
+
(field) => field.questionType !== "FormQuestion" && !field.hidden
|
166
|
+
)
|
167
|
+
);
|
168
|
+
}
|
147
169
|
|
148
170
|
/**
|
149
171
|
* The item is visible if it is navigable or has at least one child item.
|
150
172
|
*
|
151
173
|
* @property {Boolean} visible
|
152
174
|
*/
|
153
|
-
|
175
|
+
@cached
|
176
|
+
get visible() {
|
154
177
|
return this.navigable || Boolean(this.visibleChildren.length);
|
155
|
-
}
|
178
|
+
}
|
156
179
|
|
157
180
|
/**
|
158
181
|
* The current state consisting of the items and the childrens fieldset
|
@@ -165,64 +188,53 @@ export const NavigationItem = Base.extend({
|
|
165
188
|
* - `valid` if every fieldset is `valid`
|
166
189
|
*
|
167
190
|
* @property {String} state
|
168
|
-
* @accessor
|
169
191
|
*/
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
if (states.some((state) => state === STATES.INVALID)) {
|
184
|
-
return STATES.INVALID;
|
185
|
-
}
|
186
|
-
|
187
|
-
return states.every((state) => state === STATES.VALID)
|
188
|
-
? STATES.VALID
|
189
|
-
: STATES.IN_PROGRESS;
|
192
|
+
@cached
|
193
|
+
get state() {
|
194
|
+
const states = [
|
195
|
+
this.fieldsetState,
|
196
|
+
...this.visibleChildren.map((child) => child.fieldsetState),
|
197
|
+
].filter(Boolean);
|
198
|
+
|
199
|
+
if (states.every((state) => state === STATES.EMPTY)) {
|
200
|
+
return STATES.EMPTY;
|
201
|
+
}
|
202
|
+
|
203
|
+
if (states.some((state) => state === STATES.INVALID)) {
|
204
|
+
return STATES.INVALID;
|
190
205
|
}
|
191
|
-
|
206
|
+
|
207
|
+
return states.every((state) => state === STATES.VALID)
|
208
|
+
? STATES.VALID
|
209
|
+
: STATES.IN_PROGRESS;
|
210
|
+
}
|
192
211
|
|
193
212
|
/**
|
194
213
|
* The dirty state of the navigation item. This will be true if at least one
|
195
214
|
* of the children or the navigation fieldset itself is dirty.
|
196
215
|
*
|
197
216
|
* @property {Boolean} fieldsetDirty
|
198
|
-
* @accessor
|
199
217
|
*/
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
].some(Boolean);
|
208
|
-
}
|
209
|
-
),
|
218
|
+
@cached
|
219
|
+
get dirty() {
|
220
|
+
return [
|
221
|
+
this.fieldsetDirty,
|
222
|
+
...this.visibleChildren.map((child) => child.fieldsetDirty),
|
223
|
+
].some(Boolean);
|
224
|
+
}
|
210
225
|
|
211
226
|
/**
|
212
227
|
* All visible fields (excluding form question fields) of the fieldset that
|
213
228
|
* are visible.
|
214
229
|
*
|
215
230
|
* @property {Field[]} visibleFields
|
216
|
-
* @accessor
|
217
231
|
*/
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
}
|
225
|
-
),
|
232
|
+
@cached
|
233
|
+
get visibleFields() {
|
234
|
+
return this.fieldset.fields.filter(
|
235
|
+
(f) => f.questionType !== "FormQuestion" && !f.hidden
|
236
|
+
);
|
237
|
+
}
|
226
238
|
|
227
239
|
/**
|
228
240
|
* The current state of the item's fieldset. This does not consider the state
|
@@ -235,137 +247,147 @@ export const NavigationItem = Base.extend({
|
|
235
247
|
* - `valid` if every field is valid
|
236
248
|
*
|
237
249
|
* @property {String} fieldsetState
|
238
|
-
* @accessor
|
239
250
|
*/
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
return STATES.INVALID;
|
249
|
-
}
|
250
|
-
|
251
|
-
if (this.visibleFields.every((f) => f.isNew)) {
|
252
|
-
return STATES.EMPTY;
|
253
|
-
}
|
254
|
-
|
255
|
-
if (
|
256
|
-
this.visibleFields
|
257
|
-
.filter((f) => !f.optional)
|
258
|
-
.every((f) => f.isValid && !f.isNew)
|
259
|
-
) {
|
260
|
-
return STATES.VALID;
|
261
|
-
}
|
262
|
-
|
263
|
-
return STATES.IN_PROGRESS;
|
251
|
+
@cached
|
252
|
+
get fieldsetState() {
|
253
|
+
if (!this.visibleFields.length) {
|
254
|
+
return null;
|
255
|
+
}
|
256
|
+
|
257
|
+
if (this.visibleFields.some((f) => !f.isValid && f.isDirty)) {
|
258
|
+
return STATES.INVALID;
|
264
259
|
}
|
265
|
-
|
260
|
+
|
261
|
+
if (this.visibleFields.every((f) => f.isNew)) {
|
262
|
+
return STATES.EMPTY;
|
263
|
+
}
|
264
|
+
|
265
|
+
if (
|
266
|
+
this.visibleFields
|
267
|
+
.filter((f) => !f.optional)
|
268
|
+
.every((f) => f.isValid && !f.isNew)
|
269
|
+
) {
|
270
|
+
return STATES.VALID;
|
271
|
+
}
|
272
|
+
|
273
|
+
return STATES.IN_PROGRESS;
|
274
|
+
}
|
266
275
|
|
267
276
|
/**
|
268
277
|
* The dirty state of the current fieldset. This will be true if at least one
|
269
278
|
* field in the fieldset is dirty.
|
270
279
|
*
|
271
280
|
* @property {Boolean} fieldsetDirty
|
272
|
-
* @accessor
|
273
281
|
*/
|
274
|
-
|
282
|
+
@cached
|
283
|
+
get fieldsetDirty() {
|
275
284
|
return this.visibleFields.some((f) => f.isDirty);
|
276
|
-
}
|
277
|
-
}
|
285
|
+
}
|
286
|
+
}
|
278
287
|
|
279
288
|
/**
|
280
289
|
* Object to represent a navigation state for a certain document.
|
281
290
|
*
|
282
291
|
* @class Navigation
|
283
292
|
*/
|
284
|
-
export
|
285
|
-
router
|
286
|
-
calumaStore: service(),
|
293
|
+
export class Navigation extends Base {
|
294
|
+
@service router;
|
287
295
|
|
288
|
-
|
289
|
-
assert("
|
296
|
+
constructor({ document, ...args }) {
|
297
|
+
assert("`document` must be passed as an argument", document);
|
290
298
|
|
291
|
-
|
292
|
-
writable: false,
|
293
|
-
value: `Navigation:${this.document.pk}`,
|
294
|
-
});
|
299
|
+
super({ ...args });
|
295
300
|
|
296
|
-
this.
|
301
|
+
this.document = document;
|
297
302
|
|
298
|
-
this.
|
303
|
+
this.pushIntoStore();
|
299
304
|
|
300
305
|
this._createItems();
|
301
|
-
},
|
302
306
|
|
303
|
-
|
304
|
-
|
307
|
+
const transitionHandler = () => {
|
308
|
+
this._timer = next(this, "goToNextItemIfNonNavigable");
|
309
|
+
};
|
305
310
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
311
|
+
// go to next item in next run loop, this is necessary when the user clicks
|
312
|
+
// on a non navigable item in the navigation
|
313
|
+
this.router.on("routeDidChange", this, transitionHandler);
|
314
|
+
|
315
|
+
registerDestructor(this, () => {
|
316
|
+
cancel(this._timer);
|
317
|
+
this.router.off("routeDidChange", this, transitionHandler);
|
318
|
+
});
|
319
|
+
}
|
320
|
+
|
321
|
+
/**
|
322
|
+
* The primary key of the navigation
|
323
|
+
*
|
324
|
+
* @property {String} pk
|
325
|
+
*/
|
326
|
+
@cached
|
327
|
+
get pk() {
|
328
|
+
return `Navigation:${this.document.pk}`;
|
329
|
+
}
|
310
330
|
|
311
331
|
_createItems() {
|
332
|
+
const owner = getOwner(this);
|
333
|
+
|
312
334
|
const items = this.document.fieldsets.map((fieldset) => {
|
313
335
|
const pk = `NavigationItem:${fieldset.pk}`;
|
314
336
|
|
315
|
-
return (
|
337
|
+
return associateDestroyableChild(
|
338
|
+
this,
|
316
339
|
this.calumaStore.find(pk) ||
|
317
|
-
|
318
|
-
|
319
|
-
|
340
|
+
new (owner.factoryFor("caluma-model:navigation-item").class)({
|
341
|
+
fieldset,
|
342
|
+
navigation: this,
|
343
|
+
owner,
|
344
|
+
})
|
320
345
|
);
|
321
346
|
});
|
322
347
|
|
323
|
-
this.
|
324
|
-
}
|
348
|
+
this.items = items;
|
349
|
+
}
|
325
350
|
|
326
351
|
/**
|
327
352
|
* The document to build the navigation for
|
328
353
|
*
|
329
354
|
* @property {Document} document
|
330
355
|
*/
|
331
|
-
document
|
356
|
+
document = null;
|
332
357
|
|
333
358
|
/**
|
334
359
|
* The navigation items for the given document
|
335
360
|
*
|
336
361
|
* @property {NavigationItem[]} items
|
337
|
-
* @accessor
|
338
362
|
*/
|
339
|
-
items
|
363
|
+
items = [];
|
340
364
|
|
341
365
|
/**
|
342
366
|
* The top level navigation items. Those are items without a parent item that
|
343
367
|
* are visible.
|
344
368
|
*
|
345
369
|
* @property {NavigationItem[]} rootItems
|
346
|
-
* @accessor
|
347
370
|
*/
|
348
|
-
|
371
|
+
@cached
|
372
|
+
get rootItems() {
|
349
373
|
return this.items.filter((i) => !i.parent && i.visible);
|
350
|
-
}
|
374
|
+
}
|
351
375
|
|
352
376
|
/**
|
353
377
|
* The currently active navigation item
|
354
378
|
*
|
355
379
|
* @property {NavigationItem} currentItem
|
356
|
-
* @accessor
|
357
380
|
*/
|
358
|
-
currentItem
|
381
|
+
get currentItem() {
|
359
382
|
return this.items.find((item) => item.active);
|
360
|
-
}
|
383
|
+
}
|
361
384
|
|
362
385
|
/**
|
363
386
|
* The next navigable item in the navigation tree
|
364
387
|
*
|
365
388
|
* @property {NavigationItem} nextItem
|
366
|
-
* @accessor
|
367
389
|
*/
|
368
|
-
nextItem
|
390
|
+
get nextItem() {
|
369
391
|
if (!this.currentItem)
|
370
392
|
return this.items.filter((item) => item.navigable)[0];
|
371
393
|
|
@@ -374,15 +396,14 @@ export const Navigation = Base.extend({
|
|
374
396
|
.filter((item) => item.navigable);
|
375
397
|
|
376
398
|
return items.length ? items[0] : null;
|
377
|
-
}
|
399
|
+
}
|
378
400
|
|
379
401
|
/**
|
380
402
|
* The previous navigable item in the navigation tree
|
381
403
|
*
|
382
404
|
* @property {NavigationItem} previousItem
|
383
|
-
* @accessor
|
384
405
|
*/
|
385
|
-
previousItem
|
406
|
+
get previousItem() {
|
386
407
|
if (!this.currentItem) return null;
|
387
408
|
|
388
409
|
const items = this.items
|
@@ -390,36 +411,34 @@ export const Navigation = Base.extend({
|
|
390
411
|
.filter((item) => item.navigable);
|
391
412
|
|
392
413
|
return items.length ? items[items.length - 1] : null;
|
393
|
-
}
|
414
|
+
}
|
394
415
|
|
395
416
|
/**
|
396
|
-
*
|
397
|
-
* is not navigable.
|
417
|
+
* Replace the current item with the next navigable item if the current item
|
418
|
+
* is not navigable. This makes sure that only one transition per runloop
|
419
|
+
* happens.
|
398
420
|
*
|
399
|
-
* @method
|
421
|
+
* @method goToNextItemIfNonNavigable
|
400
422
|
*/
|
401
|
-
|
402
|
-
|
423
|
+
@action
|
424
|
+
goToNextItemIfNonNavigable() {
|
425
|
+
if (this.currentItem?.navigable) {
|
403
426
|
return;
|
404
427
|
}
|
405
428
|
|
406
|
-
|
407
|
-
}
|
429
|
+
once(this, "_transitionToNextItem");
|
430
|
+
}
|
408
431
|
|
409
432
|
/**
|
410
|
-
*
|
433
|
+
* Transition to next item or start (empty displayed form).
|
411
434
|
*
|
412
|
-
* @method
|
435
|
+
* @method _transitionToNextItem
|
413
436
|
*/
|
414
|
-
|
415
|
-
if (!this.get("nextItem.slug") || this.get("currentItem.navigable")) {
|
416
|
-
return;
|
417
|
-
}
|
418
|
-
|
437
|
+
_transitionToNextItem() {
|
419
438
|
this.router.replaceWith({
|
420
|
-
queryParams: { displayedForm: this.nextItem
|
439
|
+
queryParams: { displayedForm: this.nextItem?.slug ?? "" },
|
421
440
|
});
|
422
|
-
}
|
423
|
-
}
|
441
|
+
}
|
442
|
+
}
|
424
443
|
|
425
444
|
export default Navigation;
|