@ditojs/admin 2.75.1 → 2.77.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/admin",
3
- "version": "2.75.1",
3
+ "version": "2.77.0",
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/master/packages/admin",
@@ -42,8 +42,8 @@
42
42
  "not ie_mob > 0"
43
43
  ],
44
44
  "dependencies": {
45
- "@ditojs/ui": "^2.75.0",
46
- "@ditojs/utils": "^2.75.0",
45
+ "@ditojs/ui": "^2.77.0",
46
+ "@ditojs/utils": "^2.77.0",
47
47
  "@kyvg/vue3-notification": "^3.4.2",
48
48
  "@lk77/vue3-color": "^3.0.6",
49
49
  "@tiptap/core": "^3.15.3",
@@ -89,9 +89,9 @@
89
89
  "vue-upload-component": "^3.1.17"
90
90
  },
91
91
  "devDependencies": {
92
- "@ditojs/build": "^2.75.0",
92
+ "@ditojs/build": "^2.77.0",
93
93
  "typescript": "^5.9.3",
94
94
  "vite": "^7.3.1"
95
95
  },
96
- "gitHead": "d16421580ee018bd1e606570bbf7b41c0f537fc8"
96
+ "gitHead": "d2cff77924d989aad5ab601cf08433a4e683ac6d"
97
97
  }
@@ -260,10 +260,9 @@ export default class DitoContext {
260
260
  return get(this, 'open', undefined)
261
261
  }
262
262
 
