Sharing my picture list component
-
Hi, I would like to share my picture list component that allows dragging of pictures to reorder them. Dependencies are promise-file-reader, downscale and mobile-drag-drop if you want to use it on mobile. Let me know if you find it useful!
<style> .separator{ border-radius:5px; border:1px black dashed; } .pictureList{ display:flex; flex-direction:row; flex-wrap:wrap; } .picture{ width:max-content; display:flex; flex-direction:row; flex-wrap:nowrap; } #newPicture{ width:0.1px; height:0.1px; opacity:0; overflow:hidden; position:absolute; z-index:-1; } </style> <template> <div> <p v-if="this.pictures.length>0">Drag the pictures to rearrange them (hold down on the picture to drag on mobile).</p> <div class="pictureList"> <div ref="divs" v-for="(picture,index) in pictures" class="picture"> <q-separator vertical class="separator" @dragover="dragOverLeft(index,$event)" @dragenter="dragEnter($event)" @dragleave="dragLeaveLeft(index)" @drop="dropLeft(index,$event)" :style="leftSeparatorVisible[index]?'width:30px':'width:20px;opacity:0'"/> <q-avatar draggable="true" rounded :style="dragging===index?'opacity:0.5':''" @dragstart="dragStart(index,$event)" @dragend="dragEnd"> <img draggable="false" :style="dragging===index?'border:2px grey dashed':''" :src="picture.src"/> <q-badge color="primary" floating transparent v-ripple @click="removePicture(index)"> <q-icon name="far fa-times-circle"/> </q-badge> </q-avatar> <q-separator v-if="lastItem[index]" vertical class="separator" @dragover="dragOverRight(index,$event)" @dragenter="dragEnter($event)" @dragleave="dragLeaveRight(index)" @drop="dropRight(index,$event)" :style="rightSeparatorVisible[index]?'width:30px':'width:20px;opacity:0'"/> </div> <input type="file" multiple id="newPicture" ref="newPicture" @change="newPictureChanged($event)" accept="image/*"/> <q-btn color="primary" glossy push @click="$refs.newPicture.click()">Add Picture</q-btn> </div> </div> </template> <script> import PromiseFileReader from 'promise-file-reader'; import downscale from 'downscale'; export default{ props:['pictures'], data(){ return{ dragging:null, leftSeparatorVisible:[], rightSeparatorVisible:[], lastItem:[] }; }, watch:{ pictures:{ deep:true, handler(){ this.leftSeparatorVisible.length=this.pictures.length; this.leftSeparatorVisible.fill(false); this.rightSeparatorVisible.length=this.pictures.length; this.rightSeparatorVisible.fill(false); this.computeLastItem(); } } }, methods:{ computeLastItem(){ this.lastItem.length=this.pictures.length; this.lastItem.fill(false); this.lastItem[this.lastItem.length-1]=true; if(!this.$refs.divs)return; for(let i=0;i<this.lastItem.length-1;++i){ if(i+1>this.$refs.divs.length)this.lastItem[i]=true; else this.lastItem[i]=this.$refs.divs[i+1].getBoundingClientRect().top>this.$refs.divs[i].getBoundingClientRect().top; } }, async newPictureChanged(event){ this.$q.loading.show(); if(typeof cordova!=='undefined'&&cordova.plugins&&cordova.plugins.permissions){ await new Promise((resolve,reject)=>{ function getPermissions(){ let permissions=cordova.plugins.permissions; permissions.requestPermission(permissions.READ_EXTERNAL_STORAGE,async status=>{ if(!status.hasPermission){ setTimeout(getPermissions.bind(this),0); return; } this.resolve(status); },()=>setTimeout(getPermissions.bind(this),0)); } setTimeout(getPermissions.bind({resolve,reject}),0); }); } let promises=[]; for(let i=0;i<event.target.files.length;++i){ promises.push(new Promise(resolve=>{ let newImg=new Image(); newImg.onload=()=>{ if(newImg.width<=500||newImg.height<=500){ this.pictures.push({src:newImg.src}); resolve(this.pictures[this.pictures.length-1]); return; } downscale(newImg.src,newImg.width>=newImg.height?500:0,newImg.height>=newImg.width?500:0).then(src=>{ this.pictures.push({src}); resolve(this.pictures[this.pictures.length-1]); }).catch(error=>{ console.log(error); this.$q.notify({message:error,timeout:2000}); resolve(null); }); }; PromiseFileReader.readAsDataURL(event.target.files[i]).then(src=>newImg.src=src).catch(error=>{ console.log(error); this.$q.notify({message:error,timeout:2000}); resolve(null); }); })); } this.$emit('picturesAdded',(await Promise.all(promises)).filter(picture=>picture!==null)); this.$q.loading.hide(); }, removePicture(index){ this.$emit('pictureRemoved',this.pictures[index]); this.pictures.splice(index,1); }, dragStart(index,event){ event.dataTransfer.setData('pictureIndex',index); this.dragging=index; this.$forceUpdate(); }, dragEnd(){ this.dragging=null; this.leftSeparatorVisible.fill(false); this.rightSeparatorVisible.fill(false); this.$forceUpdate(); }, dragEnter(event){ event.preventDefault(); }, dragOverLeft(index,event){ if(this.dragging===null)return; event.preventDefault(); this.leftSeparatorVisible[index]=true; this.$forceUpdate(); }, dragOverRight(index,event){ if(this.dragging===null)return; event.preventDefault(); this.rightSeparatorVisible[index]=true; this.$forceUpdate(); }, dragLeaveLeft(index){ this.leftSeparatorVisible[index]=false; this.$forceUpdate(); }, dragLeaveRight(index){ this.rightSeparatorVisible[index]=false; this.$forceUpdate(); }, dropLeft(index,event){ this.leftSeparatorVisible.fill(false); this.rightSeparatorVisible.fill(false); if(index===this.dragging){ this.dragging=null; return; } this.pictures.splice(index,0,this.pictures[this.dragging]); this.pictures.splice(this.dragging+(this.dragging>index?1:0),1); this.dragging=null; }, dropRight(index,event){ this.leftSeparatorVisible.fill(false); this.rightSeparatorVisible.fill(false); if(index===this.dragging){ this.dragging=null; return; } this.pictures.splice(index+1,0,this.pictures[this.dragging]); this.pictures.splice(this.dragging+(this.dragging>index?1:0),1); this.dragging=null; } }, mounted(){ if(!Array.isArray(this.pictures))this.pictures=[]; this.leftSeparatorVisible=new Array(this.pictures.length); this.leftSeparatorVisible.fill(false); this.rightSeparatorVisible=new Array(this.pictures.length); this.rightSeparatorVisible.fill(false); this.computeLastItem(); window.removeEventListener('resize',this.computeLastItem); window.addEventListener('resize',this.computeLastItem); } }; </script>
mobileDragDrop.js (boot script)
import {polyfill} from 'mobile-drag-drop'; import {scrollBehaviourDragImageTranslateOverride} from "mobile-drag-drop/scroll-behaviour"; polyfill({dragImageTranslateOverride:scrollBehaviourDragImageTranslateOverride});