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});
    

Log in to reply