@radio-garden/ditojs-admin 2.85.2-0.5067ad799
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/README.md +180 -0
- package/dist/dito-admin.css +1 -0
- package/dist/dito-admin.es.js +12106 -0
- package/dist/dito-admin.umd.js +7 -0
- package/package.json +96 -0
- package/src/DitoAdmin.js +293 -0
- package/src/DitoComponent.js +34 -0
- package/src/DitoContext.js +318 -0
- package/src/DitoTypeComponent.js +42 -0
- package/src/DitoUser.js +12 -0
- package/src/appState.js +12 -0
- package/src/components/DitoAccount.vue +60 -0
- package/src/components/DitoAffix.vue +68 -0
- package/src/components/DitoAffixes.vue +200 -0
- package/src/components/DitoButtons.vue +80 -0
- package/src/components/DitoClipboard.vue +186 -0
- package/src/components/DitoContainer.vue +374 -0
- package/src/components/DitoCreateButton.vue +146 -0
- package/src/components/DitoDialog.vue +242 -0
- package/src/components/DitoDraggable.vue +117 -0
- package/src/components/DitoEditButtons.vue +135 -0
- package/src/components/DitoErrors.vue +83 -0
- package/src/components/DitoForm.vue +521 -0
- package/src/components/DitoFormInner.vue +26 -0
- package/src/components/DitoFormNested.vue +17 -0
- package/src/components/DitoHeader.vue +84 -0
- package/src/components/DitoLabel.vue +200 -0
- package/src/components/DitoMenu.vue +186 -0
- package/src/components/DitoNavigation.vue +40 -0
- package/src/components/DitoNotifications.vue +170 -0
- package/src/components/DitoPagination.vue +42 -0
- package/src/components/DitoPane.vue +334 -0
- package/src/components/DitoPanel.vue +256 -0
- package/src/components/DitoPanels.vue +61 -0
- package/src/components/DitoRoot.vue +524 -0
- package/src/components/DitoSchema.vue +846 -0
- package/src/components/DitoSchemaInlined.vue +97 -0
- package/src/components/DitoScopes.vue +76 -0
- package/src/components/DitoSidebar.vue +50 -0
- package/src/components/DitoSpinner.vue +95 -0
- package/src/components/DitoTableCell.vue +64 -0
- package/src/components/DitoTableHead.vue +121 -0
- package/src/components/DitoTabs.vue +103 -0
- package/src/components/DitoTrail.vue +124 -0
- package/src/components/DitoTreeItem.vue +420 -0
- package/src/components/DitoUploadFile.vue +199 -0
- package/src/components/DitoVNode.vue +14 -0
- package/src/components/DitoView.vue +143 -0
- package/src/components/index.js +42 -0
- package/src/directives/resize.js +83 -0
- package/src/index.js +1 -0
- package/src/mixins/ContextMixin.js +68 -0
- package/src/mixins/DataMixin.js +131 -0
- package/src/mixins/DitoMixin.js +591 -0
- package/src/mixins/DomMixin.js +29 -0
- package/src/mixins/EmitterMixin.js +158 -0
- package/src/mixins/ItemMixin.js +144 -0
- package/src/mixins/LoadingMixin.js +23 -0
- package/src/mixins/NumberMixin.js +118 -0
- package/src/mixins/OptionsMixin.js +304 -0
- package/src/mixins/PulldownMixin.js +63 -0
- package/src/mixins/ResourceMixin.js +398 -0
- package/src/mixins/RouteMixin.js +190 -0
- package/src/mixins/SchemaParentMixin.js +33 -0
- package/src/mixins/SortableMixin.js +49 -0
- package/src/mixins/SourceMixin.js +734 -0
- package/src/mixins/TextMixin.js +26 -0
- package/src/mixins/TypeMixin.js +280 -0
- package/src/mixins/ValidationMixin.js +119 -0
- package/src/mixins/ValidatorMixin.js +57 -0
- package/src/mixins/ValueMixin.js +31 -0
- package/src/styles/_base.scss +17 -0
- package/src/styles/_button.scss +191 -0
- package/src/styles/_imports.scss +3 -0
- package/src/styles/_info.scss +19 -0
- package/src/styles/_layout.scss +19 -0
- package/src/styles/_pulldown.scss +38 -0
- package/src/styles/_scroll.scss +13 -0
- package/src/styles/_settings.scss +88 -0
- package/src/styles/_table.scss +223 -0
- package/src/styles/_tippy.scss +45 -0
- package/src/styles/style.scss +9 -0
- package/src/types/DitoTypeButton.vue +143 -0
- package/src/types/DitoTypeCheckbox.vue +27 -0
- package/src/types/DitoTypeCheckboxes.vue +65 -0
- package/src/types/DitoTypeCode.vue +199 -0
- package/src/types/DitoTypeColor.vue +272 -0
- package/src/types/DitoTypeComponent.vue +31 -0
- package/src/types/DitoTypeComputed.vue +50 -0
- package/src/types/DitoTypeDate.vue +99 -0
- package/src/types/DitoTypeLabel.vue +23 -0
- package/src/types/DitoTypeList.vue +364 -0
- package/src/types/DitoTypeMarkup.vue +700 -0
- package/src/types/DitoTypeMultiselect.vue +522 -0
- package/src/types/DitoTypeNumber.vue +66 -0
- package/src/types/DitoTypeObject.vue +136 -0
- package/src/types/DitoTypePanel.vue +18 -0
- package/src/types/DitoTypeProgress.vue +40 -0
- package/src/types/DitoTypeRadio.vue +45 -0
- package/src/types/DitoTypeSection.vue +80 -0
- package/src/types/DitoTypeSelect.vue +133 -0
- package/src/types/DitoTypeSlider.vue +66 -0
- package/src/types/DitoTypeSpacer.vue +11 -0
- package/src/types/DitoTypeSwitch.vue +40 -0
- package/src/types/DitoTypeText.vue +101 -0
- package/src/types/DitoTypeTextarea.vue +48 -0
- package/src/types/DitoTypeTreeList.vue +193 -0
- package/src/types/DitoTypeUpload.vue +503 -0
- package/src/types/index.js +30 -0
- package/src/utils/SchemaGraph.js +147 -0
- package/src/utils/accessor.js +75 -0
- package/src/utils/agent.js +47 -0
- package/src/utils/data.js +92 -0
- package/src/utils/filter.js +266 -0
- package/src/utils/math.js +14 -0
- package/src/utils/options.js +48 -0
- package/src/utils/path.js +5 -0
- package/src/utils/resource.js +44 -0
- package/src/utils/route.js +53 -0
- package/src/utils/schema.js +1121 -0
- package/src/utils/type.js +81 -0
- package/src/utils/uid.js +15 -0
- package/src/utils/units.js +5 -0
- package/src/validators/_creditcard.js +6 -0
- package/src/validators/_decimals.js +11 -0
- package/src/validators/_domain.js +6 -0
- package/src/validators/_email.js +6 -0
- package/src/validators/_hostname.js +6 -0
- package/src/validators/_integer.js +6 -0
- package/src/validators/_max.js +6 -0
- package/src/validators/_min.js +6 -0
- package/src/validators/_password.js +5 -0
- package/src/validators/_range.js +6 -0
- package/src/validators/_required.js +9 -0
- package/src/validators/_url.js +6 -0
- package/src/validators/index.js +12 -0
- package/src/verbs.js +17 -0
- package/types/index.d.ts +3298 -0
- package/types/tests/admin.test-d.ts +27 -0
- package/types/tests/component-buttons.test-d.ts +44 -0
- package/types/tests/component-list.test-d.ts +159 -0
- package/types/tests/component-misc.test-d.ts +137 -0
- package/types/tests/component-object.test-d.ts +69 -0
- package/types/tests/component-section.test-d.ts +174 -0
- package/types/tests/component-select.test-d.ts +107 -0
- package/types/tests/components.test-d.ts +81 -0
- package/types/tests/context.test-d.ts +31 -0
- package/types/tests/fixtures.ts +24 -0
- package/types/tests/form.test-d.ts +109 -0
- package/types/tests/instance.test-d.ts +20 -0
- package/types/tests/schema-features.test-d.ts +402 -0
- package/types/tests/variance.test-d.ts +125 -0
- package/types/tests/view.test-d.ts +146 -0
|
@@ -0,0 +1,1121 @@
|
|
|
1
|
+
import DitoContext from '../DitoContext.js'
|
|
2
|
+
import DitoMixin from '../mixins/DitoMixin.js'
|
|
3
|
+
import TypeMixin from '../mixins/TypeMixin.js'
|
|
4
|
+
import { getUid } from './uid.js'
|
|
5
|
+
import { SchemaGraph } from './SchemaGraph.js'
|
|
6
|
+
import { appendDataPath } from './data.js'
|
|
7
|
+
import { isMatchingType, convertType } from './type.js'
|
|
8
|
+
import {
|
|
9
|
+
isObject,
|
|
10
|
+
isString,
|
|
11
|
+
isArray,
|
|
12
|
+
isFunction,
|
|
13
|
+
isPromise,
|
|
14
|
+
isModule,
|
|
15
|
+
asArray,
|
|
16
|
+
clone,
|
|
17
|
+
camelize,
|
|
18
|
+
assignDeeply,
|
|
19
|
+
mapConcurrently,
|
|
20
|
+
getValueAtDataPath
|
|
21
|
+
} from '@ditojs/utils'
|
|
22
|
+
import { markRaw, reactive } from 'vue'
|
|
23
|
+
|
|
24
|
+
const typeComponents = {}
|
|
25
|
+
const unknownTypeReported = {}
|
|
26
|
+
const emptySchema = {}
|
|
27
|
+
|
|
28
|
+
export function registerTypeComponent(type, component) {
|
|
29
|
+
typeComponents[type] = component
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getTypeComponent(type, allowNull = false) {
|
|
33
|
+
const component = typeComponents[type] || null
|
|
34
|
+
if (!component && !allowNull && !unknownTypeReported[type]) {
|
|
35
|
+
// Report each missing type only once, to avoid flooding the console:
|
|
36
|
+
unknownTypeReported[type] = true
|
|
37
|
+
throw new Error(`Unknown Dito.js component type: '${type}'`)
|
|
38
|
+
}
|
|
39
|
+
return component
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function iterateSchemaComponents(schemas, callback) {
|
|
43
|
+
for (const schema of schemas) {
|
|
44
|
+
if (isSingleComponentView(schema)) {
|
|
45
|
+
const res = callback(schema.component, schema.name, 0)
|
|
46
|
+
if (res !== undefined) {
|
|
47
|
+
return res
|
|
48
|
+
}
|
|
49
|
+
} else if (isSchema(schema)) {
|
|
50
|
+
for (const [name, component] of Object.entries(schema.components || {})) {
|
|
51
|
+
const res = callback(component, name, 1)
|
|
52
|
+
if (res !== undefined) {
|
|
53
|
+
return res
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function iterateNestedSchemaComponents(schema, callback) {
|
|
61
|
+
return schema
|
|
62
|
+
? iterateSchemaComponents([schema, ...getTabSchemas(schema)], callback)
|
|
63
|
+
: undefined
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function findNestedSchemaComponent(schema, callback) {
|
|
67
|
+
return (
|
|
68
|
+
iterateNestedSchemaComponents(
|
|
69
|
+
schema,
|
|
70
|
+
component => (callback(component) ? component : undefined)
|
|
71
|
+
) ?? null
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function someNestedSchemaComponent(schema, callback) {
|
|
76
|
+
return (
|
|
77
|
+
iterateNestedSchemaComponents(
|
|
78
|
+
schema,
|
|
79
|
+
component => (callback(component) ? true : undefined)
|
|
80
|
+
) ?? false
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function everyNestedSchemaComponent(schema, callback) {
|
|
85
|
+
return (
|
|
86
|
+
iterateNestedSchemaComponents(
|
|
87
|
+
schema,
|
|
88
|
+
component => (callback(component) ? undefined : false)
|
|
89
|
+
) ?? true
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function hasNestedSchemaComponents(schema) {
|
|
94
|
+
return someNestedSchemaComponent(schema, () => true) ?? false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function isSchema(schema) {
|
|
98
|
+
return isObject(schema) && isString(schema.type)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function isForm(schema) {
|
|
102
|
+
return isSchema(schema) && schema.type === 'form'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function isView(schema) {
|
|
106
|
+
return isSchema(schema) && schema.type === 'view'
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function isTab(schema) {
|
|
110
|
+
return isSchema(schema) && schema.type === 'tab'
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function isPanel(schema) {
|
|
114
|
+
return isSchema(schema) && schema.type === 'panel'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function isMenu(schema) {
|
|
118
|
+
return isSchema(schema) && schema.type === 'menu'
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function getSchemaIdentifier(schema) {
|
|
122
|
+
return JSON.stringify(schema)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const resolvedSchemas = new WeakMap()
|
|
126
|
+
export async function resolveSchema(value, unwrapModule = false) {
|
|
127
|
+
if (resolvedSchemas.has(value)) {
|
|
128
|
+
return resolvedSchemas.get(value)
|
|
129
|
+
}
|
|
130
|
+
let schema = value
|
|
131
|
+
if (isFunction(schema)) {
|
|
132
|
+
schema = schema()
|
|
133
|
+
}
|
|
134
|
+
if (isPromise(schema)) {
|
|
135
|
+
schema = await schema
|
|
136
|
+
}
|
|
137
|
+
if (isModule(schema)) {
|
|
138
|
+
// Copy to convert from module to object:
|
|
139
|
+
schema = { ...schema }
|
|
140
|
+
// Unwrap default or named schema
|
|
141
|
+
if (!schema.name && (unwrapModule || schema.default)) {
|
|
142
|
+
// Filter out internal key added by vite / vue 3 plugin when changing
|
|
143
|
+
// code in a dynamically imported vue component, see:
|
|
144
|
+
// https://github.com/vitejs/vite-plugin-vue/blob/abdf5f4f32d02af641e5f60871bde14535569b1e/packages/plugin-vue/src/main.ts#L133
|
|
145
|
+
const keys = Object.keys(schema).filter(key => key !== '_rerender_only')
|
|
146
|
+
if (keys.length === 1) {
|
|
147
|
+
const name = keys[0]
|
|
148
|
+
schema = schema[name]
|
|
149
|
+
if (name !== 'default') {
|
|
150
|
+
schema = { ...schema, name }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
resolvedSchemas.set(value, schema)
|
|
156
|
+
return schema
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function resolveSchemas(
|
|
160
|
+
unresolvedSchemas,
|
|
161
|
+
resolveItem = resolveSchema
|
|
162
|
+
) {
|
|
163
|
+
let schemas = isFunction(unresolvedSchemas)
|
|
164
|
+
? unresolvedSchemas()
|
|
165
|
+
: unresolvedSchemas
|
|
166
|
+
schemas = await resolveSchema(schemas, false)
|
|
167
|
+
if (isArray(schemas)) {
|
|
168
|
+
// Translate an array of dynamic import, each importing one named schema
|
|
169
|
+
// module to an object with named entries.
|
|
170
|
+
schemas = Object.fromEntries(
|
|
171
|
+
await mapConcurrently(
|
|
172
|
+
schemas,
|
|
173
|
+
async item => {
|
|
174
|
+
const schema = await resolveItem(item, true)
|
|
175
|
+
return [schema.name, schema]
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
} else if (isObject(schemas)) {
|
|
180
|
+
schemas = Object.fromEntries(
|
|
181
|
+
await mapConcurrently(
|
|
182
|
+
Object.entries(schemas),
|
|
183
|
+
async ([key, item]) => {
|
|
184
|
+
const schema = await resolveItem(item, true)
|
|
185
|
+
return [key, schema]
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
return schemas
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function resolveViews(unresolvedViews) {
|
|
194
|
+
return resolveSchemas(unresolvedViews, async (schema, unwrapModule) => {
|
|
195
|
+
schema = await resolveSchema(schema, unwrapModule)
|
|
196
|
+
if (!schema.name && isMenu(schema)) {
|
|
197
|
+
// Generate a name for sub-menus from their label if it's missing.
|
|
198
|
+
// NOTE: This is never actually referenced from anywhere, but they need
|
|
199
|
+
// a name by which they're stored in the parent object.
|
|
200
|
+
schema = {
|
|
201
|
+
...schema,
|
|
202
|
+
name: camelize(schema.label),
|
|
203
|
+
items: await resolveSchemas(schema.items)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return schema
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function flattenViews(views) {
|
|
211
|
+
return Object.fromEntries(
|
|
212
|
+
Object.entries(views).reduce(
|
|
213
|
+
(entries, [key, schema]) => {
|
|
214
|
+
if (isMenu(schema)) {
|
|
215
|
+
entries.push(...Object.entries(schema.items))
|
|
216
|
+
} else {
|
|
217
|
+
entries.push([key, schema])
|
|
218
|
+
}
|
|
219
|
+
return entries
|
|
220
|
+
},
|
|
221
|
+
[]
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export async function resolveSchemaComponent(schema) {
|
|
227
|
+
// Resolves async schema components and adds DitoMixin and TypeMixin to them.
|
|
228
|
+
let { component } = schema
|
|
229
|
+
if (component) {
|
|
230
|
+
component = await resolveSchema(component, true)
|
|
231
|
+
if (component) {
|
|
232
|
+
// Prevent warning: "Vue received a Component which was made a reactive
|
|
233
|
+
// object. This can lead to unnecessary performance overhead, and should
|
|
234
|
+
// be avoided by marking the component with `markRaw`":
|
|
235
|
+
schema.component = markRaw({
|
|
236
|
+
...component,
|
|
237
|
+
mixins: [DitoMixin, TypeMixin, ...(component.mixins || [])]
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export async function resolveSchemaComponents(schemas) {
|
|
244
|
+
// `schemas` are of the same possible forms as passed to `getNamedSchemas()`
|
|
245
|
+
await mapConcurrently(Object.values(schemas || {}), resolveSchemaComponent)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const processedSchemaDepths = new WeakMap()
|
|
249
|
+
export function processSchemaComponents(
|
|
250
|
+
api,
|
|
251
|
+
schema,
|
|
252
|
+
routes = null,
|
|
253
|
+
level = 0,
|
|
254
|
+
maxDepth = 1
|
|
255
|
+
) {
|
|
256
|
+
if (schema) {
|
|
257
|
+
const depth = processedSchemaDepths.get(schema) ?? 0
|
|
258
|
+
if (depth < maxDepth) {
|
|
259
|
+
processedSchemaDepths.set(schema, depth + 1)
|
|
260
|
+
const promises = []
|
|
261
|
+
const process = (component, name, relativeLevel) => {
|
|
262
|
+
promises.push(
|
|
263
|
+
processSchemaComponent(
|
|
264
|
+
api,
|
|
265
|
+
component,
|
|
266
|
+
name,
|
|
267
|
+
routes,
|
|
268
|
+
level + relativeLevel
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
iterateNestedSchemaComponents(schema, process)
|
|
274
|
+
iterateSchemaComponents(getPanelSchemas(schema), process)
|
|
275
|
+
|
|
276
|
+
return Promise.all(promises)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function processSchemaComponent(
|
|
282
|
+
api,
|
|
283
|
+
schema,
|
|
284
|
+
name,
|
|
285
|
+
routes = null,
|
|
286
|
+
level = 0
|
|
287
|
+
) {
|
|
288
|
+
processSchemaDefaults(api, schema)
|
|
289
|
+
|
|
290
|
+
return Promise.all([
|
|
291
|
+
// Also process nested panel schemas.
|
|
292
|
+
mapConcurrently(
|
|
293
|
+
getPanelSchemas(schema),
|
|
294
|
+
panel => processSchemaComponents(api, panel, routes, level)
|
|
295
|
+
),
|
|
296
|
+
// Delegate schema processing to the actual type components.
|
|
297
|
+
getTypeOptions(schema)?.processSchema?.(
|
|
298
|
+
api,
|
|
299
|
+
schema,
|
|
300
|
+
name,
|
|
301
|
+
routes,
|
|
302
|
+
level
|
|
303
|
+
)
|
|
304
|
+
])
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export async function processView(component, api, schema, name, fullPath = '') {
|
|
308
|
+
processSchemaDefaults(api, schema)
|
|
309
|
+
processRouteSchema(api, schema, name, fullPath)
|
|
310
|
+
let children = []
|
|
311
|
+
if (isView(schema)) {
|
|
312
|
+
await processNestedSchemas(api, schema)
|
|
313
|
+
await processSchemaComponents(api, schema, children)
|
|
314
|
+
} else if (isMenu(schema)) {
|
|
315
|
+
children = await Promise.all(
|
|
316
|
+
Object.entries(schema.items).map(async ([name, item]) =>
|
|
317
|
+
processView(component, api, item, name, schema.fullPath)
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
} else {
|
|
321
|
+
throw new Error(`Invalid view schema: '${getSchemaIdentifier(schema)}'`)
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
path: schema.fullPath,
|
|
325
|
+
children,
|
|
326
|
+
component,
|
|
327
|
+
meta: {
|
|
328
|
+
api,
|
|
329
|
+
schema
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function processSchemaDefaults(api, schema) {
|
|
335
|
+
let defaults = (
|
|
336
|
+
api.defaults[schema.type] ||
|
|
337
|
+
api.defaults[camelize(schema.type)]
|
|
338
|
+
)
|
|
339
|
+
if (defaults) {
|
|
340
|
+
if (isFunction(defaults)) {
|
|
341
|
+
defaults = defaults(schema)
|
|
342
|
+
}
|
|
343
|
+
if (isObject(defaults)) {
|
|
344
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
345
|
+
if (schema[key] === undefined) {
|
|
346
|
+
schema[key] = value
|
|
347
|
+
} else {
|
|
348
|
+
schema[key] = assignDeeply(schema[key], value)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function processNestedSchemaDefaults(api, schema) {
|
|
356
|
+
// Process defaults for nested schemas. Note that this is also done when
|
|
357
|
+
// calling `processSchemaComponents()`, but that function is async, and we
|
|
358
|
+
// need a sync version that only handles the defaults for filters, see
|
|
359
|
+
// `getFiltersPanel()`.
|
|
360
|
+
iterateNestedSchemaComponents(schema, component => {
|
|
361
|
+
processSchemaDefaults(api, component)
|
|
362
|
+
const forms = getFormSchemas(component)
|
|
363
|
+
for (const form of Object.values(forms)) {
|
|
364
|
+
processNestedSchemaDefaults(api, form)
|
|
365
|
+
}
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function processRouteSchema(api, schema, name, fullPath = null) {
|
|
370
|
+
// Used for view and source schemas, see SourceMixin.
|
|
371
|
+
schema.name ??= name
|
|
372
|
+
schema.path ??= api.normalizePath(name)
|
|
373
|
+
if (fullPath !== null) {
|
|
374
|
+
schema.fullPath = `${fullPath}/${schema.path}`
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export async function processForms(api, schema, level) {
|
|
379
|
+
const routes = []
|
|
380
|
+
// First resolve the forms and store the results back on the schema.
|
|
381
|
+
const { form, forms, components, maxDepth = 1 } = schema
|
|
382
|
+
if (forms) {
|
|
383
|
+
schema.forms = await resolveSchemas(forms, form =>
|
|
384
|
+
processForm(api, form, routes, level, maxDepth)
|
|
385
|
+
)
|
|
386
|
+
} else if (form) {
|
|
387
|
+
schema.form = await processForm(api, form, routes, level, maxDepth)
|
|
388
|
+
} else if (isObject(components)) {
|
|
389
|
+
// NOTE: Processing forms in computed components is not supported, since it
|
|
390
|
+
// only can be computed in conjunction with actual data.
|
|
391
|
+
const form = {
|
|
392
|
+
type: 'form',
|
|
393
|
+
components
|
|
394
|
+
}
|
|
395
|
+
await processForm(api, form, routes, level, maxDepth)
|
|
396
|
+
}
|
|
397
|
+
return routes
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export async function processForm(
|
|
401
|
+
api,
|
|
402
|
+
schema,
|
|
403
|
+
routes = null,
|
|
404
|
+
level = 0,
|
|
405
|
+
maxDepth = 1
|
|
406
|
+
) {
|
|
407
|
+
schema = await resolveSchema(schema, true)
|
|
408
|
+
if (!isForm(schema)) {
|
|
409
|
+
throw new Error(`Invalid form schema: '${getSchemaIdentifier(schema)}'`)
|
|
410
|
+
}
|
|
411
|
+
processSchemaDefaults(api, schema)
|
|
412
|
+
await processNestedSchemas(api, schema)
|
|
413
|
+
await processSchemaComponents(api, schema, routes, level, maxDepth)
|
|
414
|
+
return schema
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export async function processTab(api, schema) {
|
|
418
|
+
schema = await resolveSchema(schema, true)
|
|
419
|
+
if (!isTab(schema)) {
|
|
420
|
+
throw new Error(`Invalid tab schema: '${getSchemaIdentifier(schema)}'`)
|
|
421
|
+
}
|
|
422
|
+
processSchemaDefaults(api, schema)
|
|
423
|
+
return schema
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export async function processPanel(api, schema) {
|
|
427
|
+
schema = await resolveSchema(schema, true)
|
|
428
|
+
if (!isPanel(schema)) {
|
|
429
|
+
throw new Error(`Invalid panel schema: '${getSchemaIdentifier(schema)}'`)
|
|
430
|
+
}
|
|
431
|
+
processSchemaDefaults(api, schema)
|
|
432
|
+
return schema
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export async function processNestedSchemas(api, schema) {
|
|
436
|
+
const { tabs, panels } = schema
|
|
437
|
+
if (tabs) {
|
|
438
|
+
schema.tabs = await resolveSchemas(
|
|
439
|
+
tabs,
|
|
440
|
+
tab => processTab(api, tab)
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
if (panels) {
|
|
444
|
+
schema.panels = await resolveSchemas(
|
|
445
|
+
panels,
|
|
446
|
+
panel => processPanel(api, panel)
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function hasFormSchema(schema) {
|
|
452
|
+
// Support both single form and multiple forms notation, as well as inlined
|
|
453
|
+
// components.
|
|
454
|
+
return (
|
|
455
|
+
isSchema(schema) &&
|
|
456
|
+
isObject(schema.form || schema.forms || schema.components)
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export function hasMultipleFormSchemas(schema) {
|
|
461
|
+
return (
|
|
462
|
+
isSchema(schema) &&
|
|
463
|
+
Object.keys(schema?.forms || {}).length > 1
|
|
464
|
+
)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export function isSingleComponentView(schema) {
|
|
468
|
+
return (
|
|
469
|
+
isView(schema) &&
|
|
470
|
+
isObject(schema.component)
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export function getViewFormSchema(schema, context) {
|
|
475
|
+
const { view } = schema
|
|
476
|
+
const viewSchema = view && context.flattenedViews[view]
|
|
477
|
+
return viewSchema
|
|
478
|
+
? // NOTE: Views can have tabs, in which case the view component is nested
|
|
479
|
+
// in one of the tabs, go find it.
|
|
480
|
+
findNestedSchemaComponent(viewSchema, hasFormSchema) || null
|
|
481
|
+
: null
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export function getViewSchema(schema, context) {
|
|
485
|
+
return getViewFormSchema(schema, context)
|
|
486
|
+
? context.flattenedViews[schema.view]
|
|
487
|
+
: null
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export function hasViewSchema(schema, context) {
|
|
491
|
+
return !!getViewSchema(schema, context)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function getViewPath(schema, context) {
|
|
495
|
+
const view = getViewSchema(schema, context)
|
|
496
|
+
if (view) {
|
|
497
|
+
return isSingleComponentView(view)
|
|
498
|
+
? view.fullPath
|
|
499
|
+
: `${view.fullPath}/${view.path}`
|
|
500
|
+
}
|
|
501
|
+
return null
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export function getViewEditPath(schema, id, context) {
|
|
505
|
+
const path = getViewPath(schema, context)
|
|
506
|
+
return path ? `${path}/${id}` : null
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export function getFormSchemas(schema, context, modifyForm) {
|
|
510
|
+
const viewSchema = context && getViewFormSchema(schema, context)
|
|
511
|
+
if (viewSchema) {
|
|
512
|
+
schema = viewSchema
|
|
513
|
+
} else if (schema.view) {
|
|
514
|
+
throw new Error(`Unknown view: '${schema.view}'`)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
let { form, forms } = schema
|
|
518
|
+
if (!form && !forms) {
|
|
519
|
+
const { name, compact, clipboard, tabs, components } = schema
|
|
520
|
+
if (components || tabs) {
|
|
521
|
+
// Convert inlined forms to stand-alone forms, supporting `name`,
|
|
522
|
+
// `compact`, `clipboard`, `tabs` and `components` settings.
|
|
523
|
+
form = { type: 'form', name, compact, clipboard, tabs, components }
|
|
524
|
+
} else {
|
|
525
|
+
// No `forms`, `form` or `components`, return and empty `forms` object.
|
|
526
|
+
return {}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
forms ||= { default: form }
|
|
530
|
+
return Object.fromEntries(
|
|
531
|
+
Object.entries(forms).map(([type, form]) => {
|
|
532
|
+
// Support `schema.components` callbacks to create components on the fly.
|
|
533
|
+
if (context && isFunction(form.components)) {
|
|
534
|
+
// Make the form schema reactive since `processForm()` is async, so that
|
|
535
|
+
// the setting of defaults will be picked up by downstream code.
|
|
536
|
+
form = reactive({
|
|
537
|
+
...form,
|
|
538
|
+
components: form.components(context)
|
|
539
|
+
})
|
|
540
|
+
// Process the form again, now that we have the components.
|
|
541
|
+
processForm(context.api, form).catch(console.error)
|
|
542
|
+
}
|
|
543
|
+
return [type, modifyForm?.(form) ?? form]
|
|
544
|
+
})
|
|
545
|
+
)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function getItemFormSchemaFromForms(forms, item) {
|
|
549
|
+
return forms[item?.type] || forms.default || null
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export function getItemFormSchema(schema, item, context) {
|
|
553
|
+
return (
|
|
554
|
+
getItemFormSchemaFromForms(getFormSchemas(schema, context), item) ||
|
|
555
|
+
// Always return a schema object so we don't need to check for it.
|
|
556
|
+
emptySchema
|
|
557
|
+
)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export function isEmptySchema(schema) {
|
|
561
|
+
return schema === emptySchema
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export function isCompact(schema) {
|
|
565
|
+
return !!schema.compact
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export function isInlined(schema) {
|
|
569
|
+
return !!(schema.inlined || schema.components)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export function isNested(schema) {
|
|
573
|
+
return !!(schema.nested || getTypeOptions(schema)?.defaultNested === true)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export function hasLabel(schema, generateLabels) {
|
|
577
|
+
const { label } = schema
|
|
578
|
+
return (
|
|
579
|
+
label !== false && (
|
|
580
|
+
!!label ||
|
|
581
|
+
generateLabels && getTypeOptions(schema)?.generateLabel
|
|
582
|
+
)
|
|
583
|
+
)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export function omitSpacing(schema) {
|
|
587
|
+
return !!getTypeOptions(schema)?.omitSpacing
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function getSchemaValue(
|
|
591
|
+
keyOrDataPath,
|
|
592
|
+
{ type, schema, callback = true, default: def, context } = {}
|
|
593
|
+
) {
|
|
594
|
+
const types = type && asArray(type)
|
|
595
|
+
// For performance reasons, data-paths in `keyOrDataPath` can only be
|
|
596
|
+
// provided in in array format here:
|
|
597
|
+
let value = schema
|
|
598
|
+
? isArray(keyOrDataPath)
|
|
599
|
+
? getValueAtDataPath(schema, keyOrDataPath, () => undefined)
|
|
600
|
+
: schema[keyOrDataPath]
|
|
601
|
+
: undefined
|
|
602
|
+
|
|
603
|
+
if (value === undefined && def !== undefined) {
|
|
604
|
+
if (callback && isFunction(def) && !isMatchingType(types, def)) {
|
|
605
|
+
// Support `default()` functions for any type except `Function`:
|
|
606
|
+
def = def(context)
|
|
607
|
+
}
|
|
608
|
+
return def
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (isMatchingType(types, value)) {
|
|
612
|
+
return value
|
|
613
|
+
}
|
|
614
|
+
// Any schema value handled through `getSchemaValue()` can provide
|
|
615
|
+
// a function that's resolved when the value is evaluated:
|
|
616
|
+
if (callback && isFunction(value)) {
|
|
617
|
+
value = value(context)
|
|
618
|
+
}
|
|
619
|
+
// Now finally see if we can convert to the expect types.
|
|
620
|
+
if (types && value != null && !isMatchingType(types, value)) {
|
|
621
|
+
for (const type of types) {
|
|
622
|
+
const converted = convertType(type, value)
|
|
623
|
+
if (converted !== value) {
|
|
624
|
+
return converted
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return value
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export function shouldRenderSchema(schema, context) {
|
|
632
|
+
return (
|
|
633
|
+
getSchemaValue('if', {
|
|
634
|
+
type: Boolean,
|
|
635
|
+
schema,
|
|
636
|
+
context,
|
|
637
|
+
default: true
|
|
638
|
+
}) && (
|
|
639
|
+
!hasNestedSchemaComponents(schema) ||
|
|
640
|
+
someNestedSchemaComponent(schema, component =>
|
|
641
|
+
shouldRenderSchema(component, context)
|
|
642
|
+
)
|
|
643
|
+
)
|
|
644
|
+
)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function getContext(context) {
|
|
648
|
+
return isFunction(context) ? context() : context
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export function getDefaultValue(schema, context) {
|
|
652
|
+
// Support default values both on schema and on component level.
|
|
653
|
+
// NOTE: At the time of creation, components may not be instantiated, (e.g. if
|
|
654
|
+
// entries are created through nested forms, the parent form isn't mounted) so
|
|
655
|
+
// we can't use `dataPath` to get to components, and the `defaultValue` from
|
|
656
|
+
// there. That's why `defaultValue` is defined statically in the components:
|
|
657
|
+
const defaultValue =
|
|
658
|
+
schema.default !== undefined
|
|
659
|
+
? schema.default
|
|
660
|
+
: getTypeOptions(schema)?.defaultValue
|
|
661
|
+
return isFunction(defaultValue)
|
|
662
|
+
? defaultValue(getContext(context))
|
|
663
|
+
: clone(defaultValue)
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
export function shouldExcludeValue(schema, context) {
|
|
667
|
+
const excludeValue =
|
|
668
|
+
schema.exclude !== undefined
|
|
669
|
+
? schema.exclude
|
|
670
|
+
: getTypeOptions(schema)?.excludeValue
|
|
671
|
+
return isFunction(excludeValue)
|
|
672
|
+
? excludeValue(getContext(context))
|
|
673
|
+
: !!excludeValue
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export function shouldIgnoreMissingValue(schema, context) {
|
|
677
|
+
return !!getTypeOptions(schema)?.ignoreMissingValue?.(getContext(context))
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
export function getMultipleValue(schema) {
|
|
681
|
+
return schema.multiple ?? !!getTypeOptions(schema)?.defaultMultiple
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export function setDefaultValues(schema, data = {}, component) {
|
|
685
|
+
const options = { component, rootData: data }
|
|
686
|
+
|
|
687
|
+
const processBefore = (schema, data, name, dataPath) => {
|
|
688
|
+
const context = () =>
|
|
689
|
+
new DitoContext(component, {
|
|
690
|
+
schema,
|
|
691
|
+
name,
|
|
692
|
+
data,
|
|
693
|
+
dataPath,
|
|
694
|
+
rootData: options.rootData
|
|
695
|
+
})
|
|
696
|
+
if (!(name in data) && !shouldIgnoreMissingValue(schema, context)) {
|
|
697
|
+
data[name] = getDefaultValue(schema, context)
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Sets up a data object that has keys with default values for all
|
|
702
|
+
// form fields, so they can be correctly watched for changes.
|
|
703
|
+
return processSchemaData(
|
|
704
|
+
schema,
|
|
705
|
+
data,
|
|
706
|
+
null,
|
|
707
|
+
null,
|
|
708
|
+
processBefore,
|
|
709
|
+
null,
|
|
710
|
+
options
|
|
711
|
+
)
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
export function computeValue(schema, data, name, dataPath, {
|
|
715
|
+
component = null,
|
|
716
|
+
rootData = component?.rootData
|
|
717
|
+
} = {}) {
|
|
718
|
+
const context = () =>
|
|
719
|
+
new DitoContext(component, {
|
|
720
|
+
schema,
|
|
721
|
+
// Override value to prevent endless recursion through calling the
|
|
722
|
+
// getter for `this.value` in `DitoContext`:
|
|
723
|
+
value: data[name],
|
|
724
|
+
name,
|
|
725
|
+
data,
|
|
726
|
+
dataPath,
|
|
727
|
+
rootData
|
|
728
|
+
})
|
|
729
|
+
const { compute } = schema
|
|
730
|
+
if (compute) {
|
|
731
|
+
const value = compute(getContext(context))
|
|
732
|
+
if (value !== undefined) {
|
|
733
|
+
// Access `data[name]` directly to update the value without calling
|
|
734
|
+
// parse():
|
|
735
|
+
// TODO: Fix side-effects
|
|
736
|
+
data[name] = value
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
// If the value is still missing after compute, set the default for it:
|
|
740
|
+
if (!(name in data) && !shouldIgnoreMissingValue(schema, context)) {
|
|
741
|
+
// TODO: Fix side-effects
|
|
742
|
+
data[name] = getDefaultValue(schema, context)
|
|
743
|
+
}
|
|
744
|
+
// Now access the value. This is important for reactivity and needs to
|
|
745
|
+
// happen after all prior manipulation of `data[name]`, see above:
|
|
746
|
+
return data[name]
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function cloneItem(sourceSchema, item, options) {
|
|
750
|
+
if (options.schemaOnly) {
|
|
751
|
+
const copy = {}
|
|
752
|
+
const { idKey = 'id', orderKey } = sourceSchema
|
|
753
|
+
const id = item[idKey]
|
|
754
|
+
if (id !== undefined) {
|
|
755
|
+
copy[idKey] = id
|
|
756
|
+
}
|
|
757
|
+
// Copy over type in case there are multiple forms to choose from.
|
|
758
|
+
if (hasMultipleFormSchemas(sourceSchema)) {
|
|
759
|
+
copy.type = item.type
|
|
760
|
+
}
|
|
761
|
+
if (orderKey) {
|
|
762
|
+
copy[orderKey] = item[orderKey]
|
|
763
|
+
}
|
|
764
|
+
return copy
|
|
765
|
+
} else {
|
|
766
|
+
return { ...item }
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
export function processData(schema, sourceSchema, data, dataPath, {
|
|
771
|
+
component,
|
|
772
|
+
rootData,
|
|
773
|
+
schemaOnly, // whether to only include data covered by the schema, or all data
|
|
774
|
+
target
|
|
775
|
+
} = {}) {
|
|
776
|
+
const options = { component, rootData, schemaOnly, target }
|
|
777
|
+
const processedData = cloneItem(sourceSchema, data, options)
|
|
778
|
+
const graph = new SchemaGraph()
|
|
779
|
+
|
|
780
|
+
const processBefore = (schema, data, name, dataPath, processedData) => {
|
|
781
|
+
let value = computeValue(schema, data, name, dataPath, options)
|
|
782
|
+
// The schema expects the `wrapPrimitives` transformations to be present on
|
|
783
|
+
// the data that it is applied on, so warp before and unwrap after.
|
|
784
|
+
if (isArray(value)) {
|
|
785
|
+
const { wrapPrimitives } = schema
|
|
786
|
+
if (wrapPrimitives) {
|
|
787
|
+
value = value.map(entry => ({
|
|
788
|
+
[wrapPrimitives]: entry
|
|
789
|
+
}))
|
|
790
|
+
} else {
|
|
791
|
+
// Always shallow-clone array values:
|
|
792
|
+
value = [...value]
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
processedData[name] = value
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const processAfter = (schema, data, name, dataPath, processedData) => {
|
|
799
|
+
const { wrapPrimitives, process } = schema
|
|
800
|
+
let value = processedData[name]
|
|
801
|
+
|
|
802
|
+
// NOTE: We don't cache this context, since `value` is changing.
|
|
803
|
+
const context = () =>
|
|
804
|
+
new DitoContext(component, {
|
|
805
|
+
schema,
|
|
806
|
+
value,
|
|
807
|
+
name,
|
|
808
|
+
data,
|
|
809
|
+
dataPath,
|
|
810
|
+
rootData: options.rootData,
|
|
811
|
+
// Pass the already processed data to `process()`, so it can be modified
|
|
812
|
+
// through `processedItem` from there.
|
|
813
|
+
processedData
|
|
814
|
+
})
|
|
815
|
+
|
|
816
|
+
// First unwrap the wrapped primitives again, to bring the data back into
|
|
817
|
+
// its native form. Se `processBefore()` for more details.
|
|
818
|
+
if (wrapPrimitives && isArray(value)) {
|
|
819
|
+
value = value.map(object => object[wrapPrimitives])
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Each component type can provide its own static `processValue()` method
|
|
823
|
+
// to convert the data for storage.
|
|
824
|
+
const processValue = getTypeOptions(schema)?.processValue
|
|
825
|
+
if (processValue) {
|
|
826
|
+
value = processValue(getContext(context), graph)
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Handle the user's `process()` callback next, if one is provided, so that
|
|
830
|
+
// it can modify data in `processedData` even if it provides `exclude: true`
|
|
831
|
+
if (process) {
|
|
832
|
+
value = process(getContext(context))
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (shouldExcludeValue(schema, context)) {
|
|
836
|
+
delete processedData[name]
|
|
837
|
+
} else {
|
|
838
|
+
processedData[name] = value
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
processSchemaData(
|
|
843
|
+
schema,
|
|
844
|
+
data,
|
|
845
|
+
dataPath,
|
|
846
|
+
processedData,
|
|
847
|
+
processBefore,
|
|
848
|
+
processAfter,
|
|
849
|
+
options
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
return graph.process(sourceSchema, processedData, options)
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
export function processSchemaData(
|
|
856
|
+
schema,
|
|
857
|
+
data,
|
|
858
|
+
dataPath,
|
|
859
|
+
processedData,
|
|
860
|
+
processBefore,
|
|
861
|
+
processAfter,
|
|
862
|
+
options
|
|
863
|
+
) {
|
|
864
|
+
const processComponents = components => {
|
|
865
|
+
const getDataPath = (dataPath, token) =>
|
|
866
|
+
dataPath != null
|
|
867
|
+
? appendDataPath(dataPath, token)
|
|
868
|
+
: null
|
|
869
|
+
|
|
870
|
+
if (components) {
|
|
871
|
+
for (const [name, componentSchema] of Object.entries(components)) {
|
|
872
|
+
if (!isNested(componentSchema)) {
|
|
873
|
+
// Recursively process data on unnested components.
|
|
874
|
+
processSchemaData(
|
|
875
|
+
componentSchema,
|
|
876
|
+
data,
|
|
877
|
+
dataPath,
|
|
878
|
+
processedData,
|
|
879
|
+
processBefore,
|
|
880
|
+
processAfter,
|
|
881
|
+
options
|
|
882
|
+
)
|
|
883
|
+
} else {
|
|
884
|
+
const componentDataPath = getDataPath(dataPath, name)
|
|
885
|
+
|
|
886
|
+
const processItem = (item, index = null) => {
|
|
887
|
+
const itemDataPath =
|
|
888
|
+
index !== null
|
|
889
|
+
? getDataPath(componentDataPath, index)
|
|
890
|
+
: componentDataPath
|
|
891
|
+
const context = new DitoContext(options.component, {
|
|
892
|
+
schema: componentSchema,
|
|
893
|
+
data,
|
|
894
|
+
dataPath: componentDataPath,
|
|
895
|
+
name,
|
|
896
|
+
rootData: options.rootData
|
|
897
|
+
})
|
|
898
|
+
const getForms = (
|
|
899
|
+
getTypeOptions(componentSchema)?.getFormSchemasForProcessing ||
|
|
900
|
+
getFormSchemas
|
|
901
|
+
)
|
|
902
|
+
const forms = getForms(componentSchema, context)
|
|
903
|
+
const form = getItemFormSchemaFromForms(forms, item)
|
|
904
|
+
if (form) {
|
|
905
|
+
const processedItem = processedData
|
|
906
|
+
? cloneItem(componentSchema, item, options)
|
|
907
|
+
: null
|
|
908
|
+
return processSchemaData(
|
|
909
|
+
form,
|
|
910
|
+
item,
|
|
911
|
+
itemDataPath,
|
|
912
|
+
processedItem,
|
|
913
|
+
processBefore,
|
|
914
|
+
processAfter,
|
|
915
|
+
options
|
|
916
|
+
)
|
|
917
|
+
} else {
|
|
918
|
+
// Items without forms still get fully (but shallowly) cloned.
|
|
919
|
+
// TODO: Find out of this is actually needed / used at all?
|
|
920
|
+
return { ...item }
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
processBefore?.(
|
|
925
|
+
componentSchema,
|
|
926
|
+
data,
|
|
927
|
+
name,
|
|
928
|
+
componentDataPath,
|
|
929
|
+
processedData
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
let value = processedData ? processedData[name] : data[name]
|
|
933
|
+
if (value != null && hasFormSchema(componentSchema)) {
|
|
934
|
+
// Recursively process data on nested form items.
|
|
935
|
+
if (isArray(value)) {
|
|
936
|
+
// Optimization: No need to collect values if we're not cloning!
|
|
937
|
+
value = processedData
|
|
938
|
+
? value.map(processItem)
|
|
939
|
+
: value.forEach(processItem)
|
|
940
|
+
} else {
|
|
941
|
+
value = processItem(value)
|
|
942
|
+
}
|
|
943
|
+
if (processedData) {
|
|
944
|
+
processedData[name] = value
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
processAfter?.(
|
|
949
|
+
componentSchema,
|
|
950
|
+
data,
|
|
951
|
+
name,
|
|
952
|
+
componentDataPath,
|
|
953
|
+
processedData
|
|
954
|
+
)
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
processComponents(schema.components)
|
|
961
|
+
for (const tab of getTabSchemas(schema)) {
|
|
962
|
+
processComponents(tab.components)
|
|
963
|
+
}
|
|
964
|
+
for (const panel of getPanelSchemas(schema)) {
|
|
965
|
+
processComponents(panel.components)
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
return processedData || data
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
export function getNamedSchemas(schemas, defaults) {
|
|
972
|
+
const toObject = (array, toSchema) => {
|
|
973
|
+
return array.length > 0
|
|
974
|
+
? array.reduce((object, value) => {
|
|
975
|
+
const schema = toSchema(value)
|
|
976
|
+
if (schema) {
|
|
977
|
+
object[schema.name] =
|
|
978
|
+
schema && defaults
|
|
979
|
+
? { ...defaults, ...schema }
|
|
980
|
+
: schema
|
|
981
|
+
}
|
|
982
|
+
return object
|
|
983
|
+
}, {})
|
|
984
|
+
: null
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
return isArray(schemas)
|
|
988
|
+
? toObject(schemas, value =>
|
|
989
|
+
isObject(value)
|
|
990
|
+
? value
|
|
991
|
+
: {
|
|
992
|
+
name: camelize(value, false)
|
|
993
|
+
}
|
|
994
|
+
)
|
|
995
|
+
: isObject(schemas)
|
|
996
|
+
? toObject(
|
|
997
|
+
Object.entries(schemas),
|
|
998
|
+
([name, value]) =>
|
|
999
|
+
isObject(value)
|
|
1000
|
+
? {
|
|
1001
|
+
name,
|
|
1002
|
+
...value
|
|
1003
|
+
}
|
|
1004
|
+
: isString(value)
|
|
1005
|
+
? {
|
|
1006
|
+
name,
|
|
1007
|
+
label: value
|
|
1008
|
+
}
|
|
1009
|
+
: null
|
|
1010
|
+
)
|
|
1011
|
+
: null
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
export function getButtonSchemas(buttons) {
|
|
1015
|
+
return getNamedSchemas(
|
|
1016
|
+
buttons,
|
|
1017
|
+
{ type: 'button' } // Defaults
|
|
1018
|
+
)
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function getType(schemaOrType) {
|
|
1022
|
+
return isObject(schemaOrType) ? schemaOrType.type : schemaOrType
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
export function getTypeOptions(schemaOrType) {
|
|
1026
|
+
return getTypeComponent(getType(schemaOrType), true) ?? null
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
export function getSourceType(schemaOrType) {
|
|
1030
|
+
return (
|
|
1031
|
+
getTypeOptions(schemaOrType)?.getSourceType?.(getType(schemaOrType)) ??
|
|
1032
|
+
null
|
|
1033
|
+
)
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
export function getPanelEntry(schema, dataPath = null, tabComponent = null) {
|
|
1037
|
+
return schema
|
|
1038
|
+
? {
|
|
1039
|
+
schema,
|
|
1040
|
+
// If the panel provides its own name, append it to the dataPath.
|
|
1041
|
+
// This is used e.g. for $filters panels.
|
|
1042
|
+
dataPath:
|
|
1043
|
+
dataPath != null && schema.name
|
|
1044
|
+
? appendDataPath(dataPath, schema.name)
|
|
1045
|
+
: dataPath,
|
|
1046
|
+
tabComponent
|
|
1047
|
+
}
|
|
1048
|
+
: null
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
export function getPanelEntries(
|
|
1052
|
+
panelSchemas,
|
|
1053
|
+
dataPath,
|
|
1054
|
+
tabComponent = null,
|
|
1055
|
+
panelEntries = []
|
|
1056
|
+
) {
|
|
1057
|
+
if (panelSchemas) {
|
|
1058
|
+
for (const [key, schema] of Object.entries(panelSchemas)) {
|
|
1059
|
+
const entry = getPanelEntry(
|
|
1060
|
+
schema,
|
|
1061
|
+
appendDataPath(dataPath, key),
|
|
1062
|
+
tabComponent
|
|
1063
|
+
)
|
|
1064
|
+
if (entry) {
|
|
1065
|
+
panelEntries.push(entry)
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
return panelEntries
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
export function getTabSchemas(schema) {
|
|
1073
|
+
return schema?.tabs ? Object.values(schema.tabs) : []
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
export function getPanelSchemas(schema) {
|
|
1077
|
+
return schema?.panels ? Object.values(schema.panels) : []
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
export function getAllPanelEntries(
|
|
1081
|
+
api,
|
|
1082
|
+
schema,
|
|
1083
|
+
dataPath,
|
|
1084
|
+
component = null,
|
|
1085
|
+
tabComponent = null
|
|
1086
|
+
) {
|
|
1087
|
+
const panelSchema = getTypeOptions(schema)?.getPanelSchema?.(
|
|
1088
|
+
api,
|
|
1089
|
+
schema,
|
|
1090
|
+
dataPath,
|
|
1091
|
+
component
|
|
1092
|
+
)
|
|
1093
|
+
const panelEntries = panelSchema
|
|
1094
|
+
? [getPanelEntry(panelSchema, dataPath, tabComponent)]
|
|
1095
|
+
: []
|
|
1096
|
+
// Allow each component to provide its own set of panels, in
|
|
1097
|
+
// addition to the default one (e.g. getFiltersPanel(), $filters):
|
|
1098
|
+
getPanelEntries(schema?.panels, dataPath, tabComponent, panelEntries)
|
|
1099
|
+
return panelEntries
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
export function isObjectSource(schemaOrType) {
|
|
1103
|
+
return getSourceType(schemaOrType) === 'object'
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
export function isListSource(schemaOrType) {
|
|
1107
|
+
return getSourceType(schemaOrType) === 'list'
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
export function getItemId(sourceSchema, item) {
|
|
1111
|
+
const id = item[sourceSchema.idKey || 'id']
|
|
1112
|
+
return id != null ? String(id) : undefined
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
export function getItemUid(sourceSchema, item) {
|
|
1116
|
+
// Try to use the item id as the uid, falling back on auto-generated ids, but
|
|
1117
|
+
// either way, pass through `getUid()` so that the ids are associated with the
|
|
1118
|
+
// item through a weak map, as the ids can be filtered out in `processData()`
|
|
1119
|
+
// while the components that use the uids as key are still visible.
|
|
1120
|
+
return getUid(item, item => getItemId(sourceSchema, item))
|
|
1121
|
+
}
|