Skip to content

Instantly share code, notes, and snippets.

@mflisikowski
Created October 11, 2017 14:16
Show Gist options
  • Save mflisikowski/8c1a3399bcfb96df717facce7c4d8f69 to your computer and use it in GitHub Desktop.
Save mflisikowski/8c1a3399bcfb96df717facce7c4d8f69 to your computer and use it in GitHub Desktop.
Vue.js upload component
<template lang="pug">
div(class="gwp-contest__field", :class="{'has-error': errors.has(schema.model) }")
div(class="dropzone")
input(class="is-hidden", type="text", value="", ref="uploadedOutput")
div(class="dropzone__upload")
div(class="dropzone__text")
div(class="dropzone__title")
div(v-if="(howMany !== maxItems)")
| {{ dropMsg }}
div(v-if="(howMany < minItems)")
| {{ dropMinMsg }}: [{{ minItems }}]
div(v-if="(howMany >= minItems && howMany < maxItems)")
| {{ dropMaxMsg }}: [{{ maxItems }}]
div(v-if="(howMany >= maxItems)")
| {{ dropSuccessMsg }}
input(
ref="uploadInput",
v-validate="hasValidators()",
:name="schema.inputName",
class="dropzone-area__input",
@change="onFileChange",
type="file",
:accept="schema.accept",
:multiple="schema.multiple"
)
div(class="dropzone__items")
div(class="dropzone__item", v-for="(item, index) in uploaded")
button(class="dropzone__remove", @click.prevent="removeImage(index)")
svg(class="dropzone__image", viewBox="0 0 1 1", width="100%", height="100%")
image(:xlink:href="item", width="100%", height="100%", preserveAspectRatio="xMidYMid slice")
div(v-if="errors.has(schema.model)", :class="{'has-error': errors.has(schema.model)}")
| {{ errors.first(schema.model) }}
</template>
<script>
import ImageCompressor from '@xkeshi/image-compressor'
import fieldsMixin from '../../mixins/fields'
import { API_POINT } from '../../constants'
export default {
mixins: [ fieldsMixin ],
data () {
return {
formData: null,
formTimeout: (index) => index * 1000,
formAccept: 'image/jpg;image/jpeg;image/png',
formHeaders: {
headers: {'Content-Type': 'multipart/form-data'}
},
dropSuccessMsg: this.schema.dropSuccessMsg || 'Dziękujemy za dodanie zdjęć',
dropMinMsg: this.schema.dropMinMsg || 'Minimalna liczba zdjęć to',
dropMaxMsg: this.schema.dropMaxMsg || 'Maksymalna liczba zdjęć to',
dropMsg: this.schema.dropMsg || 'Upuść obraz lub wybierz',
dropSupport: null,
files: [],
uploaded: [],
howMany: null,
compressed: this.schema.compressed || false,
minItems: this.schema.minItems || 1,
maxItems: this.schema.maxItems || 3
}
},
computed: {
correctDropMsg () {
return (this.dropSupport) ? 'true' : 'false' // this.dropMsg
}
},
watch: {
uploaded () {
this.howMany = this.uploaded.length
this.visibilityDropArea()
}
},
mounted () {
this.dropSupport = this.supportsDragAndDrop()
},
methods: {
onFileChange (e) {
this.files = e.target.files || e.dataTransfer.files
if (!this.files.length) return
return [].filter.call(this.files, (x) => x.type.match(/jpg|jpeg|png|gif$/)).map((file, index) => {
this.makeFormDataSubmit(file, index)
})
},
onFileDrop (e) {
e.preventDefault()
console.log('onFileDrop')
},
onFileDragover (e) {
e.preventDefault()
console.log('onFileDragover')
},
supportsDragAndDrop () {
let div = document.createElement('div')
return ('ondragover' in div && 'ondrop' in div)
},
visibilityDropArea () {
if (this.howMany >= this.maxItems) {
this.$refs.uploadInput.style.display = 'none'
} else {
this.$refs.uploadInput.style.display = 'block'
}
},
removeImage (index) {
this.uploaded.splice(index, 1)
},
storeOfSavedImages (array) {
this.$refs.uploadedOutput.value = JSON.stringify(array)
},
saveUrlsOfImages (url) {
this.uploaded.push(url)
this.storeOfSavedImages(this.uploaded)
},
makeRandomName (fileName) {
return `${Math.random().toString(36).substring(2, 48)}.${fileName.split('.')[1]}`
},
makeFormData (file, name) {
this.formData = new FormData()
this.formData.append('accept', this.formAccept)
this.formData.append('file', file, name)
},
makeImageWithoutCompress (file) {
this.makeFormData(file, this.makeRandomName(file.name))
this.makePostRequest(API_POINT.upload, this.formData, this.formHeaders)
},
makeImageWithCompress (file) {
/* eslint-disable no-new */
new ImageCompressor(file, {
quality: 0.6,
success: (result) => {
this.makeFormData(result, this.makeRandomName(file.name))
this.makePostRequest(API_POINT.upload, this.formData, this.formHeaders)
}
})
},
makePostRequest (point, data, config) {
this.$http.post(point, data, config)
.then((response) => {
this.saveUrlsOfImages(response.data.url)
}).catch((err) => {
if (err.status === 406) {
console.log(`Problem accessing. Reason: Not Acceptable`)
}
})
},
makeFormDataSubmit (file, index) {
setTimeout(() => {
if (this.errors.items.length === 0) {
(this.compressed) ? this.makeImageWithCompress(file) : this.makeImageWithoutCompress(file)
}
}, this.formTimeout(index))
}
}
}
</script>
<style lang="scss">
.dropzone
{
border-style: dashed;
border-width: 2px;
border-color: silver;
height: auto;
min-height: 200px;
font-size: 14px;
margin: 10px 0;
}
.dropzone__remove
{
position: absolute;
border: none;
background: none;
display: block;
width: 60px;
height: 60px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
cursor: pointer;
visibility: hidden;
transition: all 230ms cubic-bezier(0.42, 0, 0.58, 1);
will-change: visibility;
background-color: white;
border-radius: 50%;
&:focus
{
outline: none;
}
&::before
{
transform: translate(-50%, -50%) rotate(-45deg);
}
&::after
{
transform: translate(-50%, -50%) rotate(45deg);
}
&::before,
&::after {
top: 50%;
left: 50%;
position: absolute;
content: '';
width: 22px;
height: 4px;
border-radius: 4px;
background-color: #000;
display: block;
}
}
.dropzone__upload
{
position: relative;
min-height: 200px;
.dropzone__text
{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.dropzone-area__input
{
position: absolute;
cursor: pointer;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
}
.dropzone__text
{
text-align: center;
line-height: 1.6;
width: 100%;
max-width: 960px;
span
{
display: block;
}
}
.dropzone__title > .has-error {
color: red;
}
.dropzone__items
{
margin: 0;
padding: 0;
list-style: none;
box-sizing: border-box;
&::after
{
content: '';
display: table;
clear: both;
}
.dropzone__text
{
margin: 10px 0;
padding: 10px;
}
}
.dropzone__item
{
position: relative;
float: left;
display: inline-block;
margin: 5px;
width: calc((100% / 3) - 10px);
box-sizing: border-box;
&:nth-child(4)
{
clear: both
}
&:hover
{
.dropzone__remove
{
visibility: visible;
}
.dropzone__image
{
opacity: .5;
}
}
}
.dropzone__image
{
max-width: 100%;
margin: 0 auto;
z-index: 0;
transition: all 250ms cubic-bezier(0.42, 0, 0.58, 1);
will-change: opacity;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment