@ditojs/admin 2.3.2 → 2.4.1
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/dist/dito-admin.es.js +1778 -1491
- package/dist/dito-admin.umd.js +5 -4
- package/dist/style.css +1 -1
- package/package.json +5 -5
- package/src/components/DitoContainer.vue +2 -1
- package/src/components/DitoErrors.vue +52 -4
- package/src/components/DitoHeader.vue +2 -1
- package/src/components/DitoMenu.vue +9 -4
- package/src/components/DitoPane.vue +1 -1
- package/src/components/DitoRoot.vue +126 -1
- package/src/components/DitoSchema.vue +22 -3
- package/src/components/DitoTableHead.vue +1 -0
- package/src/components/DitoUploadFile.vue +197 -0
- package/src/components/index.js +1 -0
- package/src/mixins/DitoMixin.js +24 -5
- package/src/mixins/TypeMixin.js +22 -16
- package/src/mixins/ValidationMixin.js +9 -1
- package/src/mixins/ValidatorMixin.js +1 -5
- package/src/styles/_button.scss +3 -3
- package/src/styles/_info.scss +0 -16
- package/src/styles/_settings.scss +3 -0
- package/src/styles/_tippy.scss +39 -0
- package/src/styles/style.scss +2 -1
- package/src/types/DitoTypeList.vue +2 -2
- package/src/types/DitoTypeMarkup.vue +7 -4
- package/src/types/DitoTypeUpload.vue +209 -81
- package/src/utils/schema.js +1 -1
- package/src/utils/uid.js +7 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/admin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
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.
|
|
37
|
-
"@ditojs/utils": "^2.
|
|
36
|
+
"@ditojs/ui": "^2.4.1",
|
|
37
|
+
"@ditojs/utils": "^2.4.1",
|
|
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.
|
|
77
|
+
"@ditojs/build": "^2.4.1",
|
|
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": "
|
|
86
|
+
"gitHead": "aa24e36fb6be2ad0113d13a1bda6d8d8b04bd77e",
|
|
87
87
|
"scripts": {
|
|
88
88
|
"build": "vite build",
|
|
89
89
|
"watch": "yarn build --mode 'development' --watch",
|
|
@@ -216,6 +216,7 @@ export default DitoComponent.component('DitoContainer', {
|
|
|
216
216
|
.dito-container {
|
|
217
217
|
$self: &;
|
|
218
218
|
|
|
219
|
+
position: relative;
|
|
219
220
|
display: flex;
|
|
220
221
|
flex-flow: column;
|
|
221
222
|
align-items: flex-start;
|
|
@@ -248,7 +249,7 @@ export default DitoComponent.component('DitoContainer', {
|
|
|
248
249
|
--justify: flex-end;
|
|
249
250
|
}
|
|
250
251
|
|
|
251
|
-
&:not(#{$self}--
|
|
252
|
+
&:not(#{$self}--alone-in-row) {
|
|
252
253
|
// Now only apply alignment if there are neighbouring components no the
|
|
253
254
|
// same row that also align.
|
|
254
255
|
// Look ahead:
|
|
@@ -5,17 +5,66 @@
|
|
|
5
5
|
ul
|
|
6
6
|
li(
|
|
7
7
|
v-for="error of errors"
|
|
8
|
-
)
|
|
9
|
-
| {{ error }}
|
|
8
|
+
) {{ error }}
|
|
10
9
|
</template>
|
|
11
10
|
|
|
12
11
|
<script>
|
|
12
|
+
import tippy from 'tippy.js'
|
|
13
13
|
import DitoComponent from '../DitoComponent.js'
|
|
14
|
+
import { markRaw } from 'vue'
|
|
14
15
|
|
|
15
16
|
// @vue/component
|
|
16
17
|
export default DitoComponent.component('DitoErrors', {
|
|
17
18
|
props: {
|
|
18
19
|
errors: { type: Array, default: null }
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
data() {
|
|
23
|
+
return {
|
|
24
|
+
tip: null
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
watch: {
|
|
29
|
+
errors() {
|
|
30
|
+
this.$nextTick(this.updateErrors)
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
unmounted() {
|
|
35
|
+
this.tip?.destroy()
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
methods: {
|
|
39
|
+
updateErrors() {
|
|
40
|
+
let { tip } = this
|
|
41
|
+
tip?.hide()
|
|
42
|
+
if (this.errors) {
|
|
43
|
+
tip = this.tip ??= markRaw(tippy(this.$el.closest('.dito-container')))
|
|
44
|
+
tip.setProps({
|
|
45
|
+
content: this.errors.join('\n'),
|
|
46
|
+
theme: 'error',
|
|
47
|
+
trigger: 'manual',
|
|
48
|
+
appendTo: 'parent',
|
|
49
|
+
placement: 'bottom-start',
|
|
50
|
+
animation: 'shift-away-subtle',
|
|
51
|
+
popperOptions: {
|
|
52
|
+
modifiers: [
|
|
53
|
+
{
|
|
54
|
+
name: 'flip',
|
|
55
|
+
enabled: false
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
interactive: true,
|
|
60
|
+
hideOnClick: false,
|
|
61
|
+
offset: [3, 3], // 1/2 form-spacing
|
|
62
|
+
zIndex: 1
|
|
63
|
+
})
|
|
64
|
+
tip.popper.addEventListener('mousedown', () => tip.hide())
|
|
65
|
+
tip.show()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
19
68
|
}
|
|
20
69
|
})
|
|
21
70
|
</script>
|
|
@@ -25,10 +74,9 @@ export default DitoComponent.component('DitoErrors', {
|
|
|
25
74
|
|
|
26
75
|
.dito-errors {
|
|
27
76
|
position: absolute;
|
|
28
|
-
|
|
77
|
+
opacity: 0;
|
|
29
78
|
|
|
30
79
|
ul {
|
|
31
|
-
margin-top: 1px;
|
|
32
80
|
color: $color-error;
|
|
33
81
|
}
|
|
34
82
|
}
|
|
@@ -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;
|
|
@@ -136,7 +137,7 @@ export default DitoComponent.component('DitoHeader', {
|
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
.dito-spinner {
|
|
139
|
-
margin-top: $
|
|
140
|
+
margin-top: $header-padding-ver;
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
.dito-dirty {
|
|
@@ -53,23 +53,28 @@ export default DitoComponent.component('DitoMenu', {
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
getItemPath(item) {
|
|
57
57
|
return item?.path
|
|
58
58
|
? `/${item.path}`
|
|
59
59
|
: item.items
|
|
60
|
-
? this.
|
|
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.
|
|
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.
|
|
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,80 @@ 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
|
|
155
|
+
.closest('.dito-container')
|
|
156
|
+
.classList.toggle('dito-drop-target', enabled)
|
|
157
|
+
}
|
|
158
|
+
if (!enabled) {
|
|
159
|
+
uploads = []
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const setDraggingFiles = enabled => {
|
|
164
|
+
this.isDraggingFiles = enabled
|
|
165
|
+
if (enabled) {
|
|
166
|
+
toggleDropTargetClass(true)
|
|
167
|
+
} else {
|
|
168
|
+
setTimeout(() => toggleDropTargetClass(false), 150)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.domOn(document, {
|
|
173
|
+
dragenter: event => {
|
|
174
|
+
if (!dragCount && event.dataTransfer) {
|
|
175
|
+
uploads = document.querySelectorAll('.dito-upload')
|
|
176
|
+
const hasUploads = uploads.length > 0
|
|
177
|
+
event.dataTransfer.effectAllowed = hasUploads ? 'copy' : 'none'
|
|
178
|
+
if (hasUploads) {
|
|
179
|
+
setDraggingFiles(true)
|
|
180
|
+
} else {
|
|
181
|
+
event.preventDefault()
|
|
182
|
+
event.stopPropagation()
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
dragCount++
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
dragleave: event => {
|
|
190
|
+
dragCount--
|
|
191
|
+
if (!dragCount && event.dataTransfer) {
|
|
192
|
+
setDraggingFiles(false)
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
dragover: event => {
|
|
197
|
+
if (event.dataTransfer) {
|
|
198
|
+
const canDrop = event.target.closest(
|
|
199
|
+
'.dito-container:has(.dito-upload)'
|
|
200
|
+
)
|
|
201
|
+
event.dataTransfer.dropEffect = canDrop ? 'copy' : 'none'
|
|
202
|
+
if (!canDrop) {
|
|
203
|
+
event.preventDefault()
|
|
204
|
+
event.stopPropagation()
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
drop: event => {
|
|
210
|
+
dragCount = 0
|
|
211
|
+
if (event.dataTransfer) {
|
|
212
|
+
setDraggingFiles(false)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
},
|
|
217
|
+
|
|
135
218
|
notify({ type = 'info', title, text } = {}) {
|
|
136
219
|
title ||= (
|
|
137
220
|
{
|
|
@@ -389,4 +472,46 @@ function addRoutes(router, routes) {
|
|
|
389
472
|
background: $content-color-background;
|
|
390
473
|
}
|
|
391
474
|
}
|
|
475
|
+
|
|
476
|
+
.dito-drag-overlay {
|
|
477
|
+
position: fixed;
|
|
478
|
+
top: 0;
|
|
479
|
+
left: 0;
|
|
480
|
+
z-index: $drag-overlay-z-index;
|
|
481
|
+
width: 100%;
|
|
482
|
+
height: 100%;
|
|
483
|
+
background: rgba(0, 0, 0, 0.25);
|
|
484
|
+
pointer-events: none;
|
|
485
|
+
backdrop-filter: blur(8px);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.dito-drop-target {
|
|
489
|
+
--shadow-alpha: 0.25;
|
|
490
|
+
|
|
491
|
+
background: $content-color-background;
|
|
492
|
+
border-radius: $border-radius;
|
|
493
|
+
z-index: $drag-overlay-z-index + 1;
|
|
494
|
+
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, var(--shadow-alpha)));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.dito-drag-enter-active,
|
|
498
|
+
.dito-drag-leave-active {
|
|
499
|
+
$duration: 0.15s;
|
|
500
|
+
|
|
501
|
+
transition: opacity $duration, backdrop-filter $duration;
|
|
502
|
+
|
|
503
|
+
~ * .dito-drop-target {
|
|
504
|
+
transition: filter $duration;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.dito-drag-enter-from,
|
|
509
|
+
.dito-drag-leave-to {
|
|
510
|
+
opacity: 0;
|
|
511
|
+
backdrop-filter: blur(0);
|
|
512
|
+
|
|
513
|
+
~ * .dito-drop-target {
|
|
514
|
+
--shadow-alpha: 0;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
392
517
|
</style>
|
|
@@ -3,7 +3,10 @@ slot(name="before")
|
|
|
3
3
|
.dito-schema(
|
|
4
4
|
v-bind="$attrs"
|
|
5
5
|
)
|
|
6
|
-
.dito-schema-content(
|
|
6
|
+
.dito-schema-content(
|
|
7
|
+
ref="content"
|
|
8
|
+
:class="{ 'dito-scroll': scrollable }"
|
|
9
|
+
)
|
|
7
10
|
Teleport(
|
|
8
11
|
to=".dito-header__menu"
|
|
9
12
|
:disabled="!headerInMenu"
|
|
@@ -280,6 +283,10 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
280
283
|
return this.everyComponent(it => it.isValidated)
|
|
281
284
|
},
|
|
282
285
|
|
|
286
|
+
hasErrors() {
|
|
287
|
+
return this.someComponent(it => it.hasErrors)
|
|
288
|
+
},
|
|
289
|
+
|
|
283
290
|
hasData() {
|
|
284
291
|
return !!this.data
|
|
285
292
|
},
|
|
@@ -334,6 +341,9 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
334
341
|
handler(hash) {
|
|
335
342
|
if (this.hasTabs) {
|
|
336
343
|
this.currentTab = hash?.slice(1) || null
|
|
344
|
+
if (this.hasErrors) {
|
|
345
|
+
this.repositionErrors()
|
|
346
|
+
}
|
|
337
347
|
}
|
|
338
348
|
}
|
|
339
349
|
},
|
|
@@ -453,9 +463,18 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
453
463
|
}
|
|
454
464
|
},
|
|
455
465
|
|
|
466
|
+
repositionErrors() {
|
|
467
|
+
// Force repositioning of error tooltips, as otherwise they
|
|
468
|
+
// sometimes don't show up in the right place initially when
|
|
469
|
+
// changing tabs
|
|
470
|
+
const scrollParent = this.$refs.content.closest('.dito-scroll')
|
|
471
|
+
scrollParent.scrollTop++
|
|
472
|
+
scrollParent.scrollTop--
|
|
473
|
+
},
|
|
474
|
+
|
|
456
475
|
focus() {
|
|
457
|
-
this.parentSchemaComponent?.focus()
|
|
458
476
|
this.opened = true
|
|
477
|
+
return this.parentSchemaComponent?.focus()
|
|
459
478
|
},
|
|
460
479
|
|
|
461
480
|
validateAll(match, notify = true) {
|
|
@@ -485,7 +504,7 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
485
504
|
if (!component.validate(notify)) {
|
|
486
505
|
// Focus first error field
|
|
487
506
|
if (notify && first) {
|
|
488
|
-
component.
|
|
507
|
+
component.scrollIntoView()
|
|
489
508
|
}
|
|
490
509
|
first = false
|
|
491
510
|
isValid = false
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
.dito-upload-file
|
|
3
|
+
.dito-thumbnail(
|
|
4
|
+
v-if="thumbnail"
|
|
5
|
+
:class="`dito-thumbnail--${thumbnail}`"
|
|
6
|
+
)
|
|
7
|
+
.dito-thumbnail__inner
|
|
8
|
+
img(
|
|
9
|
+
v-if="source"
|
|
10
|
+
:src="source"
|
|
11
|
+
)
|
|
12
|
+
.dito-thumbnail__type(
|
|
13
|
+
v-else
|
|
14
|
+
)
|
|
15
|
+
span {{ type }}
|
|
16
|
+
span {{ file.name }}
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
import DitoComponent from '../DitoComponent.js'
|
|
21
|
+
|
|
22
|
+
// @vue/component
|
|
23
|
+
export default DitoComponent.component('DitoUploadFile', {
|
|
24
|
+
props: {
|
|
25
|
+
file: { type: Object, required: true },
|
|
26
|
+
thumbnail: { type: String, default: null },
|
|
27
|
+
thumbnailUrl: { type: String, default: null }
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
data() {
|
|
31
|
+
return {
|
|
32
|
+
uploadUrl: null
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
computed: {
|
|
37
|
+
type() {
|
|
38
|
+
return (
|
|
39
|
+
TYPES[this.file.type] ||
|
|
40
|
+
this.file.name.split('.').pop().toUpperCase()
|
|
41
|
+
)
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
source() {
|
|
45
|
+
return this.uploadUrl || this.thumbnailUrl
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
watch: {
|
|
50
|
+
'file.upload.file': {
|
|
51
|
+
immediate: true,
|
|
52
|
+
handler(file) {
|
|
53
|
+
if (file && this.thumbnail) {
|
|
54
|
+
const reader = new FileReader()
|
|
55
|
+
reader.onload = () => {
|
|
56
|
+
this.uploadUrl = reader.result
|
|
57
|
+
}
|
|
58
|
+
reader.readAsDataURL(file)
|
|
59
|
+
} else {
|
|
60
|
+
this.uploadUrl = null
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const TYPES = {
|
|
68
|
+
'text/plain': 'TXT',
|
|
69
|
+
'text/html': 'HTML',
|
|
70
|
+
'text/css': 'CSS',
|
|
71
|
+
'text/javascript': 'JS',
|
|
72
|
+
'image/jpeg': 'JPG',
|
|
73
|
+
'image/png': 'PNG',
|
|
74
|
+
'image/gif': 'GIF',
|
|
75
|
+
'image/svg+xml': 'SVG',
|
|
76
|
+
'movie/mp4': 'MP4',
|
|
77
|
+
'audio/mpeg': 'MP3',
|
|
78
|
+
'application/json': 'JSON',
|
|
79
|
+
'application/xml': 'XML',
|
|
80
|
+
'application/pdf': 'PDF',
|
|
81
|
+
'application/zip': 'ZIP'
|
|
82
|
+
}
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<style lang="scss">
|
|
86
|
+
@use 'sass:math';
|
|
87
|
+
@import '../styles/_imports';
|
|
88
|
+
|
|
89
|
+
.dito-upload-file {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
justify-content: flex-start;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.dito-thumbnail {
|
|
96
|
+
$self: &;
|
|
97
|
+
|
|
98
|
+
// Small size by default
|
|
99
|
+
--max-size: #{1 * $input-height};
|
|
100
|
+
--corner-size: calc(var(--max-size) / 5);
|
|
101
|
+
--shadow-size: 1px;
|
|
102
|
+
--min-size: calc(2 * var(--corner-size));
|
|
103
|
+
--margin: 0em;
|
|
104
|
+
--drop-shadow: drop-shadow(
|
|
105
|
+
0 calc(var(--shadow-size) * 0.75) var(--shadow-size) #{rgba(
|
|
106
|
+
$color-black,
|
|
107
|
+
0.4
|
|
108
|
+
)}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
position: relative;
|
|
112
|
+
margin: var(--margin);
|
|
113
|
+
margin-right: 0.5em;
|
|
114
|
+
filter: var(--drop-shadow);
|
|
115
|
+
|
|
116
|
+
&--small {
|
|
117
|
+
--max-size: #{1 * $input-height};
|
|
118
|
+
--margin: 0em;
|
|
119
|
+
--shadow-size: 1px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
&--medium {
|
|
123
|
+
--max-size: #{2 * $input-height};
|
|
124
|
+
--margin: 0.25em;
|
|
125
|
+
--shadow-size: 1.5px;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
&--large {
|
|
129
|
+
--max-size: #{4 * $input-height};
|
|
130
|
+
--margin: 0.5em;
|
|
131
|
+
--shadow-size: 2.5px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&__inner {
|
|
135
|
+
background: #ffffff;
|
|
136
|
+
clip-path: polygon(
|
|
137
|
+
0 0,
|
|
138
|
+
calc(100% - var(--corner-size)) 0,
|
|
139
|
+
100% var(--corner-size),
|
|
140
|
+
100% 100%,
|
|
141
|
+
0 100%
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
&::after {
|
|
145
|
+
content: '';
|
|
146
|
+
position: absolute;
|
|
147
|
+
top: 0;
|
|
148
|
+
right: 0;
|
|
149
|
+
width: var(--corner-size);
|
|
150
|
+
height: var(--corner-size);
|
|
151
|
+
background: linear-gradient(45deg, #ffffff, #eeeeee 40%, #dddddd 50%);
|
|
152
|
+
filter: var(--drop-shadow);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
&__type {
|
|
157
|
+
--font-size: var(--corner-size);
|
|
158
|
+
|
|
159
|
+
display: flex;
|
|
160
|
+
align-items: center;
|
|
161
|
+
justify-content: center;
|
|
162
|
+
min-width: var(--min-size);
|
|
163
|
+
min-height: var(--max-size);
|
|
164
|
+
aspect-ratio: 3 / 4;
|
|
165
|
+
|
|
166
|
+
span {
|
|
167
|
+
--color: #{$color-grey};
|
|
168
|
+
|
|
169
|
+
font-size: min(var(--font-size), #{1.25 * $font-size});
|
|
170
|
+
color: var(--color);
|
|
171
|
+
|
|
172
|
+
#{$self}:not(#{$self}--small) & {
|
|
173
|
+
padding: 0 calc(var(--font-size) / 4);
|
|
174
|
+
border-radius: calc(var(--font-size) / 4);
|
|
175
|
+
background: var(--color);
|
|
176
|
+
color: #ffffff;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#{$self}--medium & {
|
|
180
|
+
--color: #{$color-light};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#{$self}--large & {
|
|
184
|
+
--color: #{$color-lighter};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
img {
|
|
190
|
+
display: block;
|
|
191
|
+
min-width: var(--min-size);
|
|
192
|
+
min-height: var(--min-size);
|
|
193
|
+
max-width: var(--max-size);
|
|
194
|
+
max-height: var(--max-size);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
</style>
|
package/src/components/index.js
CHANGED
|
@@ -31,5 +31,6 @@ export { default as DitoPagination } from './DitoPagination.vue'
|
|
|
31
31
|
export { default as DitoTreeItem } from './DitoTreeItem.vue'
|
|
32
32
|
export { default as DitoTableHead } from './DitoTableHead.vue'
|
|
33
33
|
export { default as DitoTableCell } from './DitoTableCell.vue'
|
|
34
|
+
export { default as DitoUploadFile } from './DitoUploadFile.vue'
|
|
34
35
|
export { default as DitoDraggable } from './DitoDraggable.vue'
|
|
35
36
|
export { default as DitoVNode } from './DitoVNode.vue'
|
package/src/mixins/DitoMixin.js
CHANGED
|
@@ -192,19 +192,36 @@ export default {
|
|
|
192
192
|
return value
|
|
193
193
|
},
|
|
194
194
|
|
|
195
|
-
getChildStore(key) {
|
|
196
|
-
|
|
195
|
+
getChildStore(key, index) {
|
|
196
|
+
// When storing, temporary ids change to permanent ones and thus the key
|
|
197
|
+
// can change, so we need to store the index as well, to be able to find
|
|
198
|
+
// the store again after the item was saved.
|
|
199
|
+
const store = (
|
|
200
|
+
this.getStore(key) ||
|
|
201
|
+
index != null && this.getStore(index) ||
|
|
202
|
+
this.setStore(key, reactive({}))
|
|
203
|
+
)
|
|
204
|
+
if (index != null) {
|
|
205
|
+
this.setStore(index, store)
|
|
206
|
+
}
|
|
207
|
+
return store
|
|
197
208
|
},
|
|
198
209
|
|
|
199
210
|
getSchemaValue(
|
|
200
211
|
keyOrDataPath,
|
|
201
|
-
{
|
|
212
|
+
{
|
|
213
|
+
type,
|
|
214
|
+
default: def,
|
|
215
|
+
schema = this.schema,
|
|
216
|
+
context = this.context,
|
|
217
|
+
callback = true
|
|
218
|
+
} = {}
|
|
202
219
|
) {
|
|
203
220
|
return getSchemaValue(keyOrDataPath, {
|
|
204
221
|
type,
|
|
205
222
|
schema,
|
|
223
|
+
context,
|
|
206
224
|
callback,
|
|
207
|
-
context: this.context,
|
|
208
225
|
default: isFunction(def) ? () => def.call(this) : def
|
|
209
226
|
})
|
|
210
227
|
},
|
|
@@ -365,7 +382,9 @@ export default {
|
|
|
365
382
|
}
|
|
366
383
|
// See: https://stackoverflow.com/a/49917066/1163708
|
|
367
384
|
const a = document.createElement('a')
|
|
368
|
-
a.href =
|
|
385
|
+
a.href = options.url?.startsWith('blob:')
|
|
386
|
+
? options.url
|
|
387
|
+
: this.api.getApiUrl(options)
|
|
369
388
|
a.download = options.filename ?? null
|
|
370
389
|
const { body } = document
|
|
371
390
|
body.appendChild(a)
|