263
- // TODO: Rename this to `searchTerm` or `searchQuery`, to perhaps free `query`
264
- // for the actual `resourceComponent.query` object?
265
- get query() {
266
- return get(this, 'query', undefined)
263
+ // For search term in selects:
264
+ get searchTerm() {
265
+ return get(this, 'searchTerm', undefined)
267
266
  }
268
267
 
269
268
  // The error field is only populated in the context of buttons that send
@@ -284,6 +283,10 @@ export default class DitoContext {
284
283
  set(this, 'isRunning', value)
285
284
  }
286
285
 
286
+ get query() {
287
+ return this.component.$route.query
288
+ }
289
+
287
290
  // Helper Methods
288
291
 
289
292
  get request() {
@@ -21,6 +21,10 @@
21
21
  @mousedown.stop
22
22
  )
23
23
  slot(name="append")
24
+ .dito-info(
25
+ v-if="inlineInfo"
26
+ :data-info="inlineInfo"
27
+ )
24
28
  </template>
25
29
 
26
30
  <script>
@@ -41,6 +45,7 @@ export default DitoComponent.component('DitoAffixes', {
41
45
  absolute: { type: Boolean, default: false },
42
46
  clearable: { type: Boolean, default: false },
43
47
  disabled: { type: Boolean, default: false },
48
+ inlineInfo: { type: String, default: null },
44
49
  parentContext: { type: Object, required: true }
45
50
  },
46
51
 
@@ -85,6 +90,7 @@ export default DitoComponent.component('DitoAffixes', {
85
90
  return (
86
91
  this.visibleItems.length > 0 ||
87
92
  this.clearable ||
93
+ this.inlineInfo ||
88
94
  hasSlotContent(this.$slots.prepend) ||
89
95
  hasSlotContent(this.$slots.append)
90
96
  )
@@ -210,9 +210,9 @@ export default DitoComponent.component('DitoContainer', {
210
210
  const { class: classes } = this.schema
211
211
  const prefix = 'dito-container'
212
212
  return {
213
- [`${prefix}--single`]: this.single,
214
213
  [`${prefix}--disabled`]: this.componentDisabled,
215
214
  [`${prefix}--has-errors`]: !!this.errors,
215
+ [`${prefix}--single`]: this.single,
216
216
  [`${prefix}--compact`]: this.compact,
217
217
  [`${prefix}--label-vertical`]: this.verticalLabels,
218
218
  [`${prefix}--omit-spacing`]: omitSpacing(this.schema),
@@ -105,9 +105,14 @@ export default DitoComponent.component('DitoCreateButton', {
105
105
  if (this.isInlined) {
106
106
  this.sourceComponent.createItem(form, type)
107
107
  } else {
108
+ const { creatable } = this.schema
109
+ const query = {
110
+ ...(type && { type }),
111
+ ...creatable?.query?.(this.context)
112
+ }
108
113
  this.$router.push({
109
114
  path: `${this.path}/create`,
110
- query: { type }
115
+ query
111
116
  })
112
117
  }
113
118
  } else {
@@ -122,6 +122,8 @@ export default DitoComponent.component('DitoLabel', {
122
122
  }
123
123
 
124
124
  label {
125
+ @include ellipsis;
126
+
125
127
  cursor: inherit;
126
128
  font-weight: bold;
127
129
  line-height: $input-height;
@@ -23,7 +23,7 @@ export default DitoComponent.component('DitoNotifications', {
23
23
  },
24
24
 
25
25
  methods: {
26
- notify({ type = 'info', title, text, error } = {}) {
26
+ notify({ type = 'info', title, text, error, duration } = {}) {
27
27
  title ||= (
28
28
  {
29
29
  warning: 'Warning',
@@ -59,8 +59,13 @@ export default DitoComponent.component('DitoNotifications', {
59
59
  // amount of milliseconds multiplied with the amount of characters
60
60
  // displayed in the notification, plus 40 (40 + title + message):
61
61
  const { durationFactor = 20 } = notifications
62
- const duration = (40 + text.length + title.length) * durationFactor
63
- this.$notify({ type, title, text, duration })
62
+ duration ??= (40 + text.length + title.length) * durationFactor
63
+ this.$notify({
64
+ type,
65
+ title,
66
+ text,
67
+ duration: duration === 0 ? -1 : duration // < 0 -> <= 0 = sticky
68
+ })
64
69
  }
65
70
  },
66
71
 
@@ -65,8 +65,8 @@ export default DitoComponent.component('DitoPane', {
65
65
  meta: { type: Object, required: true },
66
66
  store: { type: Object, required: true },
67
67
  tab: { type: String, default: null },
68
- padding: { type: String, default: null },
69
68
  single: { type: Boolean, default: false },
69
+ padding: { type: String, default: null },
70
70
  disabled: { type: Boolean, default: false },
71
71
  compact: { type: Boolean, default: false },
72
72
  generateLabels: { type: Boolean, default: false },
@@ -86,9 +86,10 @@ export default DitoComponent.component('DitoPane', {
86
86
  },
87
87
 
88
88
  classes() {
89
+ const prefix = 'dito-pane'
89
90
  return {
90
- 'dito-pane--single': this.isSingleComponent,
91
- [`dito-pane--padding-${this.padding}`]: !!this.padding
91
+ [`${prefix}--single`]: this.isSingleComponent,
92
+ [`${prefix}--padding-${this.padding}`]: !!this.padding
92
93
  }
93
94
  },
94
95
 
@@ -231,8 +231,8 @@ export default DitoComponent.component('DitoRoot', {
231
231
  })
232
232
  },
233
233
 
234
- notify({ type = 'info', title, text, error } = {}) {
235
- this.notifications.notify({ type, title, text, error })
234
+ notify({ type = 'info', title, text, error, duration } = {}) {
235
+ this.notifications.notify({ type, title, text, error, duration })
236
236
  },
237
237
 
238
238
  closeNotifications() {
@@ -72,7 +72,7 @@ slot(name="prepend")
72
72
  :meta="meta"
73
73
  :store="store"
74
74
  :padding="padding"
75
- :single="!inlined && !hasMainPane"
75
+ :single="single && !inlined && !hasMainPane"
76
76
  :disabled="disabled"
77
77
  :compact="compact"
78
78
  :generateLabels="generateLabels"
@@ -87,7 +87,7 @@ slot(name="prepend")
87
87
  :meta="meta"
88
88
  :store="store"
89
89
  :padding="padding"
90
- :single="!inlined && !hasTabs"
90
+ :single="single && !inlined && !hasTabs"
91
91
  :disabled="disabled"
92
92
  :compact="compact"
93
93
  :generateLabels="generateLabels"
@@ -156,6 +156,7 @@ export default DitoComponent.component('DitoSchema', {
156
156
  meta: { type: Object, default: () => ({}) },
157
157
  store: { type: Object, default: () => ({}) },
158
158
  label: { type: [String, Object], default: null },
159
+ single: { type: Boolean, default: false },
159
160
  padding: { type: String, default: null },
160
161
  active: { type: Boolean, default: true },
161
162
  inlined: { type: Boolean, default: false },
@@ -395,7 +396,10 @@ export default DitoComponent.component('DitoSchema', {
395
396
  const tab = this.shouldRenderSchema(this.tabs[newTab])
396
397
  ? newTab
397
398
  : this.defaultTab
398
- this.$router.replace({ hash: tab ? `#${tab}` : null })
399
+ this.$router.replace({
400
+ query: this.$route.query,
401
+ hash: tab ? `#${tab}` : null
402
+ })
399
403
  }
400
404
  if (this.hasErrors) {
401
405
  this.repositionErrors()
@@ -23,6 +23,7 @@ template(
23
23
  padding="root"
24
24
  :disabled="isLoading"
25
25
  scrollable
26
+ single
26
27
  )
27
28
  </template>
28
29
 
@@ -13,6 +13,7 @@ import {
13
13
  hasFormSchema,
14
14
  getFormSchemas,
15
15
  getViewSchema,
16
+ getViewPath,
16
17
  isCompact,
17
18
  isInlined,
18
19
  isObjectSource,
@@ -187,13 +188,16 @@ export default {
187
188
  // the route query parameters to override them.
188
189
  const {
189
190
  scope = this.defaultScope?.name,
190
- page = this.schema.page
191
+ page = this.schema.page,
192
+ type
191
193
  } = this.query
192
- // Preserve / merge currently stored values.
194
+ // Preserve / merge currently stored values, including any custom query
195
+ // parameters added by creatable.query
193
196
  query = {
194
197
  ...this.query,
195
198
  ...(scope != null && { scope }),
196
199
  ...(page != null && { page }),
200
+ ...(type != null && { type }),
197
201
  ...query
198
202
  }
199
203
  if (!equals(query, this.$route.query)) {
@@ -337,7 +341,17 @@ export default {
337
341
  maxDepth: getSchemaAccessor('maxDepth', {
338
342
  type: Number,
339
343
  default: 1
340
- })
344
+ }),
345
+
346
+ createPath() {
347
+ if (this.creatable) {
348
+ return (
349
+ getViewPath(this.schema, this.context) ||
350
+ this.path
351
+ )
352
+ }
353
+ return null
354
+ }
341
355
  },
342
356
 
343
357
  watch: {
@@ -101,6 +101,17 @@ export default {
101
101
  type: String
102
102
  }),
103
103
 
104
+ info: getSchemaAccessor('info', {
105
+ type: String,
106
+ default: null
107
+ }),
108
+
109
+ inlineInfo() {
110
+ // When a label is present, info is shown in the label component.
111
+ // Otherwise, we have to show it inline.
112
+ return !this.label ? this.info : null
113
+ },
114
+
104
115
  events() {
105
116
  const events = this.getEvents()
106
117
  // Register callbacks for all provides non-recognized events,
@@ -72,11 +72,6 @@ export default DitoTypeComponent.register(
72
72
  return this.text || labelize(this.verb)
73
73
  },
74
74
 
75
- info: getSchemaAccessor('info', {
76
- type: String,
77
- default: null
78
- }),
79
-
80
75
  prefixes() {
81
76
  return asArray(this.schema.prefix)
82
77
  },
@@ -28,6 +28,7 @@ DitoTrigger.dito-color(
28
28
  mode="input"
29
29
  :clearable="showClearButton"
30
30
  :disabled="disabled"
31
+ :inlineInfo="inlineInfo"
31
32
  :parentContext="context"
32
33
  @clear="clear"
33
34
  )
@@ -26,6 +26,7 @@
26
26
  absolute
27
27
  :clearable="showClearButton"
28
28
  :disabled="disabled"
29
+ :inlineInfo="inlineInfo"
29
30
  :parentContext="context"
30
31
  @clear="clear"
31
32
  )
@@ -107,6 +107,7 @@
107
107
  v-if="hasCellEditButtons"
108
108
  )
109
109
  DitoEditButtons(
110
+ nested
110
111
  :schema="getItemFormSchema(schema, item, context)"
111
112
  :dataPath="getDataPath(index)"
112
113
  :data="item"
@@ -132,10 +133,9 @@
132
133
  :data="listData"
133
134
  :meta="meta"
134
135
  :store="store"
135
- :nested="nested"
136
136
  :disabled="disabled || isLoading"
137
137
  :creatable="creatable"
138
- :createPath="path"
138
+ :createPath="createPath"
139
139
  )
140
140
  //- Render create buttons outside table when in a single component view:
141
141
  DitoEditButtons.dito-buttons--large.dito-buttons--main.dito-buttons--sticky(
@@ -148,7 +148,7 @@
148
148
  :store="store"
149
149
  :disabled="disabled || isLoading"
150
150
  :creatable="creatable"
151
- :createPath="path"
151
+ :createPath="createPath"
152
152
  )
153
153
  </template>
154
154
 
@@ -42,6 +42,7 @@
42
42
  absolute
43
43
  :clearable="showClearButton"
44
44
  :disabled="disabled"
45
+ :inlineInfo="inlineInfo"
45
46
  :parentContext="context"
46
47
  @clear="clear"
47
48
  )
@@ -221,14 +222,14 @@ export default DitoTypeComponent.register('multiselect', {
221
222
  }
222
223
  },
223
224
 
224
- async onSearchChange(query) {
225
+ async onSearchChange(searchTerm) {
225
226
  if (this.searchFilter) {
226
- if (query) {
227
+ if (searchTerm) {
227
228
  // Set `searchedOptions` to an empty array, before it will be
228
229
  // populated asynchronously with the actual results.
229
230
  this.searchedOptions = []
230
231
  this.searchedOptions = await this.resolveData(
231
- () => this.searchFilter(new DitoContext(this, { query }))
232
+ () => this.searchFilter(new DitoContext(this, { searchTerm }))
232
233
  )
233
234
  } else {
234
235
  // Clear `searchedOptions` when the query is cleared.
@@ -24,6 +24,7 @@ DitoInput.dito-number(
24
24
  mode="input"
25
25
  :clearable="showClearButton"
26
26
  :disabled="disabled"
27
+ :inlineInfo="inlineInfo"
27
28
  :parentContext="context"
28
29
  @clear="clear"
29
30
  )
@@ -51,7 +51,7 @@
51
51
  :store="store"
52
52
  :disabled="disabled || isLoading"
53
53
  :creatable="creatable"
54
- :createPath="path"
54
+ :createPath="createPath"
55
55
  )
56
56
  </template>
57
57
 
@@ -47,6 +47,7 @@
47
47
  absolute
48
48
  :clearable="showClearButton"
49
49
  :disabled="disabled"
50
+ :inlineInfo="inlineInfo"
50
51
  :parentContext="context"
51
52
  @clear="clear"
52
53
  )
@@ -21,6 +21,7 @@ DitoInput.dito-text(
21
21
  mode="input"
22
22
  :clearable="showClearButton"
23
23
  :disabled="disabled"
24
+ :inlineInfo="inlineInfo"
24
25
  :parentContext="context"
25
26
  @clear="clear"
26
27
  )
@@ -15,7 +15,7 @@
15
15
  @update:data="data => (value = data)"
16
16
  )
17
17
  .dito-tree-form-container(
18
- v-if="hasEditableForms"
18
+ v-if="editPath && hasEditableForms"
19
19
  )
20
20
  //- Include a router-view for the optional DitoFormInlined
21
21
  RouterView
@@ -52,7 +52,8 @@ export default DitoTypeComponent.register(
52
52
 
53
53
  editPath() {
54
54
  // Accessed from DitoTreeItem through `container.editPath`:
55
- return this.$route.path.slice(this.path?.length)
55
+ const path = this.$route.path.slice(this.path?.length)
56
+ return path.startsWith(`/${this.schema.path}`) ? path : ''
56
57
  },
57
58
 
58
59
  treeData() {
@@ -491,17 +491,21 @@ export function hasViewSchema(schema, context) {
491
491
  return !!getViewSchema(schema, context)
492
492
  }
493
493
 
494
- export function getViewEditPath(schema, id, context) {
494
+ export function getViewPath(schema, context) {
495
495
  const view = getViewSchema(schema, context)
496
496
  if (view) {
497
- const path = isSingleComponentView(view)
497
+ return isSingleComponentView(view)
498
498
  ? view.fullPath
499
499
  : `${view.fullPath}/${view.path}`
500
- return `${path}/${id}`
501
500
  }
502
501
  return null
503
502
  }
504
503
 
504
+ export function getViewEditPath(schema, id, context) {
505
+ const path = getViewPath(schema, context)
506
+ return path ? `${path}/${id}` : null
507
+ }
508
+
505
509
  export function getFormSchemas(schema, context, modifyForm) {
506
510
  const viewSchema = context && getViewFormSchema(schema, context)
507
511
  if (viewSchema) {