@ditojs/admin 2.100.0 → 3.1.0

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.
Files changed (81) hide show
  1. package/dist/dito-admin.css +1 -1
  2. package/dist/dito-admin.es.js +3555 -2719
  3. package/dist/dito-admin.umd.js +5 -6
  4. package/package.json +84 -46
  5. package/src/DitoAdmin.js +1 -65
  6. package/src/DitoComponent.js +1 -1
  7. package/src/DitoContext.js +51 -0
  8. package/src/DitoTypeComponent.js +23 -1
  9. package/src/components/DitoAffixes.vue +6 -3
  10. package/src/components/DitoButtons.vue +14 -1
  11. package/src/components/DitoContainer.vue +19 -10
  12. package/src/components/DitoCreateButton.vue +11 -2
  13. package/src/components/DitoDialog.vue +21 -2
  14. package/src/components/DitoForm.vue +152 -84
  15. package/src/components/DitoLabel.vue +3 -2
  16. package/src/components/DitoPane.vue +55 -11
  17. package/src/components/DitoPanel.vue +34 -5
  18. package/src/components/DitoPanels.vue +2 -1
  19. package/src/components/DitoRoot.vue +6 -6
  20. package/src/components/DitoRouterView.vue +27 -0
  21. package/src/components/DitoSchema.vue +278 -145
  22. package/src/components/DitoSchemaInlined.vue +1 -1
  23. package/src/components/DitoTabs.vue +16 -18
  24. package/src/components/DitoTrail.vue +5 -4
  25. package/src/components/DitoTreeItem.vue +17 -6
  26. package/src/components/DitoView.vue +21 -3
  27. package/src/components/index.js +1 -0
  28. package/src/mixins/DitoMixin.js +53 -7
  29. package/src/mixins/ItemMixin.js +3 -6
  30. package/src/mixins/NumberMixin.js +47 -31
  31. package/src/mixins/OptionsMixin.js +132 -71
  32. package/src/mixins/ResourceMixin.js +5 -2
  33. package/src/mixins/RouteMixin.js +39 -1
  34. package/src/mixins/SortableMixin.js +0 -13
  35. package/src/mixins/SourceMixin.js +155 -96
  36. package/src/mixins/TextMixin.js +1 -1
  37. package/src/mixins/TypeMixin.js +37 -22
  38. package/src/mixins/ValidationMixin.js +9 -27
  39. package/src/mixins/ValidatorMixin.js +5 -7
  40. package/src/mixins/ValueMixin.js +5 -2
  41. package/src/types/DitoTypeButton.vue +1 -1
  42. package/src/types/DitoTypeCheckbox.vue +1 -1
  43. package/src/types/DitoTypeCheckboxes.vue +1 -1
  44. package/src/types/DitoTypeCode.vue +8 -1
  45. package/src/types/DitoTypeColor.vue +20 -34
  46. package/src/types/DitoTypeComponent.vue +1 -1
  47. package/src/types/DitoTypeComputed.vue +4 -27
  48. package/src/types/DitoTypeDate.vue +16 -5
  49. package/src/types/DitoTypeLabel.vue +1 -4
  50. package/src/types/DitoTypeList.vue +9 -5
  51. package/src/types/DitoTypeMarkup.vue +25 -13
  52. package/src/types/DitoTypeMultiselect.vue +37 -6
  53. package/src/types/DitoTypeNumber.vue +1 -1
  54. package/src/types/DitoTypeObject.vue +10 -7
  55. package/src/types/DitoTypePanel.vue +20 -6
  56. package/src/types/DitoTypeProgress.vue +1 -1
  57. package/src/types/DitoTypeRadio.vue +1 -1
  58. package/src/types/DitoTypeSection.vue +32 -4
  59. package/src/types/DitoTypeSelect.vue +1 -1
  60. package/src/types/DitoTypeSlider.vue +1 -1
  61. package/src/types/DitoTypeSwitch.vue +1 -1
  62. package/src/types/DitoTypeText.vue +13 -15
  63. package/src/types/DitoTypeTextarea.vue +1 -1
  64. package/src/types/DitoTypeTreeList.vue +5 -7
  65. package/src/types/DitoTypeUpload.vue +1 -2
  66. package/src/utils/DataSchemaResolver.js +154 -0
  67. package/src/utils/FormModel.js +615 -0
  68. package/src/utils/data.js +20 -0
  69. package/src/utils/filter.js +3 -2
  70. package/src/utils/options.js +3 -0
  71. package/src/utils/request.js +70 -0
  72. package/src/utils/schema/data.js +659 -0
  73. package/src/utils/schema/lookup.js +250 -0
  74. package/src/utils/schema/setup.js +354 -0
  75. package/src/utils/schema/structure.js +172 -0
  76. package/src/utils/schema/types.js +34 -0
  77. package/src/utils/schema/validation.js +142 -0
  78. package/src/utils/units.js +70 -3
  79. package/types/index.d.ts +174 -59
  80. package/src/mixins/DataMixin.js +0 -131
  81. package/src/utils/schema.js +0 -1121
