@ditojs/admin 3.2.6 → 3.3.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 (112) hide show
  1. package/dist/dito-admin.css +1 -1
  2. package/dist/dito-admin.es.js +6012 -5827
  3. package/dist/dito-admin.umd.js +3 -7
  4. package/package.json +18 -17
  5. package/src/DitoAdmin.js +44 -24
  6. package/src/DitoContext.js +28 -2
  7. package/src/DitoTypeComponent.js +1 -0
  8. package/src/appState.js +6 -3
  9. package/src/components/DitoAccount.vue +35 -27
  10. package/src/components/DitoAffixes.vue +9 -2
  11. package/src/components/DitoClipboard.vue +59 -93
  12. package/src/components/DitoConfirmMessage.vue +18 -0
  13. package/src/components/DitoContainer.vue +56 -18
  14. package/src/components/DitoCreateButton.vue +36 -84
  15. package/src/components/DitoDialog.vue +20 -15
  16. package/src/components/DitoDragHandle.vue +44 -0
  17. package/src/components/DitoDraggable.vue +43 -16
  18. package/src/components/DitoEditButtons.vue +48 -24
  19. package/src/components/DitoErrors.vue +9 -56
  20. package/src/components/DitoForm.vue +82 -42
  21. package/src/components/DitoInputAffixes.vue +50 -0
  22. package/src/components/DitoLabel.vue +1 -15
  23. package/src/components/DitoMenu.vue +45 -52
  24. package/src/components/DitoNotifications.vue +38 -14
  25. package/src/components/DitoOptionList.vue +81 -0
  26. package/src/components/DitoOptionsEditButtons.vue +57 -0
  27. package/src/components/DitoPagination.vue +1 -8
  28. package/src/components/DitoPane.vue +51 -39
  29. package/src/components/DitoPanel.vue +12 -26
  30. package/src/components/DitoPanels.vue +11 -5
  31. package/src/components/DitoResizeHandle.vue +47 -0
  32. package/src/components/DitoRoot.vue +113 -320
  33. package/src/components/DitoSchema.vue +72 -63
  34. package/src/components/DitoSchemaInlined.vue +8 -9
  35. package/src/components/DitoScopes.vue +14 -18
  36. package/src/components/DitoSidebar.vue +4 -29
  37. package/src/components/DitoSourceItem.vue +114 -0
  38. package/src/components/DitoTableCell.vue +4 -0
  39. package/src/components/DitoTableHead.vue +15 -17
  40. package/src/components/DitoTabs.vue +25 -0
  41. package/src/components/DitoTrail.vue +25 -25
  42. package/src/components/DitoTreeItem.vue +102 -141
  43. package/src/components/DitoUploadFile.vue +5 -2
  44. package/src/components/DitoView.vue +33 -24
  45. package/src/index.js +4 -1
  46. package/src/mixins/DitoMixin.js +77 -110
  47. package/src/mixins/DomMixin.js +12 -14
  48. package/src/mixins/EmitterMixin.js +3 -2
  49. package/src/mixins/ItemMixin.js +38 -94
  50. package/src/mixins/OptionsMixin.js +38 -25
  51. package/src/mixins/ResourceMixin.js +182 -111
  52. package/src/mixins/RouteMixin.js +61 -44
  53. package/src/mixins/SchemaParentMixin.js +4 -1
  54. package/src/mixins/SourceMixin.js +118 -100
  55. package/src/mixins/TypeMixin.js +42 -40
  56. package/src/mixins/ValidationMixin.js +16 -4
  57. package/src/mixins/ValidatorMixin.js +1 -1
  58. package/src/styles/_settings.scss +4 -0
  59. package/src/styles/_tippy.scss +0 -11
  60. package/src/styles/style.scss +0 -1
  61. package/src/types/DitoTypeButton.vue +11 -14
  62. package/src/types/DitoTypeCheckboxes.vue +35 -17
  63. package/src/types/DitoTypeCode.vue +19 -41
  64. package/src/types/DitoTypeColor.vue +67 -19
  65. package/src/types/DitoTypeDate.vue +11 -14
  66. package/src/types/DitoTypeList.vue +50 -62
  67. package/src/types/DitoTypeMarkup.vue +41 -54
  68. package/src/types/DitoTypeMultiselect.vue +82 -52
  69. package/src/types/DitoTypeNumber.vue +12 -15
  70. package/src/types/DitoTypeObject.vue +4 -38
  71. package/src/types/DitoTypeRadio.vue +19 -6
  72. package/src/types/DitoTypeSection.vue +23 -9
  73. package/src/types/DitoTypeSelect.vue +39 -23
  74. package/src/types/DitoTypeSlider.vue +9 -1
  75. package/src/types/DitoTypeSwitch.vue +1 -1
  76. package/src/types/DitoTypeText.vue +11 -14
  77. package/src/types/DitoTypeTreeList.vue +46 -8
  78. package/src/types/DitoTypeUpload.vue +190 -67
  79. package/src/utils/DataModel.js +368 -146
  80. package/src/utils/FileDropTargets.js +97 -0
  81. package/src/utils/ItemStores.js +31 -0
  82. package/src/utils/ListQuery.js +115 -0
  83. package/src/utils/LoadingTracker.js +76 -0
  84. package/src/utils/SchemaGraph.js +3 -2
  85. package/src/utils/Session.js +151 -0
  86. package/src/utils/ViewRegistry.js +85 -0
  87. package/src/utils/accessor.js +12 -4
  88. package/src/utils/affixes.js +33 -0
  89. package/src/utils/changeOnceEdited.js +33 -0
  90. package/src/utils/clipboard.js +55 -0
  91. package/src/utils/data.js +10 -5
  92. package/src/utils/dialogs.js +59 -0
  93. package/src/utils/dragResize.js +61 -0
  94. package/src/utils/filter.js +36 -2
  95. package/src/utils/html.js +9 -0
  96. package/src/utils/list.js +10 -0
  97. package/src/utils/options.js +4 -0
  98. package/src/utils/registration.js +18 -0
  99. package/src/utils/request.js +108 -13
  100. package/src/utils/route.js +6 -17
  101. package/src/utils/schema/data.js +29 -2
  102. package/src/utils/schema/events.js +19 -0
  103. package/src/utils/schema/item.js +114 -0
  104. package/src/utils/schema/lookup.js +39 -14
  105. package/src/utils/schema/setup.js +5 -5
  106. package/src/utils/schema/validation.js +16 -2
  107. package/src/utils/uid.js +105 -8
  108. package/src/validators/_decimals.js +16 -5
  109. package/types/index.d.ts +168 -41
  110. package/src/mixins/LoadingMixin.js +0 -23
  111. package/src/mixins/PulldownMixin.js +0 -72
  112. package/src/styles/_pulldown.scss +0 -38
