@ditojs/admin 2.3.2 → 2.4.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.3.2",
3
+ "version": "2.4.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",
@@ -33,8 +33,8 @@
33
33
  "not ie_mob > 0"
34
34
  ],
35
35
  "dependencies": {
36
- "@ditojs/ui": "^2.3.2",
37
- "@ditojs/utils": "^2.3.0",
36
+ "@ditojs/ui": "^2.4.0",
37
+ "@ditojs/utils": "^2.4.0",
38
38
  "@kyvg/vue3-notification": "^2.9.0",
39
39
  "@lk77/vue3-color": "^3.0.6",
40
40
  "@tiptap/core": "^2.0.3",
@@ -74,7 +74,7 @@
74
74
  "vue-upload-component": "^3.1.8"
75
75
  },
76
76
  "devDependencies": {
77
- "@ditojs/build": "^2.3.0",
77
+ "@ditojs/build": "^2.4.0",
78
78
  "@vitejs/plugin-vue": "^4.1.0",
79
79
  "@vue/compiler-sfc": "^3.2.47",
80
80
  "pug": "^3.0.2",
@@ -83,7 +83,7 @@
83
83
  "vite": "^4.3.1"
84
84
  },
85
85
  "types": "types",
86
- "gitHead": "e8034d836e783c606746b8a51b20d8969f1472fc",
86
+ "gitHead": "11402f86e31da1b8ebd0a6ea5c984d3abf12d7be",
87
87
  "scripts": {
88
88
  "build": "vite build",
89
89
  "watch": "yarn build --mode 'development' --watch",
@@ -248,7 +248,7 @@ export default DitoComponent.component('DitoContainer', {
248
248
  --justify: flex-end;
249
249
  }
250
250
 
251
- &:not(#{$self}--only-in-row) {
251
+ &:not(#{$self}--alone-in-row) {
252
252
  // Now only apply alignment if there are neighbouring components no the
253
253
  // same row that also align.
254
254
  // Look ahead:
@@ -260,6 +260,12 @@ export default DitoComponent.component('DitoContainer', {
260
260
  }
261
261
  }
262
262
 
263
+ &--drop-target {
264
+ background: $content-color-background;
265
+ border-radius: $border-radius;
266
+ z-index: $drag-overlay-z-index + 1;
267
+ }
268
+
263
269
  &--omit-padding {
264
270
  padding: 0;
265
271
 
@@ -77,6 +77,7 @@ export default DitoComponent.component('DitoHeader', {
77
77
  @import '../styles/_imports';
78
78
 
79
79
  .dito-header {
80
+ position: relative;
80
81
  background: $color-black;
81
82
  font-size: $header-font-size;
82
83
  line-height: $header-line-height;
@@ -53,23 +53,28 @@ export default DitoComponent.component('DitoMenu', {
53
53
  }
54
54
  },
55
55
 
56
- getItemHref(item) {
56
+ getItemPath(item) {
57
57
  return item?.path
58
58
  ? `/${item.path}`
59
59
  : item.items
60
- ? this.getItemHref(Object.values(item.items)[0])
60
+ ? this.getItemPath(Object.values(item.items)[0])
61
61
  : null
62
62
  },
63
63
 
64
+ getItemHref(item) {
65
+ const path = this.getItemPath(item)
66
+ return path ? this.$router.resolve(path).href : null
67
+ },
68
+
64
69
  isActiveItem(item) {
65
70
  return (
66
- this.$route.path.startsWith(this.getItemHref(item)) ||
71
+ this.$route.path.startsWith(this.getItemPath(item)) ||
67
72
  item.items && Object.values(item.items).some(this.isActiveItem)
68
73
  )
69
74
  },
70
75
 
71
76
  onClickItem(item) {
72
- const path = this.getItemHref(item)
77
+ const path = this.getItemPath(item)
73
78
  if (path) {
74
79
  this.$router.push({ path, force: true })
75
80
  }
@@ -5,6 +5,10 @@
5
5
  position="top right"
6
6
  classes="dito-notification"
7
7
  )
8
+ Transition(name="dito-drag")
9
+ .dito-drag-overlay(
10
+ v-if="isDraggingFiles"
11
+ )
8
12
  TransitionGroup(name="dito-dialog")
9
13
  DitoDialog(
10
14
  v-for="(dialog, key) in dialogs"
@@ -69,7 +73,8 @@ export default DitoComponent.component('DitoRoot', {
69
73
  removeRoutes: null,
70
74
  dialogs: {},
71
75
  allowLogin: false,
72
- loadingCount: 0
76
+ loadingCount: 0,
77
+ isDraggingFiles: false
73
78
  }
74
79
  },
75
80
 
@@ -91,6 +96,8 @@ export default DitoComponent.component('DitoRoot', {
91
96
  },
92
97
 
93
98
  async mounted() {
99
+ this.setupDragAndDrop()
100
+
94
101
  tippyDelegate(this.$el, {
95
102
  target: '.dito-info',
96
103
  onCreate(instance) {
@@ -103,6 +110,7 @@ export default DitoComponent.component('DitoRoot', {
103
110
  })
104
111
  }
105
112
  })
113
+
106
114
  // Clear the label marked as active on all mouse and keyboard events, except
107
115
  // the ones that DitoLabel itself intercepts.
108
116
  this.domOn(document, {
@@ -118,6 +126,7 @@ export default DitoComponent.component('DitoRoot', {
118
126
  }
119
127
  }
120
128
  })
129
+
121
130
  try {
122
131
  this.allowLogin = false
123
132
  if (await this.fetchUser()) {
@@ -132,6 +141,81 @@ export default DitoComponent.component('DitoRoot', {
132
141
  },
133
142
 
134
143
  methods: {
144
+ setupDragAndDrop() {
145
+ // This code only happens the visual effects around dragging and dropping
146
+ // files into a `DitoTypeUpload` component. The actual uploading is
147
+ // handled by the `DitoTypeUpload` component itself.
148
+
149
+ let dragCount = 0
150
+ let uploads = []
151
+
152
+ const toggleDropTargetClass = enabled => {
153
+ for (const upload of uploads) {
154
+ upload.closest('.dito-container').classList.toggle(
155
+ 'dito-container--drop-target',
156
+ enabled
157
+ )
158
+ }
159
+ if (!enabled) {
160
+ uploads = []
161
+ }
162
+ }
163
+
164
+ const setDraggingFiles = enabled => {
165
+ this.isDraggingFiles = enabled
166
+ if (enabled) {
167
+ toggleDropTargetClass(true)
168
+ } else {
169
+ setTimeout(() => toggleDropTargetClass(false), 250)
170
+ }
171
+ }
172
+
173
+ this.domOn(document, {
174
+ dragenter: event => {
175
+ if (!dragCount && event.dataTransfer) {
176
+ uploads = document.querySelectorAll('.dito-upload')
177
+ const hasUploads = uploads.length > 0
178
+ event.dataTransfer.effectAllowed = hasUploads ? 'copy' : 'none'
179
+ if (hasUploads) {
180
+ setDraggingFiles(true)
181
+ } else {
182
+ event.preventDefault()
183
+ event.stopPropagation()
184
+ return
185
+ }
186
+ }
187
+ dragCount++
188
+ },
189
+
190
+ dragleave: event => {
191
+ dragCount--
192
+ if (!dragCount && event.dataTransfer) {
193
+ setDraggingFiles(false)
194
+ }
195
+ },
196
+
197
+ dragover: event => {
198
+ if (event.dataTransfer) {
199
+ const canDrop = event.target.closest(
200
+ '.dito-container:has(.dito-upload)'
201
+ )
202
+ event.dataTransfer.dropEffect = canDrop ? 'copy' : 'none'
203
+ if (!canDrop) {
204
+ event.preventDefault()
205
+ event.stopPropagation()
206
+ }
207
+ }
208
+ },
209
+
210
+ drop: event => {
211
+ dragCount = 0
212
+ if (event.dataTransfer) {
213
+ setDraggingFiles(false)
214
+ }
215
+ }
216
+ })
217
+ },
218
+
135
219
  notify({ type = 'info', title, text } = {}) {
136
220
  title ||= (
137
221
  {
@@ -389,4 +473,27 @@ function addRoutes(router, routes) {
389
473
  background: $content-color-background;
390
474
  }
391
475
  }
476
+
477
+ .dito-drag-overlay {
478
+ position: fixed;
479
+ top: 0;
480
+ left: 0;
481
+ z-index: $drag-overlay-z-index;
482
+ width: 100%;
483
+ height: 100%;
484
+ background: rgba(0, 0, 0, 0.25);
485
+ pointer-events: none;
486
+ backdrop-filter: blur(8px);
487
+ }
488
+
489
+ .dito-drag-enter-active,
490
+ .dito-drag-leave-active {
491
+ transition: opacity 0.25s, backdrop-filter 0.25s;
492
+ }
493
+
494
+ .dito-drag-enter-from,
495
+ .dito-drag-leave-to {
496
+ opacity: 0;
497
+ backdrop-filter: blur(0);
498
+ }
392
499
  </style>
@@ -94,6 +94,7 @@ export default DitoComponent.component('DitoTableHead', {
94
94
 
95
95
  .dito-button {
96
96
  // Convention: Nested spans handle padding, see below
97
+ display: block; // Override default inline-flex positioning.
97
98
  padding: 0;
98
99
  width: 100%;
99
100
  text-align: inherit;
@@ -239,17 +239,15 @@ export default {
239
239
 
240
240
  // @overridable
241
241
  focusElement() {
242
- const [element] = asArray(this.$refs.element)
243
- if (element) {
244
- this.$nextTick(() => {
245
- element.focus()
246
- // If the element is disabled, `focus()` will likely not have the
247
- // desired effect. Use `scrollIntoView()` if available:
248
- if (this.disabled) {
249
- ;(element.$el || element).scrollIntoView?.()
250
- }
251
- })
252
- }
242
+ const [element = this.$el] = asArray(this.$refs.element)
243
+ this.$nextTick(() => {
244
+ element.focus?.()
245
+ // If the element is disabled, `focus()` will likely not have the
246
+ // desired effect. Use `scrollIntoView()` if available:
247
+ if (!element.focus || this.disabled) {
248
+ ;(element.$el || element).scrollIntoView?.()
249
+ }
250
+ })
253
251
  },
254
252
 
255
253
  focus() {
@@ -149,9 +149,9 @@
149
149
  // For now, nothing for these:
150
150
  // .dito-button-create:empty::before,
151
151
  // .dito-button-add:empty::before
152
- // Special .dito-button-add-upload without :empty:
153
- .dito-button-add-upload::before {
154
- @extend %icon-add;
152
+
153
+ .dito-button-upload:empty::before {
154
+ @extend %icon-upload;
155
155
  }
156
156
 
157
157
  .dito-button-delete:empty::before,
@@ -66,6 +66,9 @@ $menu-padding-ver: $header-padding-ver - $menu-spacing;
66
66
  $menu-padding-hor: $header-padding-hor - $menu-spacing;
67
67
  $menu-padding: $menu-padding-ver $menu-padding-hor;
68
68
 
69
+ // Drag & Drop
70
+ $drag-overlay-z-index: 2000;
71
+
69
72
  // Patterns
70
73
  $pattern-transparency-size: ($font-size - $border-width) * $input-height-factor;
71
74
  $pattern-transparency: repeating-conic-gradient(
@@ -1,7 +1,6 @@
1
1
  <template lang="pug">
2
2
  button.dito-button(
3
3
  :id="dataPath"
4
- ref="element"
5
4
  :type="type"
6
5
  :title="title"
7
6
  :class="buttonClass"
@@ -1,7 +1,6 @@
1
1
  <template lang="pug">
2
2
  input.dito-checkbox(
3
3
  :id="dataPath"
4
- ref="element"
5
4
  v-model="value"
6
5
  type="checkbox"
7
6
  v-bind="attributes"
@@ -3,7 +3,6 @@
3
3
  //- involve actually rendering it when the component is not visible.
4
4
  input.dito-text.dito-input(
5
5
  :id="dataPath"
6
- ref="element"
7
6
  :name="name"
8
7
  type="text"
9
8
  :value="value"
@@ -1,6 +1,6 @@
1
1
  <template lang="pug">
2
2
  .dito-markup(:id="dataPath")
3
- .dito-markup-toolbar(:editor="editor")
3
+ .dito-markup-toolbar
4
4
  .dito-buttons.dito-buttons-toolbar(
5
5
  v-if="groupedButtons.length > 0"
6
6
  )
@@ -1,7 +1,6 @@
1
1
  <template lang="pug">
2
2
  InputField.dito-number(
3
3
  :id="dataPath"
4
- ref="element"
5
4
  v-model="inputValue"
6
5
  type="number"
7
6
  v-bind="attributes"
@@ -1,7 +1,6 @@
1
1
  <template lang="pug">
2
2
  progress.dito-progress(
3
3
  :id="dataPath"
4
- ref="element"
5
4
  :value="progressValue"
6
5
  :max="progressMax"
7
6
  v-bind="attributes"
@@ -1,7 +1,6 @@
1
1
  <template lang="pug">
2
2
  SwitchButton.dito-switch(
3
3
  :id="dataPath"
4
- ref="element"
5
4
  v-model="value"
6
5
  :labels="labels"
7
6
  v-bind="attributes"
@@ -1,7 +1,6 @@
1
1
  <template lang="pug">
2
2
  InputField.dito-text(
3
3
  :id="dataPath"
4
- ref="element"
5
4
  v-model="inputValue"
6
5
  :type="inputType"
7
6
  v-bind="attributes"
@@ -1,7 +1,6 @@
1
1
  <template lang="pug">
2
2
  textarea.dito-textarea.dito-input(
3
3
  :id="dataPath"
4
- ref="element"
5
4
  v-model="value"
6
5
  v-bind="attributes"
7
6
  :rows="lines"
@@ -73,13 +73,8 @@
73
73
  v-if="isUploadActive"
74
74
  type="button"
75
75
  @click.prevent="upload.active = false"
76
- ) Cancel All
77
- button.dito-button(
78
- v-else-if="isUploadReady"
79
- type="button"
80
- @click.prevent="upload.active = true"
81
- ) Upload All
82
- VueUpload.dito-button.dito-button-add-upload(
76
+ ) Cancel
77
+ VueUpload.dito-button(
83
78
  ref="upload"
84
79
  v-model="uploads"
85
80
  :inputId="dataPath"
@@ -90,10 +85,13 @@
90
85
  :accept="accept"
91
86
  :multiple="multiple"
92
87
  :size="maxSize"
93
- title="Upload Files"
88
+ :title="multiple ? 'Upload Files' : 'Upload File'"
89
+ :drop="$el?.closest('.dito-container')"
90
+ :dropDirectory="true"
94
91
  @input-filter="inputFilter"
95
92
  @input-file="inputFile"
96
93
  )
94
+ .dito-button-upload
97
95
  </template>
98
96
 
99
97
  <script>
@@ -169,7 +167,7 @@ export default DitoTypeComponent.register('upload', {
169
167
 
170
168
  isUploadReady() {
171
169
  return (
172
- this.uploads.length &&
170
+ this.uploads.length > 0 &&
173
171
  !(this.upload.active || this.upload.uploaded)
174
172
  )
175
173
  },
@@ -180,7 +178,7 @@ export default DitoTypeComponent.register('upload', {
180
178
 
181
179
  uploadProgress() {
182
180
  return (
183
- this.uploads.reduce((total, file) => total + file.progress, 0) /
181
+ this.uploads.reduce((total, file) => +file.progress + total, 0) /
184
182
  this.uploads.length
185
183
  )
186
184
  },
@@ -193,6 +191,17 @@ export default DitoTypeComponent.register('upload', {
193
191
  }
194
192
  },
195
193
 
194
+ watch: {
195
+ isUploadReady(ready) {
196
+ if (ready) {
197
+ // Auto-upload.
198
+ this.$nextTick(() => {
199
+ this.upload.active = true
200
+ })
201
+ }
202
+ }
203
+ },
204
+
196
205
  methods: {
197
206
  formatFileSize,
198
207
 
@@ -293,6 +302,7 @@ export default DitoTypeComponent.register('upload', {
293
302
  this.removeFile(newFile)
294
303
  }
295
304
  } else if (error) {
305
+ this.removeFile(newFile)
296
306
  const text = (
297
307
  {
298
308
  abort: 'Upload aborted',
@@ -310,7 +320,6 @@ export default DitoTypeComponent.register('upload', {
310
320
  title: 'File Upload Error',
311
321
  text
312
322
  })
313
- this.removeFile(newFile)
314
323
  }
315
324
  }
316
325
  },
@@ -347,7 +356,7 @@ function asFiles(value) {
347
356
  }
348
357
  }
349
358
 
350
- .dito-button-add-upload {
359
+ .dito-button-upload {
351
360
  padding: 0;
352
361
 
353
362
  > * {