@@ -17,14 +17,26 @@
17
17
  </template>
18
18
 
19
19
  <script>
20
+ import { normalizeDataPath, parseDataPath } from '@ditojs/utils'
20
21
  import DitoTypeComponent from '../DitoTypeComponent.js'
22
+ import SchemaParentMixin from '../mixins/SchemaParentMixin.js'
21
23
  import { getSchemaAccessor } from '../utils/accessor.js'
22
- import { getItemFormSchema, processSchemaComponents } from '../utils/schema.js'
24
+ import { setupSchemaComponents } from '../utils/schema/setup.js'
25
+ import { getItemFormSchema } from '../utils/schema/lookup.js'
26
+ import { hasComponentNamed } from '../utils/schema/structure.js'
23
27
 
24
28
  // @vue/component
25
29
  export default DitoTypeComponent.register('section', {
26
- defaultValue: () => undefined, // Callback to override `defaultValue: null`
30
+ // Register the section's schema with the section rather than the form, so
31
+ // that the section can reveal its components, see `navigateToComponent()`.
32
+ mixins: [SchemaParentMixin],
33
+ // Nested sections hold their data in an object, other sections don't have
34
+ // their own value. Use a callback to override `defaultValue: null`.
35
+ defaultValue: ({ schema }) => (schema.nested ? {} : undefined),
27
36
  ignoreMissingValue: ({ schema }) => !schema.nested && !('default' in schema),
37
+ // The object of nested sections can't be `null`, unlike the value of the
38
+ // `object` type, where `null` means that there's no object.
39
+ treatNullAsMissing: ({ schema }) => !!schema.nested,
28
40
  defaultNested: false,
29
41
  generateLabel: false,
30
42
 
@@ -52,12 +64,28 @@ export default DitoTypeComponent.register('section', {
52
64
  },
53
65
 
54
66
  methods: {
55
- getItemFormSchema
67
+ getItemFormSchema,
68
+
69
+ async navigateToComponent(dataPath, onComplete, options) {
70
+ // Unnested sections share the data path of their parent schema, so
71
+ // decide by schema whether the section displays the value at `dataPath`:
72
+ const { mainSchemaComponent } = this
73
+ const dataPathPrefix = this.dataPath ? `${this.dataPath}/` : ''
74
+ const normalizedDataPath = normalizeDataPath(dataPath)
75
+ const componentName = normalizedDataPath.startsWith(dataPathPrefix)
76
+ ? parseDataPath(normalizedDataPath.slice(dataPathPrefix.length))[0]
77
+ : null
78
+ return (
79
+ !!mainSchemaComponent &&
80
+ hasComponentNamed(mainSchemaComponent.schema, componentName) &&
81
+ mainSchemaComponent.navigateToComponent(dataPath, onComplete, options)
82
+ )
83
+ }
56
84
  },
57
85
 
58
86
  async processSchema(api, schema, name, routes, level) {
59
87
  // Process section components so their forms get resolved too.
60
- await processSchemaComponents(api, schema, routes, level)
88
+ await setupSchemaComponents(api, schema, routes, level)
61
89
  }
62
90
  })
63
91
  </script>
@@ -11,7 +11,7 @@
11
11
  :parentContext="context"
12
12
  )
