sprintify-ui 0.0.204 → 0.1.2
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/sprintify-ui.es.js +19690 -14694
- package/dist/types/src/components/BaseAddressForm.vue.d.ts +24 -3
- package/dist/types/src/components/BaseMediaGallery.vue.d.ts +64 -0
- package/dist/types/src/components/BaseMediaGalleryItem.vue.d.ts +45 -0
- package/dist/types/src/components/BaseMediaLibrary.vue.d.ts +37 -3
- package/dist/types/src/components/BaseMediaList.vue.d.ts +47 -0
- package/dist/types/src/components/BaseMediaListItem.vue.d.ts +47 -0
- package/dist/types/src/components/BaseMediaPictures.vue.d.ts +55 -0
- package/dist/types/src/components/BaseMediaPicturesItem.vue.d.ts +54 -0
- package/dist/types/src/index.d.ts +8 -0
- package/dist/types/src/types/Media.d.ts +1 -0
- package/dist/types/src/types/UploadedFile.d.ts +1 -0
- package/dist/types/src/types/index.d.ts +2 -4
- package/package.json +3 -2
- package/src/components/BaseAddressForm.vue +27 -6
- package/src/components/BaseBelongsTo.vue +4 -4
- package/src/components/BaseMediaGallery.vue +95 -0
- package/src/components/BaseMediaGalleryItem.vue +92 -0
- package/src/components/BaseMediaItem.vue +1 -1
- package/src/components/BaseMediaLibrary.stories.js +181 -19
- package/src/components/BaseMediaLibrary.vue +92 -102
- package/src/components/BaseMediaList.vue +70 -0
- package/src/components/BaseMediaListItem.vue +171 -0
- package/src/components/BaseMediaPictures.vue +66 -0
- package/src/components/BaseMediaPicturesItem.vue +93 -0
- package/src/components/BaseMediaPreview.vue +16 -4
- package/src/lang/en.json +2 -0
- package/src/lang/fr.json +2 -0
- package/src/types/Media.ts +1 -0
- package/src/types/UploadedFile.ts +1 -0
- package/src/types/index.ts +2 -4
|
@@ -52,56 +52,64 @@
|
|
|
52
52
|
</template>
|
|
53
53
|
</BaseFileUploader>
|
|
54
54
|
|
|
55
|
-
<div
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<
|
|
75
|
-
v-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
55
|
+
<div v-if="normalizedModelValue.length" class="mt-5">
|
|
56
|
+
<slot
|
|
57
|
+
:model-value="normalizedModelValue"
|
|
58
|
+
name="list"
|
|
59
|
+
:disabled="disabled"
|
|
60
|
+
:draggable="draggable"
|
|
61
|
+
:remove="promptRemove"
|
|
62
|
+
@update:model-value="sync"
|
|
63
|
+
>
|
|
64
|
+
<BaseMediaPictures
|
|
65
|
+
v-if="layout == 'images'"
|
|
66
|
+
v-bind="listProps"
|
|
67
|
+
:model-value="normalizedModelValue"
|
|
68
|
+
:disabled="disabled"
|
|
69
|
+
:draggable="draggable"
|
|
70
|
+
@update:model-value="sync"
|
|
71
|
+
@remove="promptRemove($event)"
|
|
72
|
+
></BaseMediaPictures>
|
|
73
|
+
|
|
74
|
+
<BaseMediaList
|
|
75
|
+
v-else-if="layout == 'list'"
|
|
76
|
+
v-bind="listProps"
|
|
77
|
+
:model-value="normalizedModelValue"
|
|
78
|
+
:disabled="disabled"
|
|
79
|
+
:draggable="draggable"
|
|
80
|
+
@update:model-value="sync"
|
|
81
|
+
@remove="promptRemove($event)"
|
|
82
|
+
></BaseMediaList>
|
|
83
|
+
|
|
84
|
+
<BaseMediaGallery
|
|
85
|
+
v-else-if="layout == 'gallery'"
|
|
86
|
+
v-bind="listProps"
|
|
87
|
+
:model-value="normalizedModelValue"
|
|
88
|
+
:disabled="disabled"
|
|
89
|
+
:draggable="draggable"
|
|
90
|
+
@update:model-value="sync"
|
|
91
|
+
@remove="promptRemove($event)"
|
|
92
|
+
></BaseMediaGallery>
|
|
93
|
+
</slot>
|
|
88
94
|
</div>
|
|
89
95
|
</div>
|
|
90
96
|
</template>
|
|
91
97
|
|
|
92
98
|
<script lang="ts" setup>
|
|
93
99
|
import { PropType } from 'vue';
|
|
94
|
-
import { cloneDeep, isArray,
|
|
100
|
+
import { cloneDeep, isArray, capitalize } from 'lodash';
|
|
95
101
|
import { UploadedFile } from '@/types/UploadedFile';
|
|
96
102
|
import { Media } from '@/types/Media';
|
|
97
103
|
import { BaseCropperConfig, MediaLibraryPayload } from '@/types';
|
|
98
104
|
import { fileSizeFormat } from '@/utils';
|
|
99
105
|
import { useDialogsStore } from '@/stores/dialogs';
|
|
100
106
|
import { useNotificationsStore } from '@/stores/notifications';
|
|
101
|
-
import BaseMediaItem from '@/components/BaseMediaItem.vue';
|
|
102
107
|
import BaseFileUploader from './BaseFileUploader.vue';
|
|
103
108
|
import { useField } from '@/composables/field';
|
|
104
109
|
import { BaseIcon } from '.';
|
|
110
|
+
import BaseMediaList from './BaseMediaList.vue';
|
|
111
|
+
import BaseMediaPictures from './BaseMediaPictures.vue';
|
|
112
|
+
import BaseMediaGallery from './BaseMediaGallery.vue';
|
|
105
113
|
|
|
106
114
|
const i18n = useI18n();
|
|
107
115
|
|
|
@@ -111,7 +119,7 @@ const notifications = useNotificationsStore();
|
|
|
111
119
|
const props = defineProps({
|
|
112
120
|
modelValue: {
|
|
113
121
|
default: undefined,
|
|
114
|
-
type: Object as PropType<MediaLibraryPayload
|
|
122
|
+
type: Object as PropType<MediaLibraryPayload>,
|
|
115
123
|
},
|
|
116
124
|
name: {
|
|
117
125
|
default: undefined,
|
|
@@ -163,6 +171,18 @@ const props = defineProps({
|
|
|
163
171
|
default: undefined,
|
|
164
172
|
type: Object as PropType<BaseCropperConfig | boolean | null>,
|
|
165
173
|
},
|
|
174
|
+
draggable: {
|
|
175
|
+
default: false,
|
|
176
|
+
type: Boolean,
|
|
177
|
+
},
|
|
178
|
+
listProps: {
|
|
179
|
+
default: undefined,
|
|
180
|
+
type: Object as PropType<Record<string, unknown>>,
|
|
181
|
+
},
|
|
182
|
+
layout: {
|
|
183
|
+
default: 'gallery',
|
|
184
|
+
type: String as PropType<'list' | 'images' | 'gallery'>,
|
|
185
|
+
},
|
|
166
186
|
});
|
|
167
187
|
|
|
168
188
|
const emit = defineEmits([
|
|
@@ -173,12 +193,6 @@ const emit = defineEmits([
|
|
|
173
193
|
'upload:end',
|
|
174
194
|
]);
|
|
175
195
|
|
|
176
|
-
const filteredCurrentMedia = computed(() => {
|
|
177
|
-
return props.currentMedia.filter((media) => {
|
|
178
|
-
return !normalizedModelValue.value.to_remove.includes(media.id);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
196
|
const { emitUpdate, enableForm, disableForm } = useField({
|
|
183
197
|
name: computed(() => props.name),
|
|
184
198
|
required: computed(() => false),
|
|
@@ -187,6 +201,24 @@ const { emitUpdate, enableForm, disableForm } = useField({
|
|
|
187
201
|
errorType: 'alert',
|
|
188
202
|
});
|
|
189
203
|
|
|
204
|
+
// Model value normalization
|
|
205
|
+
|
|
206
|
+
const normalizedModelValue = computed(() => {
|
|
207
|
+
if (!isArray(props.modelValue)) {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return props.modelValue;
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
sync(normalizedModelValue.value);
|
|
215
|
+
|
|
216
|
+
const numberOfFiles = computed((): number => {
|
|
217
|
+
return normalizedModelValue.value.length;
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Validations
|
|
221
|
+
|
|
190
222
|
const normalizedMax = computed(() => {
|
|
191
223
|
if (props.max == null) {
|
|
192
224
|
return 100;
|
|
@@ -195,31 +227,19 @@ const normalizedMax = computed(() => {
|
|
|
195
227
|
return props.max;
|
|
196
228
|
});
|
|
197
229
|
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
isArray(props.modelValue.to_add) &&
|
|
203
|
-
isArray(props.modelValue.to_remove)
|
|
204
|
-
) {
|
|
205
|
-
return props.modelValue;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return {
|
|
209
|
-
to_add: [],
|
|
210
|
-
to_remove: [],
|
|
211
|
-
};
|
|
230
|
+
const maxFileText = computed(() => {
|
|
231
|
+
return i18n.t('sui.you_can_upload_up_to_n_files', {
|
|
232
|
+
count: normalizedMax.value,
|
|
233
|
+
});
|
|
212
234
|
});
|
|
213
235
|
|
|
214
|
-
const
|
|
215
|
-
return (
|
|
216
|
-
|
|
217
|
-
(props.modelValue?.to_add.length ?? 0) -
|
|
218
|
-
(props.modelValue?.to_remove.length ?? 0)
|
|
236
|
+
const maxFileSize = computed(() => {
|
|
237
|
+
return capitalize(
|
|
238
|
+
i18n.t('sui.up_to_x', { x: fileSizeFormat(props.maxSize) })
|
|
219
239
|
);
|
|
220
240
|
});
|
|
221
241
|
|
|
222
|
-
|
|
242
|
+
// Upload
|
|
223
243
|
|
|
224
244
|
function onUploadSuccess(file: UploadedFile) {
|
|
225
245
|
if (file == null) {
|
|
@@ -241,78 +261,48 @@ function onUploadSuccess(file: UploadedFile) {
|
|
|
241
261
|
return;
|
|
242
262
|
}
|
|
243
263
|
|
|
244
|
-
|
|
264
|
+
let modelValue = cloneDeep(normalizedModelValue.value);
|
|
245
265
|
|
|
246
266
|
if (normalizedMax.value == 1) {
|
|
247
267
|
// Remove everything...
|
|
248
|
-
modelValue
|
|
249
|
-
modelValue.to_add = [];
|
|
268
|
+
modelValue = [];
|
|
250
269
|
}
|
|
251
270
|
|
|
252
|
-
modelValue.
|
|
271
|
+
modelValue.push(file);
|
|
253
272
|
|
|
254
273
|
sync(modelValue);
|
|
255
274
|
|
|
256
275
|
emit('upload:success', file);
|
|
257
276
|
}
|
|
258
277
|
|
|
259
|
-
|
|
260
|
-
dialogs.push({
|
|
261
|
-
title: i18n.t('sui.remove_file'),
|
|
262
|
-
message: i18n.t('sui.remove_file_description'),
|
|
263
|
-
color: 'warning',
|
|
264
|
-
onConfirm() {
|
|
265
|
-
removeUploadedFile(index, length);
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
}
|
|
278
|
+
// Remove
|
|
269
279
|
|
|
270
|
-
function
|
|
280
|
+
function promptRemove(index: number, length = 1) {
|
|
271
281
|
dialogs.push({
|
|
272
282
|
title: i18n.t('sui.remove_file'),
|
|
273
283
|
message: i18n.t('sui.remove_file_description'),
|
|
274
284
|
color: 'warning',
|
|
275
285
|
onConfirm() {
|
|
276
|
-
|
|
286
|
+
removeByIndex(index, length);
|
|
277
287
|
},
|
|
278
288
|
});
|
|
279
289
|
}
|
|
280
290
|
|
|
281
|
-
function
|
|
291
|
+
function removeByIndex(index: number, length = 1) {
|
|
282
292
|
const modelValue = cloneDeep(normalizedModelValue.value);
|
|
283
293
|
|
|
284
|
-
modelValue
|
|
294
|
+
modelValue.splice(index, length);
|
|
285
295
|
|
|
286
296
|
sync(modelValue);
|
|
287
297
|
}
|
|
288
298
|
|
|
289
|
-
|
|
290
|
-
const media = props.currentMedia[index];
|
|
291
|
-
|
|
292
|
-
if (media) {
|
|
293
|
-
const modelValue = cloneDeep(normalizedModelValue.value);
|
|
294
|
-
|
|
295
|
-
modelValue.to_remove.push(media.id);
|
|
296
|
-
|
|
297
|
-
sync(modelValue);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
299
|
+
// Sync
|
|
300
300
|
|
|
301
301
|
function sync(modelValue: MediaLibraryPayload) {
|
|
302
302
|
emitUpdate(modelValue);
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
|
|
306
|
-
return i18n.t('sui.you_can_upload_up_to_n_files', {
|
|
307
|
-
count: normalizedMax.value,
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
const maxFileSize = computed(() => {
|
|
312
|
-
return capitalize(
|
|
313
|
-
i18n.t('sui.up_to_x', { x: fileSizeFormat(props.maxSize) })
|
|
314
|
-
);
|
|
315
|
-
});
|
|
305
|
+
// Events
|
|
316
306
|
|
|
317
307
|
function onUploadStart(event: any) {
|
|
318
308
|
emit('upload:start', event);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<vuedraggable
|
|
3
|
+
:model-value="modelValue"
|
|
4
|
+
group="media"
|
|
5
|
+
item-key="id"
|
|
6
|
+
tag="div"
|
|
7
|
+
class="border-t border-slate-200"
|
|
8
|
+
handle=".handle"
|
|
9
|
+
:disabled="disabled"
|
|
10
|
+
@update:model-value="onDragUpdate"
|
|
11
|
+
>
|
|
12
|
+
<template #item="{ element, index }">
|
|
13
|
+
<div class="border-b border-slate-200">
|
|
14
|
+
<BaseMediaListItem
|
|
15
|
+
:media="element"
|
|
16
|
+
:show-remove="showRemove"
|
|
17
|
+
:draggable="draggable"
|
|
18
|
+
:disabled="disabled"
|
|
19
|
+
@update="onItemUpdate(index, $event)"
|
|
20
|
+
@remove="$emit('remove', index)"
|
|
21
|
+
@save:name="$emit('save:name', index, $event)"
|
|
22
|
+
></BaseMediaListItem>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
</vuedraggable>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script lang="ts" setup>
|
|
29
|
+
import { Media } from '@/types/Media';
|
|
30
|
+
import { UploadedFile } from '@/types/UploadedFile';
|
|
31
|
+
import { cloneDeep } from 'lodash';
|
|
32
|
+
import { PropType } from 'vue';
|
|
33
|
+
import vuedraggable from 'vuedraggable';
|
|
34
|
+
import BaseMediaListItem from './BaseMediaListItem.vue';
|
|
35
|
+
|
|
36
|
+
const props = defineProps({
|
|
37
|
+
modelValue: {
|
|
38
|
+
required: true,
|
|
39
|
+
type: Object as PropType<(Media | UploadedFile)[]>,
|
|
40
|
+
},
|
|
41
|
+
showRemove: {
|
|
42
|
+
default: true,
|
|
43
|
+
type: Boolean,
|
|
44
|
+
},
|
|
45
|
+
disabled: {
|
|
46
|
+
default: false,
|
|
47
|
+
type: Boolean,
|
|
48
|
+
},
|
|
49
|
+
draggable: {
|
|
50
|
+
default: false,
|
|
51
|
+
type: Boolean,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const emit = defineEmits(['update:modelValue', 'remove', 'save:name']);
|
|
56
|
+
|
|
57
|
+
function onItemUpdate(index: number, media: Media | UploadedFile) {
|
|
58
|
+
const modelValue = cloneDeep(props.modelValue);
|
|
59
|
+
modelValue[index] = media;
|
|
60
|
+
emit('update:modelValue', modelValue);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function onDragUpdate(value: (Media | UploadedFile)[]) {
|
|
64
|
+
if (props.disabled) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
emit('update:modelValue', value);
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<li
|
|
3
|
+
class="group flex h-10 items-center justify-between"
|
|
4
|
+
:class="[draggable ? 'pr-2' : 'px-2']"
|
|
5
|
+
>
|
|
6
|
+
<div v-if="draggable && !disabled" class="handle shrink-0 cursor-move px-1">
|
|
7
|
+
<BaseIcon icon="mdi:drag" class="h-5 w-5 text-slate-400"></BaseIcon>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="mr-2 shrink-0">
|
|
10
|
+
<BaseIcon
|
|
11
|
+
icon="heroicons-solid:paper-clip"
|
|
12
|
+
class="h-5 w-5 shrink-0 text-slate-400"
|
|
13
|
+
></BaseIcon>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="flex grow items-center gap-3 overflow-hidden">
|
|
16
|
+
<div
|
|
17
|
+
v-if="viewMode == 'show'"
|
|
18
|
+
class="flex grow items-center overflow-hidden"
|
|
19
|
+
>
|
|
20
|
+
<button
|
|
21
|
+
type="button"
|
|
22
|
+
:disabled="disabled"
|
|
23
|
+
class="flex h-10 items-center overflow-hidden"
|
|
24
|
+
@click="editName()"
|
|
25
|
+
>
|
|
26
|
+
<span class="mr-2 truncate text-sm">{{ fileName }}</span>
|
|
27
|
+
<div
|
|
28
|
+
v-if="!disabled"
|
|
29
|
+
class="text-center opacity-0 group-hover:opacity-100"
|
|
30
|
+
>
|
|
31
|
+
<BaseIcon
|
|
32
|
+
icon="heroicons-solid:pencil"
|
|
33
|
+
class="h-5 w-5 shrink-0 text-slate-400"
|
|
34
|
+
></BaseIcon>
|
|
35
|
+
</div>
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div v-else class="flex grow items-center py-1">
|
|
40
|
+
<input
|
|
41
|
+
ref="inputRef"
|
|
42
|
+
class="h-8 w-full max-w-md rounded border border-slate-300 px-2 py-0 focus:ring-0"
|
|
43
|
+
type="text"
|
|
44
|
+
:value="name"
|
|
45
|
+
autofocus
|
|
46
|
+
@input="onNameChange"
|
|
47
|
+
@keydown.enter.prevent="saveName"
|
|
48
|
+
@keydown.escape.prevent="viewMode = 'show'"
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
<div class="flex shrink-0 items-center">
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
class="h-10 shrink-0 pr-2 pl-3 text-sm text-blue-600"
|
|
55
|
+
@click="saveName"
|
|
56
|
+
>
|
|
57
|
+
{{ $t('sui.save') }}
|
|
58
|
+
</button>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div class="flex shrink-0 gap-2">
|
|
63
|
+
<button
|
|
64
|
+
v-if="showRemove"
|
|
65
|
+
type="button"
|
|
66
|
+
:disabled="disabled"
|
|
67
|
+
class="shrink-0 text-sm disabled:opacity-50"
|
|
68
|
+
@click="$emit('remove')"
|
|
69
|
+
>
|
|
70
|
+
<span class="hidden sm:inline">
|
|
71
|
+
{{ $t('sui.delete') }}
|
|
72
|
+
</span>
|
|
73
|
+
<BaseIcon
|
|
74
|
+
icon="heroicons-solid:x"
|
|
75
|
+
class="h-5 w-5 shrink-0 text-slate-400 sm:hidden"
|
|
76
|
+
/>
|
|
77
|
+
</button>
|
|
78
|
+
<a
|
|
79
|
+
v-if="url"
|
|
80
|
+
:href="url"
|
|
81
|
+
class="shrink-0 text-sm text-blue-600"
|
|
82
|
+
target="_blank"
|
|
83
|
+
>
|
|
84
|
+
<span class="hidden sm:inline">
|
|
85
|
+
{{ $t('sui.download') }}
|
|
86
|
+
</span>
|
|
87
|
+
<BaseIcon
|
|
88
|
+
icon="heroicons-solid:download"
|
|
89
|
+
class="h-5 w-5 shrink-0 text-slate-400 sm:hidden"
|
|
90
|
+
/>
|
|
91
|
+
</a>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</li>
|
|
95
|
+
</template>
|
|
96
|
+
|
|
97
|
+
<script lang="ts" setup>
|
|
98
|
+
import { Media } from '@/types/Media';
|
|
99
|
+
import { UploadedFile } from '@/types/UploadedFile';
|
|
100
|
+
import { PropType } from 'vue';
|
|
101
|
+
import { Icon as BaseIcon } from '@iconify/vue';
|
|
102
|
+
import { cloneDeep } from 'lodash';
|
|
103
|
+
|
|
104
|
+
const emit = defineEmits(['remove', 'update', 'save:name']);
|
|
105
|
+
|
|
106
|
+
const props = defineProps({
|
|
107
|
+
media: {
|
|
108
|
+
required: true,
|
|
109
|
+
type: Object as PropType<Media | UploadedFile>,
|
|
110
|
+
},
|
|
111
|
+
disabled: {
|
|
112
|
+
default: false,
|
|
113
|
+
type: Boolean,
|
|
114
|
+
},
|
|
115
|
+
showRemove: {
|
|
116
|
+
default: true,
|
|
117
|
+
type: Boolean,
|
|
118
|
+
},
|
|
119
|
+
draggable: {
|
|
120
|
+
default: false,
|
|
121
|
+
type: Boolean,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const viewMode = ref<'show' | 'edit'>('show');
|
|
126
|
+
|
|
127
|
+
const name = computed(() => {
|
|
128
|
+
return props.media.name;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const fileName = computed(() => {
|
|
132
|
+
return props.media.file_name;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const url = computed(() => {
|
|
136
|
+
if ('url' in props.media) {
|
|
137
|
+
return props.media.url;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return null;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const inputRef = ref<HTMLInputElement | null>();
|
|
144
|
+
|
|
145
|
+
function editName() {
|
|
146
|
+
if (props.disabled) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
viewMode.value = 'edit';
|
|
151
|
+
|
|
152
|
+
setTimeout(() => {
|
|
153
|
+
inputRef.value?.focus();
|
|
154
|
+
inputRef.value?.select();
|
|
155
|
+
}, 100);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function onNameChange(e: Event) {
|
|
159
|
+
const target = e.target as HTMLInputElement;
|
|
160
|
+
const media = cloneDeep(props.media);
|
|
161
|
+
media.name = (target.value ?? '') as string;
|
|
162
|
+
const ext = media.file_name.split('.').pop();
|
|
163
|
+
media.file_name = media.name + '.' + ext;
|
|
164
|
+
emit('update', media);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function saveName() {
|
|
168
|
+
viewMode.value = 'show';
|
|
169
|
+
emit('save:name');
|
|
170
|
+
}
|
|
171
|
+
</script>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<vuedraggable
|
|
3
|
+
:model-value="modelValue"
|
|
4
|
+
group="media"
|
|
5
|
+
item-key="id"
|
|
6
|
+
tag="div"
|
|
7
|
+
class="flex flex-wrap"
|
|
8
|
+
handle=".handle"
|
|
9
|
+
:disabled="disabled"
|
|
10
|
+
@update:model-value="onDragUpdate"
|
|
11
|
+
>
|
|
12
|
+
<template #item="{ element, index }">
|
|
13
|
+
<div>
|
|
14
|
+
<BaseMediaPicturesItem
|
|
15
|
+
:media="element"
|
|
16
|
+
:show-remove="showRemove"
|
|
17
|
+
:draggable="draggable"
|
|
18
|
+
:size="size"
|
|
19
|
+
:disabled="disabled"
|
|
20
|
+
@remove="$emit('remove', index)"
|
|
21
|
+
></BaseMediaPicturesItem>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
</vuedraggable>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script lang="ts" setup>
|
|
28
|
+
import { Media } from '@/types/Media';
|
|
29
|
+
import { UploadedFile } from '@/types/UploadedFile';
|
|
30
|
+
import { PropType } from 'vue';
|
|
31
|
+
import vuedraggable from 'vuedraggable';
|
|
32
|
+
import BaseMediaPicturesItem from './BaseMediaPicturesItem.vue';
|
|
33
|
+
|
|
34
|
+
const props = defineProps({
|
|
35
|
+
modelValue: {
|
|
36
|
+
required: true,
|
|
37
|
+
type: Object as PropType<(Media | UploadedFile)[]>,
|
|
38
|
+
},
|
|
39
|
+
showRemove: {
|
|
40
|
+
default: true,
|
|
41
|
+
type: Boolean,
|
|
42
|
+
},
|
|
43
|
+
disabled: {
|
|
44
|
+
default: false,
|
|
45
|
+
type: Boolean,
|
|
46
|
+
},
|
|
47
|
+
draggable: {
|
|
48
|
+
default: false,
|
|
49
|
+
type: Boolean,
|
|
50
|
+
},
|
|
51
|
+
size: {
|
|
52
|
+
default: 140,
|
|
53
|
+
type: Number,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const emit = defineEmits(['update:modelValue', 'remove']);
|
|
58
|
+
|
|
59
|
+
function onDragUpdate(value: (Media | UploadedFile)[]) {
|
|
60
|
+
if (props.disabled) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
emit('update:modelValue', value);
|
|
65
|
+
}
|
|
66
|
+
</script>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="relative mr-4 mb-4 flex flex-col items-center rounded-lg bg-slate-200 shadow-md ring-1 ring-black ring-opacity-10"
|
|
4
|
+
:class="[draggable && !disabled ? 'handle cursor-move' : 'cursor-default']"
|
|
5
|
+
:style="{
|
|
6
|
+
width: size + 'px',
|
|
7
|
+
height: size + 'px',
|
|
8
|
+
marginRight: spacing,
|
|
9
|
+
marginBottom: spacing,
|
|
10
|
+
}"
|
|
11
|
+
>
|
|
12
|
+
<img
|
|
13
|
+
:src="src"
|
|
14
|
+
:style="{
|
|
15
|
+
width: size + 'px',
|
|
16
|
+
height: size + 'px',
|
|
17
|
+
}"
|
|
18
|
+
class="overflow-hidden rounded-lg object-cover"
|
|
19
|
+
/>
|
|
20
|
+
<div class="absolute -top-2 -right-2 flex gap-1">
|
|
21
|
+
<a
|
|
22
|
+
v-if="url"
|
|
23
|
+
:href="url"
|
|
24
|
+
target="_blank"
|
|
25
|
+
class="btn btn-white rounded-full p-1 shadow-sm ring-1 ring-black ring-opacity-10"
|
|
26
|
+
>
|
|
27
|
+
<BaseIcon class="h-4 w-4" icon="mdi:download"></BaseIcon>
|
|
28
|
+
</a>
|
|
29
|
+
<button
|
|
30
|
+
v-if="showRemove"
|
|
31
|
+
:disabled="disabled"
|
|
32
|
+
class="btn btn-white rounded-full p-1 shadow-sm ring-1 ring-black ring-opacity-10 disabled:bg-white disabled:opacity-70"
|
|
33
|
+
@click="$emit('remove')"
|
|
34
|
+
>
|
|
35
|
+
<BaseIcon class="h-4 w-4" icon="mdi:close"></BaseIcon>
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script lang="ts" setup>
|
|
42
|
+
import { Media } from '@/types/Media';
|
|
43
|
+
import { UploadedFile } from '@/types/UploadedFile';
|
|
44
|
+
import { PropType } from 'vue';
|
|
45
|
+
import { Icon as BaseIcon } from '@iconify/vue';
|
|
46
|
+
|
|
47
|
+
defineEmits(['remove']);
|
|
48
|
+
|
|
49
|
+
const props = defineProps({
|
|
50
|
+
media: {
|
|
51
|
+
required: true,
|
|
52
|
+
type: Object as PropType<Media | UploadedFile>,
|
|
53
|
+
},
|
|
54
|
+
showRemove: {
|
|
55
|
+
default: true,
|
|
56
|
+
type: Boolean,
|
|
57
|
+
},
|
|
58
|
+
draggable: {
|
|
59
|
+
default: false,
|
|
60
|
+
type: Boolean,
|
|
61
|
+
},
|
|
62
|
+
size: {
|
|
63
|
+
default: 140,
|
|
64
|
+
type: Number,
|
|
65
|
+
},
|
|
66
|
+
disabled: {
|
|
67
|
+
default: false,
|
|
68
|
+
type: Boolean,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const src = computed(() => {
|
|
73
|
+
if ('url' in props.media) {
|
|
74
|
+
return props.media.url;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return props.media.data_url;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const url = computed(() => {
|
|
81
|
+
if ('url' in props.media) {
|
|
82
|
+
return props.media.url;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const spacing = computed(() => {
|
|
89
|
+
const MAX_SPACING = 18;
|
|
90
|
+
const value = Math.min(MAX_SPACING, 0.15 * props.size);
|
|
91
|
+
return value + 'px';
|
|
92
|
+
});
|
|
93
|
+
</script>
|