Support replace feature; close #188; multiple bug fixes on upload
parent
a117f83256
commit
6ee846ef0e
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue