1
1
<template >
2
2
<div class =" flex flex-wrap gap-1" >
3
- <div v-for =" image of imageUrls"
4
- class =" relative w-[120px] h-[120px] rounded border border-dashed flex items-center justify-center hover:border-purple-500 overflow-hidden" >
5
- <img :src =" image.url" class =" max-w-full max-h-full" :class =" image.deleted ? 'opacity-50' : ''" >
6
- <small v-if =" image.deleted" class =" absolute left-0 bottom-0 right-0 py-1 px-2 bg-black w-100 text-white justify-between items-center flex" >
7
- To be deleted
8
- <svg xmlns =" http://www.w3.org/2000/svg" fill =" none" viewBox =" 0 0 24 24" stroke-width =" 1.5" stroke =" currentColor" class =" w-4 h-4 cursor-pointer" @click =" revertImage(image)" >
9
- <path stroke-linecap =" round" stroke-linejoin =" round" d =" M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3" />
10
- </svg >
11
- </small >
12
- <span class =" absolute top-1 right-1 cursor-pointer" @click =" removeImage(image)" >
3
+ <Sortable
4
+ :list =" imageUrls"
5
+ item-key =" id"
6
+ class =" flex gap-1 flex-wrap"
7
+ @end =" onImageDragEnd"
8
+ >
9
+ <template #item =" {element: image , index } " >
10
+ <div
11
+ class =" relative w-[120px] h-[120px] rounded border border-dashed flex items-center justify-center hover:border-purple-500 overflow-hidden" >
12
+ <img :src =" image.url" class =" max-w-full max-h-full" :class =" image.deleted ? 'opacity-50' : ''" >
13
+ <small v-if =" image.deleted"
14
+ class =" absolute left-0 bottom-0 right-0 py-1 px-2 bg-black w-100 text-white justify-between items-center flex" >
15
+ To be deleted
16
+ <svg xmlns =" http://www.w3.org/2000/svg" fill =" none" viewBox =" 0 0 24 24" stroke-width =" 1.5"
17
+ stroke =" currentColor" class =" w-4 h-4 cursor-pointer" @click =" revertImage(image)" >
18
+ <path stroke-linecap =" round" stroke-linejoin =" round" d =" M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3" />
19
+ </svg >
20
+ </small >
21
+ <span class =" absolute top-1 right-1 cursor-pointer" @click =" removeImage(image)" >
13
22
<svg xmlns =" http://www.w3.org/2000/svg" viewBox =" 0 0 20 20" fill =" currentColor" class =" w-5 h-5" >
14
23
<path
15
24
d =" M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
16
25
</svg >
17
26
</span >
18
- </div >
27
+ </div >
28
+ </template >
29
+ </Sortable >
19
30
<div
20
31
class =" relative w-[120px] h-[120px] rounded border border-dashed flex items-center justify-center hover:border-purple-500 overflow-hidden" >
21
32
<span >
29
40
30
41
<script setup>
31
42
// Imports
43
+ import {Sortable } from " sortablejs-vue3" ;
32
44
import {v4 as uuidv4 } from ' uuid' ;
33
45
import {onMounted , ref , watch } from " vue" ;
34
46
@@ -39,10 +51,11 @@ import {onMounted, ref, watch} from "vue";
39
51
const files = ref ([])
40
52
const imageUrls = ref ([])
41
53
const deletedImages = ref ([])
54
+ const imagePositions = ref ([])
42
55
43
56
// Props & Emit
44
57
const props = defineProps ([' modelValue' , ' deletedImages' , ' images' ])
45
- const emit = defineEmits ([' update:modelValue' , ' update:deletedImages' ])
58
+ const emit = defineEmits ([' update:modelValue' , ' update:deletedImages' , ' update:imagePositions ' ])
46
59
47
60
// Computed
48
61
@@ -51,16 +64,23 @@ function onFileChange($event) {
51
64
const chosenFiles = [... $event .target .files ];
52
65
files .value = [... files .value , ... chosenFiles];
53
66
$event .target .value = ' '
67
+ const allPromises = [];
54
68
for (let file of chosenFiles) {
55
69
file .id = uuidv4 ()
56
- readFile (file)
70
+ const promise = readFile (file)
71
+ allPromises .push (promise)
72
+ promise
57
73
.then (url => {
58
74
imageUrls .value .push ({
59
75
url,
60
76
id: file .id
61
77
})
62
78
})
63
79
}
80
+ Promise .all (allPromises)
81
+ .then (() => {
82
+ updateImagePositions ()
83
+ })
64
84
emit (' update:modelValue' , files .value )
65
85
}
66
86
@@ -87,9 +107,11 @@ function removeImage(image) {
87
107
88
108
emit (' update:modelValue' , files .value )
89
109
}
110
+
111
+ updateImagePositions ();
90
112
}
91
113
92
- function revertImage (image ){
114
+ function revertImage (image ) {
93
115
if (image .isProp ) {
94
116
deletedImages .value = deletedImages .value .filter (id => id !== image .id )
95
117
image .deleted = false ;
@@ -98,6 +120,33 @@ function revertImage(image){
98
120
}
99
121
}
100
122
123
+ function onImageDragEnd (ev ) {
124
+ console .log (ev)
125
+
126
+ const {newIndex , oldIndex } = ev;
127
+
128
+ const [tmp ] = imageUrls .value .splice (oldIndex, 1 )
129
+ imageUrls .value .splice (newIndex, 0 , tmp)
130
+
131
+ updateImagePositions ()
132
+ }
133
+
134
+ function updateImagePositions () {
135
+ /**
136
+ * [
137
+ * [1, 1],
138
+ * [4, 2],
139
+ * [5, 3],
140
+ * ]
141
+ */
142
+ imagePositions .value = Object .fromEntries (
143
+ imageUrls .value .filter (im => ! im .deleted )
144
+ .map ((im , ind ) => [im .id , ind + 1 ])
145
+ )
146
+
147
+ emit (' update:imagePositions' , imagePositions .value )
148
+ }
149
+
101
150
// Hooks
102
151
watch (' props.images' , () => {
103
152
console .log (props .images )
@@ -108,6 +157,8 @@ watch('props.images', () => {
108
157
isProp: true
109
158
}))
110
159
]
160
+
161
+ updateImagePositions ()
111
162
}, {immediate: true , deep: true })
112
163
onMounted (() => {
113
164
emit (' update:modelValue' , [])
0 commit comments