Support replace feature; close #188; multiple bug fixes on upload

pull/191/head
Henrique Dias 2017-08-07 14:44:32 +01:00
parent a117f83256
commit 6ee846ef0e
No known key found for this signature in database
GPG Key ID: 936F5EB68D786730
10 changed files with 118 additions and 25 deletions

View File

@ -8,7 +8,6 @@
</div> </div>
<div v-else id="listing" <div v-else id="listing"
:class="req.display" :class="req.display"
@drop="drop"
@dragenter="dragEnter" @dragenter="dragEnter"
@dragend="dragEnd"> @dragend="dragEnd">
<div> <div>
@ -230,7 +229,7 @@ export default {
if (columns === 0) columns = 1 if (columns === 0) columns = 1
items.style.width = `calc(${100 / columns}% - 1em)` items.style.width = `calc(${100 / columns}% - 1em)`
}, },
dragEnter: function (event) { dragEnter (event) {
// When the user starts dragging an item, put every // When the user starts dragging an item, put every
// file on the listing with 50% opacity. // file on the listing with 50% opacity.
let items = document.getElementsByClassName('item') let items = document.getElementsByClassName('item')
@ -239,51 +238,85 @@ export default {
file.style.opacity = 0.5 file.style.opacity = 0.5
}) })
}, },
dragEnd: function (event) { dragEnd (event) {
this.resetOpacity() this.resetOpacity()
}, },
drop: function (event) { drop: function (event) {
event.preventDefault() event.preventDefault()
this.resetOpacity()
let dt = event.dataTransfer let dt = event.dataTransfer
let files = dt.files let files = dt.files
let el = event.target let el = event.target
if (files.length <= 0) return
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
if (el !== null && !el.classList.contains('item')) { if (el !== null && !el.classList.contains('item')) {
el = el.parentElement el = el.parentElement
} }
} }
if (files.length > 0) { let base = ''
if (el !== null && el.classList.contains('item') && el.dataset.dir === 'true') { if (el !== null && el.classList.contains('item') && el.dataset.dir === 'true') {
this.handleFiles(files, el.querySelector('.name').innerHTML + '/') base = el.querySelector('.name').innerHTML + '/'
return
}
this.handleFiles(files, '')
} else {
this.resetOpacity()
} }
if (base !== '') {
api.fetch(this.$route.path + base)
.then(req => {
this.checkConflict(files, req.items, base)
})
.catch(error => { console.log(error) })
return
}
this.checkConflict(files, this.req.items, base)
}, },
uploadInput: function (event) { checkConflict (files, items, base) {
this.handleFiles(event.currentTarget.files, '') let conflict = false
for (let i = 0; i < files.length; i++) {
let res = items.findIndex(function hasConflict (element) {
return (element.name === this)
}, files[i].name)
if (res >= 0) {
conflict = true
break
}
}
if (!conflict) {
this.handleFiles(files, base)
return
}
this.$store.commit('showHover', {
prompt: 'replace',
confirm: (event) => {
event.preventDefault()
this.$store.commit('closeHovers')
this.handleFiles(files, base, true)
}
})
}, },
resetOpacity: function () { uploadInput (event) {
this.checkConflict(event.currentTarget.files, this.req.items, '')
},
resetOpacity () {
let items = document.getElementsByClassName('item') let items = document.getElementsByClassName('item')
Array.from(items).forEach(file => { Array.from(items).forEach(file => {
file.style.opacity = 1 file.style.opacity = 1
}) })
}, },
handleFiles: function (files, base) { handleFiles (files, base, overwrite = false) {
this.resetOpacity()
buttons.loading('upload') buttons.loading('upload')
let promises = [] let promises = []
for (let file of files) { for (let file of files) {
promises.push(api.post(this.$route.path + base + file.name, file)) promises.push(api.post(this.$route.path + base + file.name, file, overwrite))
} }
Promise.all(promises) Promise.all(promises)

View File

@ -9,6 +9,7 @@
@click="click" @click="click"
@dblclick="open" @dblclick="open"
@touchstart="touchstart" @touchstart="touchstart"
:data-dir="isDir"
:aria-label="name" :aria-label="name"
:aria-selected="isSelected"> :aria-selected="isSelected">
<div> <div>

View File

@ -11,6 +11,7 @@
<copy v-else-if="showCopy"></copy> <copy v-else-if="showCopy"></copy>
<error v-else-if="showError"></error> <error v-else-if="showError"></error>
<success v-else-if="showSuccess"></success> <success v-else-if="showSuccess"></success>
<replace v-else-if="showReplace"></replace>
<template v-for="plugin in plugins"> <template v-for="plugin in plugins">
<form class="prompt" <form class="prompt"
@ -54,6 +55,7 @@ import Error from './Error'
import Success from './Success' import Success from './Success'
import NewFile from './NewFile' import NewFile from './NewFile'
import NewDir from './NewDir' import NewDir from './NewDir'
import Replace from './Replace'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import buttons from '@/utils/buttons' import buttons from '@/utils/buttons'
import api from '@/utils/api' import api from '@/utils/api'
@ -71,7 +73,8 @@ export default {
Copy, Copy,
NewFile, NewFile,
NewDir, NewDir,
Help Help,
Replace
}, },
data: function () { data: function () {
return { return {
@ -96,6 +99,7 @@ export default {
showNewFile: function () { return this.show === 'newFile' }, showNewFile: function () { return this.show === 'newFile' },
showNewDir: function () { return this.show === 'newDir' }, showNewDir: function () { return this.show === 'newDir' },
showDownload: function () { return this.show === 'download' }, showDownload: function () { return this.show === 'download' },
showReplace: function () { return this.show === 'replace' },
showOverlay: function () { showOverlay: function () {
return (this.show !== null && this.show !== 'search' && this.show !== 'more') return (this.show !== null && this.show !== 'search' && this.show !== 'more')
} }

View File

@ -0,0 +1,26 @@
<template>
<div class="prompt">
<h3>{{ $t('prompts.replace') }}</h3>
<p>{{ $t('prompts.replaceMessage') }}</p>
<div>
<button class="ok"
@click="showConfirm"
:aria-label="$t('buttons.replace')"
:title="$t('buttons.replace')">{{ $t('buttons.replace') }}</button>
<button class="cancel"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'replace',
computed: mapState(['showConfirm'])
}
</script>

View File

@ -13,6 +13,7 @@ buttons:
new: New new: New
next: Next next: Next
ok: OK ok: OK
replace: Replace
previous: Previous previous: Previous
rename: Rename rename: Rename
reportIssue: Report Issue reportIssue: Report Issue
@ -84,6 +85,10 @@ prompts:
newFileMessage: Write the name of the new file. newFileMessage: Write the name of the new file.
numberDirs: Number of directories numberDirs: Number of directories
numberFiles: Number of files numberFiles: Number of files
replace: Replace
replaceMessage: >
One of the files you're trying to upload is conflicting because of its name.
Do you wish to replace the existing one?
rename: Rename rename: Rename
renameMessage: Insert a new name for renameMessage: Insert a new name for
show: Show show: Show

View File

@ -15,6 +15,7 @@ buttons:
ok: OK ok: OK
previous: Anterior previous: Anterior
rename: Renomear rename: Renomear
replace: Substituir
reportIssue: Reportar Erro reportIssue: Reportar Erro
save: Guardar save: Guardar
search: Pesquisar search: Pesquisar
@ -86,6 +87,10 @@ prompts:
numberFiles: Número de ficheiros numberFiles: Número de ficheiros
rename: Renomear rename: Renomear
renameMessage: Insira um novo nome para renameMessage: Insira um novo nome para
replace: Substituir
replaceMessage: >
Já existe um ficheiro com nome igual a um dos que está a tentar enviar. Deseja
substituir?
show: Mostrar show: Mostrar
size: Tamanho size: Tamanho
settings: settings:

View File

@ -20,7 +20,8 @@ const state = {
selected: [], selected: [],
multiple: false, multiple: false,
show: null, show: null,
showMessage: null showMessage: null,
showConfirm: null
} }
export default new Vuex.Store({ export default new Vuex.Store({

View File

@ -13,6 +13,7 @@ const mutations = {
state.show = value.prompt state.show = value.prompt
state.showMessage = value.message state.showMessage = value.message
state.showConfirm = value.confirm
}, },
showError: (state, value) => { showError: (state, value) => {
state.show = 'error' state.show = 'error'

View File

@ -4,9 +4,11 @@ const ssl = (window.location.protocol === 'https:')
export function removePrefix (url) { export function removePrefix (url) {
if (url.startsWith('/files')) { if (url.startsWith('/files')) {
return url.slice(6) url = url.slice(6)
} }
if (url === '') url = '/'
if (url[0] !== '/') url = '/' + url
return url return url
} }
@ -54,7 +56,7 @@ export function rm (url) {
}) })
} }
export function post (url, content = '') { export function post (url, content = '', overwrite = false) {
url = removePrefix(url) url = removePrefix(url)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -62,15 +64,23 @@ export function post (url, content = '') {
request.open('POST', `${store.state.baseURL}/api/resource${url}`, true) request.open('POST', `${store.state.baseURL}/api/resource${url}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (overwrite) {
request.setRequestHeader('Action', `override`)
}
request.onload = () => { request.onload = () => {
if (request.status === 200) { if (request.status === 200) {
resolve(request.responseText) resolve(request.responseText)
} else if (request.status === 409) {
reject(request.status)
} else { } else {
reject(request.responseText) reject(request.responseText)
} }
} }
request.onerror = (error) => reject(error) request.onerror = (error) => {
reject(error)
}
request.send(content) request.send(content)
}) })
} }

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@ -155,6 +156,12 @@ func resourcePostPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Re
return http.StatusForbidden, nil return http.StatusForbidden, nil
} }
// Discard any invalid upload before returning to avoid connection
// reset error.
defer func() {
io.Copy(ioutil.Discard, r.Body)
}()
// Checks if the current request is for a directory and not a file. // Checks if the current request is for a directory and not a file.
if strings.HasSuffix(r.URL.Path, "/") { if strings.HasSuffix(r.URL.Path, "/") {
// If the method is PUT, we return 405 Method not Allowed, because // If the method is PUT, we return 405 Method not Allowed, because
@ -171,7 +178,7 @@ func resourcePostPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Re
// If using POST method, we are trying to create a new file so it is not // If using POST method, we are trying to create a new file so it is not
// desirable to override an already existent file. Thus, we check // desirable to override an already existent file. Thus, we check
// if the file already exists. If so, we just return a 409 Conflict. // if the file already exists. If so, we just return a 409 Conflict.
if r.Method == http.MethodPost { if r.Method == http.MethodPost && r.Header.Get("Action") != "override" {
if _, err := c.User.FileSystem.Stat(r.URL.Path); err == nil { if _, err := c.User.FileSystem.Stat(r.URL.Path); err == nil {
return http.StatusConflict, errors.New("There is already a file on that path") return http.StatusConflict, errors.New("There is already a file on that path")
} }