13
13
  select(
14
- :id="dataPath"
14
+ :id="componentPath"
15
15
  ref="element"
16
16
  v-model="selectedValue"
17
17
  v-bind="attributes"
@@ -1,7 +1,7 @@
1
1
  <template lang="pug">
2
2
  .dito-slider
3
3
  input.dito-range(
4
- :id="dataPath"
4
+ :id="componentPath"
5
5
  ref="element"
6
6
  v-model="inputValue"
7
7
  type="range"
@@ -1,6 +1,6 @@
1
1
  <template lang="pug">
2
2
  DitoSwitch.dito-switch(
3
- :id="dataPath"
3
+ :id="componentPath"
4
4
  ref="element"
5
5
  v-model="value"
6
6
  :labels="labels"
@@ -1,6 +1,6 @@
1
1
  <template lang="pug">
2
2
  DitoInput.dito-text(
3
- :id="dataPath"
3
+ :id="componentPath"
4
4
  ref="element"
5
5
  v-model="inputValue"
6
6
  :type="inputType"
@@ -54,6 +54,18 @@ export default DitoTypeComponent.register(
54
54
  textField: true,
55
55
  ignoreMissingValue: ({ schema }) => schema.type === 'password',
56
56
 
57
+ getTypeValidations(schema) {
58
+ const validationName = {
59
+ email: 'email',
60
+ url: 'url',
61
+ hostname: 'hostname',
62
+ domain: 'domain',
63
+ password: 'password',
64
+ creditcard: 'creditcard'
65
+ }[schema.type]
66
+ return validationName ? { [validationName]: true } : {}
67
+ },
68
+
57
69
  computed: {
58
70
  inputType() {
59
71
  return (
@@ -81,20 +93,6 @@ export default DitoTypeComponent.register(
81
93
  this.value = value
82
94
  }
83
95
  }
84
- },
85
-
86
- methods: {
87
- getValidations() {
88
- const rule = {
89
- email: 'email',
90
- url: 'url',
91
- hostname: 'hostname',
92
- domain: 'domain',
93
- password: 'password',
94
- creditcard: 'creditcard'
95
- }[this.type]
96
- return rule ? { [rule]: true } : {}
97
- }
98
96
  }
99
97
  }
100
98
  )
@@ -1,6 +1,6 @@
1
1
  <template lang="pug">
2
2
  textarea.dito-textarea.dito-input(
3
- :id="dataPath"
3
+ :id="componentPath"
4
4
  ref="element"
5
5
  v-model="value"
6
6
  v-bind="attributes"
@@ -18,17 +18,15 @@
18
18
  v-if="editPath && hasEditableForms"
19
19
  )
20
20
  //- Include a router-view for the optional DitoFormInlined
21
- RouterView
21
+ DitoRouterView(:routeLevel="routeComponent.routeLevel + 1")
22
22
  </template>
23
23
 
24
24
  <script>
25
25
  import DitoTypeComponent from '../DitoTypeComponent.js'
26
26
  import SourceMixin from '../mixins/SourceMixin.js'
27
- import {
28
- hasFormSchema,
29
- getFormSchemas,
30
- resolveSchemaComponents
31
- } from '../utils/schema.js'
27
+ import { resolveSchemaComponents } from '../utils/schema/setup.js'
28
+ import { hasFormSchema, getFormSchemas } from '../utils/schema/lookup.js'
29
+ import { getParentDataPath } from '../utils/data.js'
32
30
 
33
31
  export default DitoTypeComponent.register(
34
32
  ['tree-list', 'tree-object'],
@@ -66,7 +64,7 @@ export default DitoTypeComponent.register(
66
64
  // Remove `name` from `dataPath`, as it is added
67
65
  // to `treeData` and `treeSchema`
68
66
  return this.isListSource
69
- ? this.dataPath.slice(0, this.dataPath.length - this.name.length)
67
+ ? getParentDataPath(this.dataPath)
70
68
  : this.dataPath
71
69
  },
72
70
 
@@ -138,9 +138,8 @@
138
138
  import DitoTypeComponent from '../DitoTypeComponent.js'
139
139
  import DitoContext from '../DitoContext.js'
140
140
  import SortableMixin from '../mixins/SortableMixin.js'
141
- import parseFileSize from 'filesize-parser'
142
141
  import { getSchemaAccessor } from '../utils/accessor.js'
143
- import { formatFileSize } from '../utils/units.js'
142
+ import { formatFileSize, parseFileSize } from '../utils/units.js'
144
143
  import { appendDataPath } from '../utils/data.js'
145
144
  import { isArray, asArray } from '@ditojs/utils'
146
145
  import VueUpload from 'vue-upload-component'
@@ -0,0 +1,154 @@
1
+ import { computed, shallowRef } from 'vue'
2
+ import {
3
+ isObject,
4
+ isFunction,
5
+ isPromise,
6
+ normalizeDataPath,
7
+ getValueAtDataPath
8
+ } from '@ditojs/utils'
9
+
10
+ // DataSchemaResolver resolves a data schema, `{ data, dataPath }` as used by
11
+ // `schema.options` and by the `computed` types, to its value, lazily: Nothing
12
+ // is evaluated or loaded before `value` or `isLoading` is read.
13
+ //
14
+ // - `data` is the value itself, or a function that returns the value, a
15
+ // promise of it, or a function that loads it. In the last case, `data()`
16
+ // only tracks the dependencies and the returned function loads the value,
17
+ // e.g. `data: ({ item: { id } }) => async () => load(id)`, so that loading
18
+ // only starts again when these dependencies change.
19
+ // - `dataPath` is resolved relative to the data path of the context, in its
20
+ // root data, e.g. `'../fontFamily/fontShopSets'`.
21
+ // - Data schemas that aren't objects are values, e.g. `options: ['A', 'B']`.
22
+ //
23
+ // While a value is loading, `value` is `undefined` and `isLoading` is `true`.
24
+ // If loading fails, the error is logged and kept in `lastLoadError`. Results
25
+ // of outdated loads are discarded. Resolved values are returned as they are, so
26
+ // that their object identity is kept.
27
+ //
28
+ // `createContext()` returns the `DitoContext` that `data()` and the loading
29
+ // functions are called with. `onLoadStart(promise)` is called with the promise
30
+ // of each load, which resolves once the load finished, see `finishLoad()`.
31
+
32
+ export class DataSchemaResolver {
33
+ // The promise or loading function of the current load, see `startLoad()`:
34
+ currentPromiseOrLoader = null
35
+ // The promise of the current load, which resolves once it finished:
36
+ currentLoadPromise = null
37
+ // The promise or loading function of the last finished load, with its value
38
+ // or error, see `finishLoad()`:
39
+ lastFinishedLoad = shallowRef({
40
+ promiseOrLoader: null,
41
+ value: undefined,
42
+ error: null
43
+ })
44
+
45
+ constructor(dataSchema, { createContext, onLoadStart = null }) {
46
+ this.dataSchema = isObject(dataSchema) ? dataSchema : { data: dataSchema }
47
+ this.createContext = createContext
48
+ this.onLoadStart = onLoadStart
49
+ this.valueOrPromiseOrLoader = computed(() =>
50
+ this.getValueOrPromiseOrLoader()
51
+ )
52
+ this.valueOrUndefinedWhileLoading = computed(() =>
53
+ this.getValueOrStartLoad()
54
+ )
55
+ }
56
+
57
+ get value() {
58
+ return this.valueOrUndefinedWhileLoading.value
59
+ }
60
+
61
+ get isLoading() {
62
+ // Read `value` first, so that its loading starts:
63
+ if (this.value !== undefined) {
64
+ return false
65
+ }
66
+ const valueOrPromiseOrLoader = this.valueOrPromiseOrLoader.value
67
+ return (
68
+ isPromiseOrLoader(valueOrPromiseOrLoader) &&
69
+ this.lastFinishedLoad.value.promiseOrLoader !== valueOrPromiseOrLoader
70
+ )
71
+ }
72
+
73
+ // Returns the value, once the load of it finished if it is pending, also
74
+ // after loads that replace outdated ones, e.g. when the dependencies of the
75
+ // data schema change in the meantime. Returns `undefined` if loading fails.
76
+ async waitForValue() {
77
+ while (this.isLoading) {
78
+ await this.currentLoadPromise
79
+ }
80
+ return this.value
81
+ }
82
+
83
+ // The error of the last load, if it failed and isn't outdated:
84
+ get lastLoadError() {
85
+ const { promiseOrLoader, error } = this.lastFinishedLoad.value
86
+ return promiseOrLoader === this.valueOrPromiseOrLoader.value ? error : null
87
+ }
88
+
89
+ // Returns the value of the data schema, a promise of it, or a function that
90
+ // loads it. Called by a computed property, so that it only runs again when
91
+ // its dependencies change.
92
+ getValueOrPromiseOrLoader() {
93
+ const { data, dataPath } = this.dataSchema
94
+ if (data !== undefined) {
95
+ return isFunction(data) ? data(this.createContext()) : data
96
+ } else if (dataPath) {
97
+ const context = this.createContext()
98
+ // Data that isn't there, e.g. after it was reset by code, has no value,
99
+ // the same as a missing value at an existing data path.
100
+ return getValueAtDataPath(
101
+ context.rootItem,
102
+ normalizeDataPath(`${context.dataPath}/${dataPath}`),
103
+ () => undefined
104
+ )
105
+ }
106
+ }
107
+
108
+ // Returns the value of the data schema, or its loaded value once the load
109
+ // of its promise or loading function finished. Until then, it starts the
110
+ // load if it isn't started yet, and returns `undefined`. Called by a
111
+ // computed property, see `value`.
112
+ getValueOrStartLoad() {
113
+ const valueOrPromiseOrLoader = this.valueOrPromiseOrLoader.value
114
+ if (!isPromiseOrLoader(valueOrPromiseOrLoader)) {
115
+ return valueOrPromiseOrLoader
116
+ }
117
+ const { promiseOrLoader, value } = this.lastFinishedLoad.value
118
+ if (promiseOrLoader === valueOrPromiseOrLoader) {
119
+ return value
120
+ }
121
+ if (this.currentPromiseOrLoader !== valueOrPromiseOrLoader) {
122
+ this.startLoad(valueOrPromiseOrLoader)
123
+ }
124
+ return undefined
125
+ }
126
+
127
+ startLoad(promiseOrLoader) {
128
+ this.currentPromiseOrLoader = promiseOrLoader
129
+ const context = this.createContext()
130
+ // Keeps the result in `lastFinishedLoad`, unless the load is outdated:
131
+ const finishLoad = (value, error = null) => {
132
+ if (this.currentPromiseOrLoader === promiseOrLoader) {
133
+ this.lastFinishedLoad.value = { promiseOrLoader, value, error }
134
+ }
135
+ }
136
+ // Call loading functions in a microtask, outside of the computed property
137
+ // that started the load, so that they don't add to its dependencies.
138
+ const promise = Promise.resolve()
139
+ .then(() =>
140
+ isFunction(promiseOrLoader) ? promiseOrLoader(context) : promiseOrLoader
141
+ )
142
+ .then(value => finishLoad(value))
143
+ .catch(error => {
144
+ console.error(error)
145
+ finishLoad(undefined, error)
146
+ })
147
+ this.currentLoadPromise = promise
148
+ this.onLoadStart?.(promise)
149
+ }
150
+ }
151
+
152
+ function isPromiseOrLoader(valueOrPromiseOrLoader) {
153
+ return isFunction(valueOrPromiseOrLoader) || isPromise(valueOrPromiseOrLoader)
154
+ }