sprintify-ui 0.0.204 → 0.1.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/dist/sprintify-ui.es.js +19691 -14695
- 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/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 +94 -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, uniqueId } 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,50 @@ 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
|
-
|
|
271
|
+
file.id = 'new' + uniqueId();
|
|
272
|
+
|
|
273
|
+
modelValue.push(file);
|
|
253
274
|
|
|
254
275
|
sync(modelValue);
|
|
255
276
|
|
|
256
277
|
emit('upload:success', file);
|
|
257
278
|
}
|
|
258
279
|
|
|
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
|
-
}
|
|
280
|
+
// Remove
|
|
269
281
|
|
|
270
|
-
function
|
|
282
|
+
function promptRemove(index: number, length = 1) {
|
|
271
283
|
dialogs.push({
|
|
272
284
|
title: i18n.t('sui.remove_file'),
|
|
273
285
|
message: i18n.t('sui.remove_file_description'),
|
|
274
286
|
color: 'warning',
|
|
275
287
|
onConfirm() {
|
|
276
|
-
|
|
288
|
+
removeByIndex(index, length);
|
|
277
289
|
},
|
|
278
290
|
});
|
|
279
291
|
}
|
|
280
292
|
|
|
281
|
-
function
|
|
293
|
+
function removeByIndex(index: number, length = 1) {
|
|
282
294
|
const modelValue = cloneDeep(normalizedModelValue.value);
|
|
283
295
|
|
|
284
|
-
modelValue
|
|
296
|
+
modelValue.splice(index, length);
|
|
285
297
|
|
|
286
298
|
sync(modelValue);
|
|
287
299
|
}
|
|
288
300
|
|
|
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
|
-
}
|
|
301
|
+
// Sync
|
|
300
302
|
|
|
301
303
|
function sync(modelValue: MediaLibraryPayload) {
|
|
302
304
|
emitUpdate(modelValue);
|
|
303
305
|
}
|
|
304
306
|
|
|
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
|
-
});
|
|
307
|
+
// Events
|
|
316
308
|
|
|
317
309
|
function onUploadStart(event: any) {
|
|
318
310
|
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>
|