@ditojs/admin 3.1.0 → 3.1.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/dist/dito-admin.es.js +1896 -1869
- package/dist/dito-admin.umd.js +5 -5
- package/package.json +5 -5
- package/src/mixins/ResourceMixin.js +4 -2
- package/src/types/DitoTypeTreeList.vue +4 -3
- package/src/utils/FormModel.js +15 -11
- package/src/utils/schema/data.js +42 -5
- package/src/utils/schema/lookup.js +52 -15
- package/src/utils/schema/validation.js +2 -4
- package/types/index.d.ts +13 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/admin",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dito.js Admin is a schema based admin interface for Dito.js Server, featuring auto-generated views and forms and built with Vue.js",
|
|
6
6
|
"repository": "https://github.com/ditojs/dito/tree/main/packages/admin",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"not ie_mob > 0"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@ditojs/ui": "^3.1.
|
|
45
|
-
"@ditojs/utils": "^3.1.
|
|
44
|
+
"@ditojs/ui": "^3.1.2",
|
|
45
|
+
"@ditojs/utils": "^3.1.2"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@kyvg/vue3-notification": "^3.4.2",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"vue-upload-component": "^3.2.2"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
|
-
"@ditojs/build": "^3.1.
|
|
90
|
+
"@ditojs/build": "^3.1.2",
|
|
91
91
|
"@kyvg/vue3-notification": "^3.4.2",
|
|
92
92
|
"@tiptap/core": "^3.31.4",
|
|
93
93
|
"@tiptap/extension-blockquote": "^3.31.4",
|
|
@@ -131,5 +131,5 @@
|
|
|
131
131
|
"vue-router": "^5.3.1",
|
|
132
132
|
"vue-upload-component": "^3.2.2"
|
|
133
133
|
},
|
|
134
|
-
"gitHead": "
|
|
134
|
+
"gitHead": "a87ccff01f65386d9363366fb19bb993d38e8fbc"
|
|
135
135
|
}
|
|
@@ -54,8 +54,10 @@ export default {
|
|
|
54
54
|
// `isTransient()` to only return `!this.providesData`.
|
|
55
55
|
const form = this.formComponent
|
|
56
56
|
return (
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
(
|
|
58
|
+
!this.providesData &&
|
|
59
|
+
!this.linksToView
|
|
60
|
+
) ||
|
|
59
61
|
form && (
|
|
60
62
|
form.isTransient ||
|
|
61
63
|
form.isCreating
|
package/src/utils/FormModel.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from '@ditojs/utils'
|
|
10
10
|
import DitoContext from '../DitoContext.js'
|
|
11
11
|
import { DataSchemaResolver } from './DataSchemaResolver.js'
|
|
12
|
-
import { isNested
|
|
12
|
+
import { isNested } from './schema/structure.js'
|
|
13
13
|
import { isEmptySchema } from './schema/lookup.js'
|
|
14
14
|
import {
|
|
15
15
|
getSchemaValue,
|
|
@@ -295,7 +295,7 @@ export class FormModel {
|
|
|
295
295
|
initializeData(schema, data, this.component, {
|
|
296
296
|
dataPath: this.dataPath,
|
|
297
297
|
rootData: this.rootData,
|
|
298
|
-
|
|
298
|
+
shouldSkipSourcesWithResource: true,
|
|
299
299
|
// The model writes the values of components with `compute()`,
|
|
300
300
|
// including their defaults:
|
|
301
301
|
shouldSetDefaultsOfComponentsWithCompute: false
|
|
@@ -318,9 +318,8 @@ export class FormModel {
|
|
|
318
318
|
processSchemaData(schema, data, {
|
|
319
319
|
dataPath: this.dataPath,
|
|
320
320
|
componentPath: this.componentPath,
|
|
321
|
-
shouldProcess: entry => (
|
|
322
|
-
|
|
323
|
-
),
|
|
321
|
+
shouldProcess: entry => this.isEntryShown(entry),
|
|
322
|
+
shouldSkipSourcesWithResource: true,
|
|
324
323
|
before: entry => {
|
|
325
324
|
if (hasComputedValueSource(entry.schema)) {
|
|
326
325
|
computedValueEntries.set(entry.componentPath, entry)
|
|
@@ -585,12 +584,17 @@ function takeOverChangedValues(target, before, after) {
|
|
|
585
584
|
return target
|
|
586
585
|
}
|
|
587
586
|
const isSameShape = (
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
587
|
+
(
|
|
588
|
+
isPlainObject(before) &&
|
|
589
|
+
isPlainObject(after) &&
|
|
590
|
+
isPlainObject(target)
|
|
591
|
+
) || (
|
|
592
|
+
isArray(before) &&
|
|
593
|
+
isArray(after) &&
|
|
594
|
+
isArray(target) &&
|
|
595
|
+
before.length === after.length &&
|
|
596
|
+
after.length === target.length
|
|
597
|
+
)
|
|
594
598
|
)
|
|
595
599
|
if (!isSameShape) {
|
|
596
600
|
return clone(after)
|
package/src/utils/schema/data.js
CHANGED
|
@@ -16,12 +16,14 @@ import {
|
|
|
16
16
|
someNestedSchemaComponent,
|
|
17
17
|
hasNestedSchemaComponents,
|
|
18
18
|
isPanelWithOwnData,
|
|
19
|
+
isSourceWithResource,
|
|
19
20
|
isNested
|
|
20
21
|
} from './structure.js'
|
|
21
22
|
import {
|
|
22
23
|
hasFormSchema,
|
|
23
24
|
hasMultipleFormSchemas,
|
|
24
25
|
getFormSchemas,
|
|
26
|
+
getItemFormSchema,
|
|
25
27
|
getItemFormSchemaFromForms
|
|
26
28
|
} from './lookup.js'
|
|
27
29
|
|
|
@@ -129,9 +131,10 @@ export function isMissingValue(schema, data, name, context) {
|
|
|
129
131
|
// Only read the value if needed, so that callers that are tracked, e.g. the
|
|
130
132
|
// walk of the form model's data, don't depend on all values.
|
|
131
133
|
return (
|
|
132
|
-
!(name in data) ||
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
!(name in data) || (
|
|
135
|
+
!!getTypeOptions(schema)?.treatNullAsMissing?.(getContext(context)) &&
|
|
136
|
+
data[name] === null
|
|
137
|
+
)
|
|
135
138
|
)
|
|
136
139
|
}
|
|
137
140
|
|
|
@@ -178,6 +181,7 @@ export function initializeData(schema, data = {}, component, {
|
|
|
178
181
|
dataPath = null,
|
|
179
182
|
rootData = data,
|
|
180
183
|
shouldProcess,
|
|
184
|
+
shouldSkipSourcesWithResource = false,
|
|
181
185
|
// Whether to also set the defaults of components with `compute()`. New data
|
|
182
186
|
// starts with all defaults, which `compute()` can rely on. In data that was
|
|
183
187
|
// loaded, `compute()` may derive missing values instead, and they fall back
|
|
@@ -214,6 +218,7 @@ export function initializeData(schema, data = {}, component, {
|
|
|
214
218
|
dataPath,
|
|
215
219
|
before,
|
|
216
220
|
shouldProcess,
|
|
221
|
+
shouldSkipSourcesWithResource,
|
|
217
222
|
options
|
|
218
223
|
})
|
|
219
224
|
}
|
|
@@ -429,6 +434,11 @@ export function processData(schema, sourceSchema, data, dataPath, {
|
|
|
429
434
|
* them contain the `componentPath` of the component, tab or panel, continuing
|
|
430
435
|
* `componentPath` like `DitoMixin.componentPath` does.
|
|
431
436
|
*
|
|
437
|
+
* With `shouldSkipSourcesWithResource`, sources with their own resource are
|
|
438
|
+
* skipped along with their items, which they load and save through their
|
|
439
|
+
* resource, but not their panels, which display the data that contains them,
|
|
440
|
+
* see `DitoContainer.panelEntries`.
|
|
441
|
+
*
|
|
432
442
|
* The primitive values of lists with `wrapPrimitives` are walked wrapped in
|
|
433
443
|
* objects under the `wrapPrimitives` key, like the admin edits them, see
|
|
434
444
|
* `SourceMixin`. `wrappedPrimitiveName` is that key while walking such an
|
|
@@ -444,9 +454,16 @@ export function processSchemaData(schema, data, {
|
|
|
444
454
|
before = null,
|
|
445
455
|
after = null,
|
|
446
456
|
shouldProcess = () => true,
|
|
457
|
+
shouldSkipSourcesWithResource = false,
|
|
447
458
|
options
|
|
448
459
|
}) {
|
|
449
|
-
const walkOptions = {
|
|
460
|
+
const walkOptions = {
|
|
461
|
+
before,
|
|
462
|
+
after,
|
|
463
|
+
shouldProcess,
|
|
464
|
+
shouldSkipSourcesWithResource,
|
|
465
|
+
options
|
|
466
|
+
}
|
|
450
467
|
const getDataPath = (dataPath, token) =>
|
|
451
468
|
dataPath != null
|
|
452
469
|
? appendDataPath(dataPath, token)
|
|
@@ -472,6 +489,13 @@ export function processSchemaData(schema, data, {
|
|
|
472
489
|
if (!shouldProcess(entry)) {
|
|
473
490
|
continue
|
|
474
491
|
}
|
|
492
|
+
if (
|
|
493
|
+
shouldSkipSourcesWithResource &&
|
|
494
|
+
isSourceWithResource(componentSchema)
|
|
495
|
+
) {
|
|
496
|
+
processTabOrPanelSchemas(componentSchema.panels, entry.componentPath)
|
|
497
|
+
continue
|
|
498
|
+
}
|
|
475
499
|
if (isPanelWithOwnData(componentSchema)) {
|
|
476
500
|
// The components of panels with their own data don't edit `data`.
|
|
477
501
|
continue
|
|
@@ -582,7 +606,20 @@ export function processSchemaData(schema, data, {
|
|
|
582
606
|
}
|
|
583
607
|
}
|
|
584
608
|
|
|
585
|
-
|
|
609
|
+
const components = isFunction(schema.components)
|
|
610
|
+
? // Unnested components with `components()` callbacks, e.g. sections:
|
|
611
|
+
getItemFormSchema(
|
|
612
|
+
schema,
|
|
613
|
+
data,
|
|
614
|
+
new DitoContext(options.component, {
|
|
615
|
+
schema,
|
|
616
|
+
data,
|
|
617
|
+
dataPath,
|
|
618
|
+
rootData: options.rootData
|
|
619
|
+
})
|
|
620
|
+
).components
|
|
621
|
+
: schema.components
|
|
622
|
+
processComponents(components, componentPath)
|
|
586
623
|
processTabOrPanelSchemas(schema.tabs, componentPath)
|
|
587
624
|
processTabOrPanelSchemas(schema.panels, componentPath)
|
|
588
625
|
|
|
@@ -6,26 +6,32 @@ import {
|
|
|
6
6
|
isFunction,
|
|
7
7
|
camelize
|
|
8
8
|
} from '@ditojs/utils'
|
|
9
|
-
import { reactive } from 'vue'
|
|
9
|
+
import { reactive, computed } from 'vue'
|
|
10
10
|
import { getTypeOptions } from './types.js'
|
|
11
11
|
import {
|
|
12
12
|
findNestedSchemaComponent,
|
|
13
13
|
isSchema,
|
|
14
14
|
isSingleComponentView
|
|
15
15
|
} from './structure.js'
|
|
16
|
-
// TODO:
|
|
17
|
-
//
|
|
18
|
-
// modules. Move this out of the lookup, see the form model.
|
|
16
|
+
// TODO: Setting up the forms of `components()` callbacks causes the only
|
|
17
|
+
// circular import between these modules.
|
|
19
18
|
import { setupForm } from './setup.js'
|
|
20
19
|
|
|
21
20
|
const emptySchema = {}
|
|
22
21
|
|
|
22
|
+
// The forms with the components that `components()` callbacks create, by
|
|
23
|
+
// callback and by the data that they create them for, see
|
|
24
|
+
// `getFormWithCreatedComponents()`.
|
|
25
|
+
const formsByComponentsCallback = new WeakMap()
|
|
26
|
+
|
|
23
27
|
export function hasFormSchema(schema) {
|
|
24
28
|
// Support both single form and multiple forms notation, as well as inlined
|
|
25
|
-
// components.
|
|
29
|
+
// components, also created by `components()` callbacks.
|
|
26
30
|
return (
|
|
27
|
-
isSchema(schema) &&
|
|
28
|
-
|
|
31
|
+
isSchema(schema) && (
|
|
32
|
+
isObject(schema.form || schema.forms || schema.components) ||
|
|
33
|
+
isFunction(schema.components)
|
|
34
|
+
)
|
|
29
35
|
)
|
|
30
36
|
}
|
|
31
37
|
|
|
@@ -96,20 +102,51 @@ export function getFormSchemas(schema, context, modifyForm) {
|
|
|
96
102
|
Object.entries(forms).map(([type, form]) => {
|
|
97
103
|
// Support `schema.components` callbacks to create components on the fly.
|
|
98
104
|
if (context && isFunction(form.components)) {
|
|
99
|
-
|
|
100
|
-
// the setting of defaults will be picked up by downstream code.
|
|
101
|
-
form = reactive({
|
|
102
|
-
...form,
|
|
103
|
-
components: form.components(context)
|
|
104
|
-
})
|
|
105
|
-
// Process the form again, now that we have the components.
|
|
106
|
-
setupForm(context.api, form).catch(console.error)
|
|
105
|
+
form = getFormWithCreatedComponents(form, context)
|
|
107
106
|
}
|
|
108
107
|
return [type, modifyForm?.(form) ?? form]
|
|
109
108
|
})
|
|
110
109
|
)
|
|
111
110
|
}
|
|
112
111
|
|
|
112
|
+
// Returns the form with the components that its `components()` callback
|
|
113
|
+
// creates for the data of `context`. The forms are cached per data, and the
|
|
114
|
+
// callback only creates new components when the data that it reads changes,
|
|
115
|
+
// so that rendering and the walks of the form model, see `FormModel`, share
|
|
116
|
+
// the same component schemas. `components()` callbacks therefore derive the
|
|
117
|
+
// components from the data of their context, not from its components.
|
|
118
|
+
function getFormWithCreatedComponents(form, context) {
|
|
119
|
+
const { item } = context
|
|
120
|
+
if (!isObject(item)) {
|
|
121
|
+
return createFormWithComponents(form, context)
|
|
122
|
+
}
|
|
123
|
+
let formsByItem = formsByComponentsCallback.get(form.components)
|
|
124
|
+
if (!formsByItem) {
|
|
125
|
+
formsByItem = new WeakMap()
|
|
126
|
+
formsByComponentsCallback.set(form.components, formsByItem)
|
|
127
|
+
}
|
|
128
|
+
let formWithCreatedComponents = formsByItem.get(item)
|
|
129
|
+
if (!formWithCreatedComponents) {
|
|
130
|
+
formWithCreatedComponents = computed(() =>
|
|
131
|
+
createFormWithComponents(form, context)
|
|
132
|
+
)
|
|
133
|
+
formsByItem.set(item, formWithCreatedComponents)
|
|
134
|
+
}
|
|
135
|
+
return formWithCreatedComponents.value
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function createFormWithComponents(form, context) {
|
|
139
|
+
// Make the form schema reactive since `setupForm()` is async, so that the
|
|
140
|
+
// setting of defaults will be picked up by downstream code.
|
|
141
|
+
const formWithComponents = reactive({
|
|
142
|
+
...form,
|
|
143
|
+
components: form.components(context)
|
|
144
|
+
})
|
|
145
|
+
// Process the form again, now that it has its components.
|
|
146
|
+
setupForm(context.api, formWithComponents).catch(console.error)
|
|
147
|
+
return formWithComponents
|
|
148
|
+
}
|
|
149
|
+
|
|
113
150
|
export function getItemFormSchemaFromForms(forms, item) {
|
|
114
151
|
return forms[item?.type] || forms.default || null
|
|
115
152
|
}
|
|
@@ -2,7 +2,6 @@ import DitoContext from '../../DitoContext.js'
|
|
|
2
2
|
import * as validators from '../../validators/index.js'
|
|
3
3
|
import { isFunction, isRegExp, asArray, normalizeDataPath } from '@ditojs/utils'
|
|
4
4
|
import { getTypeOptions } from './types.js'
|
|
5
|
-
import { isSourceWithResource } from './structure.js'
|
|
6
5
|
import {
|
|
7
6
|
getSchemaValue,
|
|
8
7
|
shouldRenderSchema,
|
|
@@ -86,10 +85,8 @@ export function getDataValidationErrors(schema, data, {
|
|
|
86
85
|
rootData
|
|
87
86
|
})
|
|
88
87
|
|
|
89
|
-
const shouldProcess = entry =>
|
|
90
|
-
!isSourceWithResource(entry.schema) &&
|
|
88
|
+
const shouldProcess = entry =>
|
|
91
89
|
shouldRenderSchema(entry.schema, createContext(entry))
|
|
92
|
-
)
|
|
93
90
|
|
|
94
91
|
const before = entry => {
|
|
95
92
|
const { schema, data, name, dataPath } = entry
|
|
@@ -112,6 +109,7 @@ export function getDataValidationErrors(schema, data, {
|
|
|
112
109
|
processSchemaData(schema, data, {
|
|
113
110
|
dataPath,
|
|
114
111
|
shouldProcess,
|
|
112
|
+
shouldSkipSourcesWithResource: true,
|
|
115
113
|
before,
|
|
116
114
|
options: { component, rootData }
|
|
117
115
|
})
|
package/types/index.d.ts
CHANGED
|
@@ -1548,9 +1548,18 @@ export interface RadioSchema<$Item = any>
|
|
|
1548
1548
|
layout?: 'horizontal' | 'vertical'
|
|
1549
1549
|
}
|
|
1550
1550
|
|
|
1551
|
-
type SectionContent<$Data> = {
|
|
1552
|
-
/**
|
|
1553
|
-
|
|
1551
|
+
type SectionContent<$Data, $Item = $Data> = {
|
|
1552
|
+
/**
|
|
1553
|
+
* The section's field components, or a callback that
|
|
1554
|
+
* creates them from the data, e.g. fields that depend on
|
|
1555
|
+
* a selected template. The callback runs again when the
|
|
1556
|
+
* data that it reads changes, and receives the context of
|
|
1557
|
+
* the section, so that `item` is the item that contains
|
|
1558
|
+
* it, also for nested sections.
|
|
1559
|
+
*/
|
|
1560
|
+
components?:
|
|
1561
|
+
| Components<$Data>
|
|
1562
|
+
| ItemAccessor<$Item, {}, Components<$Data>>
|
|
1554
1563
|
/**
|
|
1555
1564
|
* A form schema for the section's content. Use this
|
|
1556
1565
|
* instead of `components` to get form-level options
|
|
@@ -1690,7 +1699,7 @@ export type SectionSchema<$Item = any, $Nested = $Item> = BaseSchema<$Item> &
|
|
|
1690
1699
|
* @defaultValue `false`
|
|
1691
1700
|
*/
|
|
1692
1701
|
nested: true
|
|
1693
|
-
} & SectionContent<$Nested>)
|
|
1702
|
+
} & SectionContent<$Nested, $Item>)
|
|
1694
1703
|
)
|
|
1695
1704
|
|
|
1696
1705
|
export interface CheckboxSchema<$Item = any> extends BaseSchema<$Item> {
|