@@ -1,30 +1,37 @@
1
1
  <template lang="pug">
2
- .dito-trail(aria-label="Breadcrumb")
2
+ nav.dito-trail(aria-label="Breadcrumb")
3
3
  ul
4
4
  li(
5
5
  v-for="component in trail"
6
6
  )
7
- a.dito-trail__link(
8
- :class="{ 'dito-trail__link--active': component.path === $route.path }"
9
- :aria-current="component.path === $route.path ? 'page' : null"
10
- :href="getComponentHref(component)"
11
- @click.prevent.stop="onClickComponent(component)"
7
+ RouterLink(
8
+ v-slot="{ href, navigate }"
9
+ :to="getComponentRoute(component)"
10
+ custom
12
11
  )
13
- span.dito-trail__text(
14
- :class="{ 'dito-trail__text--dirty': component.isDirty }"
12
+ a.dito-trail__link(
13
+ :class="{ 'dito-trail__link--active': isCurrentComponent(component) }"
14
+ :aria-current="isCurrentComponent(component) ? 'page' : null"
15
+ :href="href"
16
+ @click="navigate"
15
17
  )
16
- | {{ component.breadcrumb }}
18
+ span.dito-trail__text(
19
+ :class="{ 'dito-trail__text--dirty': component.isDirty }"
20
+ )
21
+ | {{ component.breadcrumb }}
17
22
  slot
18
23
  </template>
19
24
 
20
25
  <script>
21
26
  import DitoComponent from '../DitoComponent.js'
27
+ import { getViewLinkPath } from '../utils/schema/lookup.js'
22
28
 
23
29
  // @vue/component
24
30
  export default DitoComponent.component('DitoTrail', {
25
31
  computed: {
26
32
  trail() {
27
- // Leave out the components that are being left, until they're unmounted.
33
+ // The route components by route level. Leave out the components that are
34
+ // being left, until they're unmounted.
28
35
  return this.appState.routeComponents.filter(
29
36
  component => !component.isLeavingRoute
30
37
  )
@@ -32,23 +39,16 @@ export default DitoComponent.component('DitoTrail', {
32
39
  },
33
40
 
34
41
  methods: {
35
- getComponentRoutePath(component) {
36
- // Do the same as in `DitoMenu`: Link menu items to the first children.
37
- const { schema } = component
38
- return schema.type === 'menu'
39
- ? Object.values(schema.items)[0].fullPath
40
- : component.path
41
- },
42
-
43
- getComponentHref(component) {
44
- return this.$router.resolve(this.getComponentRoutePath(component)).href
42
+ getComponentRoute(component) {
43
+ // `force` reloads the view even if it is already the current route.
44
+ return {
45
+ path: getViewLinkPath(component.schema, component.path),
46
+ force: true
47
+ }
45
48
  },
46
49
 
47
- onClickComponent(component) {
48
- this.$router.push({
49
- path: this.getComponentRoutePath(component),
50
- force: true
51
- })
50
+ isCurrentComponent(component) {
51
+ return component.path === this.$route.path
52
52
  }
53
53
  }
54
54
  })
@@ -6,7 +6,6 @@
6
6
  'dito-tree-item--active': active
7
7
  }`
8
8
  :style="level > 0 && { '--level': level }"
9
- :data-path="path"
10
9
  )
11
10
  .dito-tree-header(
12
11
  v-if="label"
@@ -36,26 +35,24 @@
36
35
  .dito-tree-label(
37
36
  v-html="label"
38
37
  )
39
- .dito-buttons.dito-buttons--small(
38
+ DitoEditButtons.dito-buttons--small(
40
39
  v-if="hasEditButtons"
40
+ :round="false"
41
+ :schema="schema"
42
+ :formSchema="getItemFormSchema(schema, data, context)"
43
+ :dataPath="dataPath"
44
+ :data="data"
45
+ :meta="nestedMeta"
46
+ :store="store"
47
+ :disabled="disabled"
48
+ :draggable="draggable"
49
+ :editable="editable"
50
+ :deletable="deletable"
51
+ :editPath="editPath"
52
+ :editQuery="$route.query"
53
+ @delete="$emit('delete')"
54
+ @move="delta => $emit('move', delta)"
41
55
  )
42
- //- Firefox doesn't like <button> here, so use <a> instead:
43
- a.dito-button(
44
- v-if="draggable"
45
- v-bind="getButtonAttributes(verbs.drag)"
46
- )
47
- button.dito-button(
48
- v-if="editable"
49
- type="button"
50
- v-bind="getButtonAttributes(verbs.edit)"
51
- @click="onEdit"
52
- )
53
- button.dito-button(
54
- v-if="deletable"
55
- type="button"
56
- v-bind="getButtonAttributes(verbs.delete)"
57
- @click="onDelete"
58
- )
59
56
  table.dito-properties(
60
57
  v-if="properties"
61
58
  v-show="opened"
@@ -70,8 +67,8 @@
70
67
  )
71
68
  DitoTableCell(
72
69
  :cell="property"
73
- :schema="property"
74
- :dataPath="getPropertyDataPath(property)"
70
+ :schema="schema"
71
+ :dataPath="dataPath"
75
72
  :data="data"
76
73
  :meta="nestedMeta"
77
74
  :store="store"
@@ -83,7 +80,7 @@
83
80
  :options="getDraggableOptions(true)"
84
81
  :draggable="childrenDraggable"
85
82
  :modelValue="childrenList"
86
- @update:modelValue="value => (childrenList = value)"
83
+ @update:modelValue="changeChildren"
87
84
  )
88
85
  DitoTreeItem(
89
86
  v-for="(item, index) in childrenItems"
@@ -97,7 +94,9 @@
97
94
  :draggable="childrenDraggable"
98
95
  :label="getItemLabel(childrenSchema, item.data, { index })"
99
96
  :level="level + 1"
100
- @delete="deleteChild(index)"
97
+ @delete="deleteChild(item.data)"
98
+ @move="delta => moveChild(item.data, delta)"
99
+ @change-children="change => $emit('changeChildren', change)"
101
100
  )
102
101
  //- TODO: Convert dito-tree-item to use dito-label internally, and then
103
102
  //- pass `asObject: true` in the `getItemLabel()` call above.
@@ -105,19 +104,25 @@
105
104
 
106
105
  <script>
107
106
  import DitoComponent from '../DitoComponent.js'
107
+ import DitoEditButtons from './DitoEditButtons.vue'
108
108
  import ItemMixin from '../mixins/ItemMixin'
109
109
  import SortableMixin from '../mixins/SortableMixin.js'
110
110
  import { appendDataPath, getRelativeDataPath } from '../utils/data.js'
111
111
  import { getSchemaAccessor } from '../utils/accessor.js'
112
112
  import { getNamedSchemas, hasFormSchema } from '../utils/schema/lookup.js'
113
- import { updateOrder } from '../utils/schema/data.js'
114
- import { stripHtml } from '@ditojs/utils'
115
-
113
+ import { getTextFromHtml } from '../utils/html.js'
114
+ import { getListWithMovedItem } from '../utils/list.js'
115
+ import { confirmAndRemove } from '../utils/dialogs.js'
116
+ import { isPathWithin } from '../utils/route.js'
117
+
118
+ // Renders an item of a tree list or tree object, see `DitoTypeTreeList`, which
119
+ // is the item's `sourceComponent`. Items don't write the tree: They emit the
120
+ // changed children with `changeChildren`, which the tree list applies.
116
121
  // @vue/component
117
122
  export default DitoComponent.component('DitoTreeItem', {
118
123
  mixins: [ItemMixin, SortableMixin],
119
- emits: ['update:data', 'delete'],
120
- inject: ['container'],
124
+ components: { DitoEditButtons },
125
+ emits: ['delete', 'move', 'changeChildren'],
121
126
 
122
127
  props: {
123
128
  schema: { type: Object, required: true },
@@ -142,7 +147,7 @@ export default DitoComponent.component('DitoTreeItem', {
142
147
  // Continue the path of the tree list with the item indices and children
143
148
  // names of the data path. The root item's data path doesn't contain the
144
149
  // list's name, see `DitoTypeTreeList.treeDataPath`.
145
- const { componentPath, dataPath } = this.container
150
+ const { componentPath, dataPath } = this.sourceComponent
146
151
  const relativeDataPath = getRelativeDataPath(this.dataPath, dataPath)
147
152
  return relativeDataPath
148
153
  ? appendDataPath(componentPath, relativeDataPath)
@@ -150,15 +155,15 @@ export default DitoComponent.component('DitoTreeItem', {
150
155
  },
151
156
 
152
157
  meta() {
153
- return this.container.meta
158
+ return this.sourceComponent.meta
154
159
  },
155
160
 
156
161
  store() {
157
- return this.container.store
162
+ return this.sourceComponent.store
158
163
  },
159
164
 
160
165
  disabled() {
161
- return this.container.disabled
166
+ return this.sourceComponent.disabled
162
167
  },
163
168
 
164
169
  nestedMeta() {
@@ -172,26 +177,13 @@ export default DitoComponent.component('DitoTreeItem', {
172
177
  return getNamedSchemas(this.schema.properties)
173
178
  },
174
179
 
175
- // TODO: Should this be named `sourceSchema` instead? Use SourceMixin?
176
180
  childrenSchema() {
177
181
  return this.schema.children
178
182
  },
179
183
 
180
- childrenList: {
181
- get() {
182
- const name = this.childrenSchema?.name
183
- return name && this.data[name]
184
- },
185
-
186
- set(value) {
187
- const name = this.childrenSchema?.name
188
- if (name) {
189
- updateOrder(this.childrenSchema, value)
190
- // eslint-disable-next-line vue/no-mutating-props
191
- this.data[name] = value
192
- this.$emit('update:data', value)
193
- }
194
- }
184
+ childrenList() {
185
+ const name = this.childrenSchema?.name
186
+ return name && this.data[name]
195
187
  },
196
188
 
197
189
  childrenDraggable() {
@@ -220,7 +212,7 @@ export default DitoComponent.component('DitoTreeItem', {
220
212
  childrenItems() {
221
213
  const { childrenSchema, childrenList } = this
222
214
  if (childrenSchema && childrenList) {
223
- const { editPath } = this.container
215
+ const { editPath } = this.sourceComponent
224
216
  const childrenOpen = !this.path && childrenSchema.open
225
217
  // Build a children list with child meta information for the template.
226
218
  return childrenList.map((data, index) => {
@@ -231,7 +223,7 @@ export default DitoComponent.component('DitoTreeItem', {
231
223
  const open = (
232
224
  childrenOpen ||
233
225
  // Only count as "in edit path" when it's not the full edit path.
234
- editPath.startsWith(path) && path.length < editPath.length
226
+ !!path && editPath !== path && isPathWithin(editPath, path)
235
227
  )
236
228
  const active = editPath === path
237
229
  return { data, path, open, active }
@@ -241,7 +233,7 @@ export default DitoComponent.component('DitoTreeItem', {
241
233
  },
242
234
 
243
235
  ariaLabel() {
244
- return this.label ? stripHtml(this.label) : null
236
+ return this.label ? getTextFromHtml(this.label) : null
245
237
  },
246
238
 
247
239
  details() {
@@ -258,17 +250,13 @@ export default DitoComponent.component('DitoTreeItem', {
258
250
  return this.draggable || this.editable || this.deletable
259
251
  },
260
252
 
261
- // TODO: Support creatable!
262
- // TODO: Add support for creatable, editable and deletable overrides on the
263
- // associated forms, just like in `TypeList` and `TypeObject`, through
264
- // `DitoEditButtons`. It would be best to use `DitoEditButtons` here too.
265
- creatable: getSchemaAccessor('creatable', {
266
- type: Boolean,
267
- default: false,
268
- get(creatable) {
269
- return creatable && hasFormSchema(this.schema)
270
- }
271
- }),
253
+ editPath() {
254
+ // The edit routes of the items are handled by the routes of the tree
255
+ // list, allowing reloads as well, see `DitoTypeTreeList.processSchema()`.
256
+ return this.editable && this.path
257
+ ? `${this.sourceComponent.path}${this.path}`
258
+ : null
259
+ },
272
260
 
273
261
  editable: getSchemaAccessor('editable', {
274
262
  type: Boolean,
@@ -284,91 +272,60 @@ export default DitoComponent.component('DitoTreeItem', {
284
272
  })
285
273
  },
286
274
 
287
- methods: {
288
- getPropertyDataPath(property) {
289
- return appendDataPath(this.dataPath, property.name)
290
- },
291
-
292
- editPath(path) {
293
- // All we got to do is push the right edit path to the router, the rest
294
- // is handled by our routes, allowing reloads as well.
295
- this.$router.push({
296
- path: `${this.container.path}${path}`,
297
- // Preserve current query
298
- query: this.$route.query
299
- })
300
- },
301
-
302
- onEdit() {
303
- this.editPath(this.path)
304
- },
305
-
306
- // Items are removed from the children of their parent, see
307
- // `deleteChild()`.
308
- onDelete() {
309
- this.$emit('delete')
310
- },
275
+ watch: {
276
+ open(open) {
277
+ // Open the item also when it comes into the edit path later, e.g. when
278
+ // navigating to the form of one of its children.
279
+ if (open) {
280
+ this.opened = true
281
+ }
282
+ }
283
+ },
311
284
 
312
- // Removes the child after confirming it, like inlined lists do. Open forms
313
- // of the child or of its later siblings are closed, as the removal shifts
314
- // the indices in their paths. The removal is transient, as the tree is
315
- // part of its form's data.
316
- deleteChild(index) {
317
- const { childrenSchema, container } = this
318
- const item = this.childrenList[index]
319
- const label = this.getItemLabel(childrenSchema, item, {
320
- index,
321
- extended: true
285
+ methods: {
286
+ changeChildren(children) {
287
+ this.$emit('changeChildren', {
288
+ data: this.data,
289
+ childrenSchema: this.childrenSchema,
290
+ path: this.path,
291
+ children
322
292
  })
323
- if (
324
- window.confirm(`Do you really want to ${this.verbs.delete} ${label}?`)
325
- ) {
326
- if (this.isEditingChildFrom(index)) {
327
- this.$router.push({
328
- path: container.path,
329
- query: this.$route.query
330
- })
331
- }
332
- this.childrenList = this.childrenList.toSpliced(index, 1)
333
- this.onChange()
334
- this.notify({
335
- type: 'info',
336
- title: 'Successfully Removed',
337
- text: [`${label} was ${this.verbs.deleted}.`, container.transientNote]
338
- })
339
- }
340
293
  },
341
294
 
342
- // Returns whether the form of a child at the index or after it is open,
343
- // or the form of one of their own children.
344
- isEditingChildFrom(index) {
345
- const childrenPath = this.childrenSchema.path
346
- if (!childrenPath) {
347
- return false
295
+ // Moves the child by `delta` positions, see `DitoDragHandle`.
296
+ moveChild(child, delta) {
297
+ const children = getListWithMovedItem(this.childrenList, child, delta)
298
+ if (children) {
299
+ this.changeChildren(children)
348
300
  }
349
- const prefix = `${this.path}/${childrenPath}/`
350
- const { editPath } = this.container
351
- if (!editPath.startsWith(prefix)) {
352
- return false
353
- }
354
- const [childIndex] = editPath.slice(prefix.length).split('/')
355
- return Number(childIndex) >= index
356
301
  },
357
302
 
358
- onChange() {
359
- this.container.onChange()
303
+ // Removes the child after confirming it, like inlined lists do. The removal
304
+ // is transient, as the tree is part of its form's data.
305
+ async deleteChild(child) {
306
+ await confirmAndRemove(this, {
307
+ label: this.getItemLabel(this.childrenSchema, child, {
308
+ index: this.childrenList.indexOf(child),
309
+ extended: true
310
+ }),
311
+ isTransient: true,
312
+ remove: () => {
313
+ // Look up the child by identity once confirmed, as the children may
314
+ // have changed while the dialog was open.
315
+ const children = this.childrenList.filter(it => it !== child)
316
+ if (children.length === this.childrenList.length) {
317
+ return false
318
+ }
319
+ this.changeChildren(children)
320
+ }
321
+ })
360
322
  },
361
323
 
362
324
  // @override
363
- onEndDrag(event) {
364
- SortableMixin.methods.onEndDrag.call(this, event)
365
- const { item } = event
366
- // Preserve active state of edited sub-items, by editing their new path.
367
- if (item.classList.contains('dito-tree-item--active')) {
368
- this.$nextTick(() => {
369
- this.editPath(event.item.dataset.path)
370
- })
371
- }
325
+ onEndDrag() {
326
+ // The tree list applies the reordered children and notifies of the
327
+ // change, see `DitoTypeTreeList.changeChildren()`.
328
+ this.isDragging = false
372
329
  }
373
330
  }
374
331
  })
@@ -444,18 +401,22 @@ export default DitoComponent.component('DitoTreeItem', {
444
401
  .dito-buttons {
445
402
  flex: auto;
446
403
  display: flex;
447
- visibility: hidden;
404
+ // Transparent rather than hidden, so the buttons can be focused with the
405
+ // keyboard, which shows them, see below.
406
+ opacity: 0;
448
407
  height: 100%;
449
408
  margin: 1px;
450
409
  margin-right: 0;
451
410
  }
452
411
 
453
- .dito-tree-header:hover {
412
+ .dito-tree-header:hover,
413
+ .dito-tree-header:focus-within {
454
414
  > .dito-buttons {
455
- visibility: visible;
456
- } // Hide buttons during dragging
415
+ opacity: 1;
416
+ }
457
417
  }
458
418
 
419
+ // Hide buttons during dragging
459
420
  &--dragging {
460
421
  .dito-tree-header {
461
422
  > .dito-buttons {
@@ -24,6 +24,9 @@ import DitoComponent from '../DitoComponent.js'
24
24
  export default DitoComponent.component('DitoUploadFile', {
25
25
  props: {
26
26
  file: { type: Object, required: true },
27
+ // The file object of the upload component while the file is added through
28
+ // it, see `DitoTypeUpload.getUpload()`.
29
+ upload: { type: Object, default: null },
27
30
  thumbnail: { type: String, default: null },
28
31
  thumbnailUrl: { type: String, default: null }
29
32
  },
@@ -48,7 +51,7 @@ export default DitoComponent.component('DitoUploadFile', {
48
51
  },
49
52
 
50
53
  watch: {
51
- 'file.upload.file': {
54
+ 'upload.file': {
52
55
  immediate: true,
53
56
  handler(file) {
54
57
  if (this.thumbnail && file?.type.startsWith('image/')) {
@@ -74,7 +77,7 @@ const TYPES = {
74
77
  'image/png': 'PNG',
75
78
  'image/gif': 'GIF',
76
79
  'image/svg+xml': 'SVG',
77
- 'movie/mp4': 'MP4',
80
+ 'video/mp4': 'MP4',
78
81
  'audio/mpeg': 'MP3',
79
82
  'application/json': 'JSON',
80
83
  'application/xml': 'XML',
@@ -29,6 +29,7 @@ template(
29
29
 
30
30
  <script>
31
31
  import { markRaw } from 'vue'
32
+ import { deprecate } from '@ditojs/utils'
32
33
  import DitoComponent from '../DitoComponent.js'
33
34
  import RouteMixin from '../mixins/RouteMixin.js'
34
35
  import {
@@ -37,25 +38,31 @@ import {
37
38
  } from '../utils/schema/structure.js'
38
39
  import { hasResource } from '../utils/resource.js'
39
40
  import { DataModel } from '../utils/DataModel.js'
41
+ import { LoadingTracker, LoadingSwitch } from '../utils/LoadingTracker.js'
40
42
 
41
43
  // @vue/component
42
44
  export default DitoComponent.component('DitoView', {
43
45
  mixins: [RouteMixin],
44
46
 
45
47
  provide() {
46
- // Redirect $sourceComponent and $resourceComponent to the main component:
47
48
  return {
49
+ // Redirect $sourceComponent and $resourceComponent to the main component:
48
50
  $sourceComponent: () => this.mainComponent?.sourceComponent || null,
49
- $resourceComponent: () => this.mainComponent?.resourceComponent || null
51
+ $resourceComponent: () => this.mainComponent?.resourceComponent || null,
52
+ $loadingTracker: () => this.loadingTracker
50
53
  }
51
54
  },
52
55
 
53
56
  data() {
57
+ // Tracks the requests of all resource components in the view, which is
58
+ // disabled while any of them is pending, see `isLoading`:
59
+ const loadingTracker = new LoadingTracker(this.$loadingTracker())
54
60
  return {
55
- // Updated from LoadingMixin through `setLoading(isLoading)`:
56
- isLoading: false,
57
- // NOTE: Data is shared across all views because the router recycles the
58
- // DitoView component.
61
+ loadingTracker: markRaw(loadingTracker),
62
+ // The loading operation of the deprecated `setLoading()`.
63
+ setLoadingSwitch: markRaw(new LoadingSwitch(loadingTracker)),
64
+ // NOTE: Each view has its own data, as `DitoRouterView` renders each
65
+ // route record with its own component instance.
59
66
  data: {}
60
67
  }
61
68
  },
@@ -73,6 +80,10 @@ export default DitoComponent.component('DitoView', {
73
80
  return true
74
81
  },
75
82
 
83
+ isLoading() {
84
+ return this.loadingTracker.isLoading
85
+ },
86
+
76
87
  isSingleComponentView() {
77
88
  return isSingleComponentView(this.schema)
78
89
  },
@@ -104,20 +115,6 @@ export default DitoComponent.component('DitoView', {
104
115
  }
105
116
  },
106
117
 
107
- watch: {
108
- $route: {
109
- // https://github.com/vuejs/vue-router/issues/3393#issuecomment-1158470149
110
- flush: 'post',
111
- handler(to, from) {
112
- // See if the route changes completely, and clear the data if it does.
113
- if (this.isFullRouteChange(to, from)) {
114
- this.isLoading = false
115
- this.data = {}
116
- }
117
- }
118
- }
119
- },
120
-
121
118
  created() {
122
119
  // Writes defaults and computed values into the view's data and resolves
123
120
  // options, see `DataModel`:
@@ -141,9 +138,25 @@ export default DitoComponent.component('DitoView', {
141
138
  // Stop the model before the component's own watchers are stopped, as the
142
139
  // model's watchers aren't part of the component, see `DataModel`.
143
140
  this.ownDataModel.stop()
141
+ // Don't leave the root loading after the view is gone, see the deprecated
142
+ // `setLoading()`.
143
+ this.setLoadingSwitch.set(false)
144
144
  },
145
145
 
146
146
  methods: {
147
+ /**
148
+ * @deprecated `isLoading` is tracked by `loadingTracker` now. Use
149
+ * `loadingTracker.begin()` and call the function that it returns, or
150
+ * `loadingTracker.track(callback)` instead.
151
+ */
152
+ setLoading(isLoading) {
153
+ deprecate(
154
+ 'DitoView.setLoading() is deprecated, use ' +
155
+ '`loadingTracker.begin()` or `loadingTracker.track()` instead.'
156
+ )
157
+ this.setLoadingSwitch.set(isLoading)
158
+ },
159
+
147
160
  setData(data) {
148
161
  this.data = data
149
162
  return this.data
@@ -154,10 +167,6 @@ export default DitoComponent.component('DitoView', {
154
167
  return this.isSingleComponentView
155
168
  ? this.path
156
169
  : `${this.path}/${path}`
157
- },
158
-
159
- setLoading(isLoading) {
160
- this.isLoading = !!isLoading
161
170
  }
162
171
  }
163
172
  })
package/src/index.js CHANGED
@@ -1 +1,4 @@
1
- export { default } from './DitoAdmin.js'
1
+ import DitoAdmin from './DitoAdmin.js'
2
+
3
+ export { DitoAdmin }
4
+ export default DitoAdmin