11<template >
22 <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)"  >
1322 <svg  xmlns =" http://www.w3.org/2000/svg"   viewBox =" 0 0 20 20"   fill =" currentColor"   class =" w-5 h-5"  >
1423 <path 
1524 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"  />
1625 </svg >
1726 </span >
18-  </div >
27+  </div >
28+  </template >
29+  </Sortable >
1930 <div 
2031 class =" relative w-[120px] h-[120px] rounded border border-dashed flex items-center justify-center hover:border-purple-500 overflow-hidden"  >
2132 <span >
2940
3041<script  setup>
3142//  Imports
43+ import  {Sortable } from  " sortablejs-vue3"  ;
3244import  {v4  as  uuidv4 } from  ' uuid'  ;
3345import  {onMounted , ref , watch } from  " vue"  ;
3446
@@ -39,10 +51,11 @@ import {onMounted, ref, watch} from "vue";
3951const  files  =  ref ([])
4052const  imageUrls  =  ref ([])
4153const  deletedImages  =  ref ([])
54+ const  imagePositions  =  ref ([])
4255
4356//  Props & Emit
4457const  props  =  defineProps ([' modelValue'  , ' deletedImages'  , ' images'  ])
45- const  emit  =  defineEmits ([' update:modelValue'  , ' update:deletedImages'  ])
58+ const  emit  =  defineEmits ([' update:modelValue'  , ' update:deletedImages' ,  ' update:imagePositions '  ])
4659
4760//  Computed
4861
@@ -51,16 +64,23 @@ function onFileChange($event) {
5164 const  chosenFiles  =  [... $event .target .files ]; 
5265 files .value  =  [... files .value , ... chosenFiles]; 
5366 $event .target .value  =  ' '  
67+  const  allPromises  =  []; 
5468 for  (let  file of  chosenFiles) { 
5569 file .id  =  uuidv4 () 
56-  readFile (file) 
70+  const  promise  =  readFile (file) 
71+  allPromises .push (promise) 
72+  promise 
5773 .then (url  =>  { 
5874 imageUrls .value .push ({ 
5975 url, 
6076 id:  file .id  
6177 }) 
6278 }) 
6379 } 
80+  Promise .all (allPromises) 
81+  .then (() =>  { 
82+  updateImagePositions () 
83+  }) 
6484 emit (' update:modelValue'  , files .value ) 
6585} 
6686
@@ -87,9 +107,11 @@ function removeImage(image) {
87107
88108 emit (' update:modelValue'  , files .value ) 
89109 } 
110+ 
111+  updateImagePositions (); 
90112} 
91113
92- function  revertImage (image ){
114+ function  revertImage (image )  {
93115 if  (image .isProp ) { 
94116 deletedImages .value  =  deletedImages .value .filter (id  =>  id !==  image .id ) 
95117 image .deleted  =  false ; 
@@ -98,6 +120,33 @@ function revertImage(image){
98120 } 
99121} 
100122
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+ 
101150//  Hooks
102151watch (' props.images'  , () =>  {
103152 console .log (props .images ) 
@@ -108,6 +157,8 @@ watch('props.images', () => {
108157 isProp:  true  
109158 })) 
110159 ] 
160+ 
161+  updateImagePositions () 
111162}, {immediate:  true , deep:  true }) 
112163onMounted (() =>  {
113164 emit (' update:modelValue'  , []) 
0 commit comments