From 6ee846ef0eab0f3b91ff3557877cd2eb63acb505 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Mon, 7 Aug 2017 14:44:32 +0100 Subject: [PATCH] Support replace feature; close #188; multiple bug fixes on upload --- assets/src/components/files/Listing.vue | 71 +++++++++++++++------ assets/src/components/files/ListingItem.vue | 1 + assets/src/components/prompts/Prompts.vue | 6 +- assets/src/components/prompts/Replace.vue | 26 ++++++++ assets/src/i18n/en.yaml | 5 ++ assets/src/i18n/pt.yaml | 5 ++ assets/src/store/index.js | 3 +- assets/src/store/mutations.js | 1 + assets/src/utils/api.js | 16 ++++- resource.go | 9 ++- 10 files changed, 118 insertions(+), 25 deletions(-) create mode 100644 assets/src/components/prompts/Replace.vue diff --git a/assets/src/components/files/Listing.vue b/assets/src/components/files/Listing.vue index af7a8c6b..cb4e818d 100644 --- a/assets/src/components/files/Listing.vue +++ b/assets/src/components/files/Listing.vue @@ -8,7 +8,6 @@
@@ -230,7 +229,7 @@ export default { if (columns === 0) columns = 1 items.style.width = `calc(${100 / columns}% - 1em)` }, - dragEnter: function (event) { + dragEnter (event) { // When the user starts dragging an item, put every // file on the listing with 50% opacity. let items = document.getElementsByClassName('item') @@ -239,51 +238,85 @@ export default { file.style.opacity = 0.5 }) }, - dragEnd: function (event) { + dragEnd (event) { this.resetOpacity() }, drop: function (event) { event.preventDefault() + this.resetOpacity() let dt = event.dataTransfer let files = dt.files let el = event.target + if (files.length <= 0) return + for (let i = 0; i < 5; i++) { if (el !== null && !el.classList.contains('item')) { el = el.parentElement } } - if (files.length > 0) { - if (el !== null && el.classList.contains('item') && el.dataset.dir === 'true') { - this.handleFiles(files, el.querySelector('.name').innerHTML + '/') - return - } - - this.handleFiles(files, '') - } else { - this.resetOpacity() + let base = '' + if (el !== null && el.classList.contains('item') && el.dataset.dir === 'true') { + base = el.querySelector('.name').innerHTML + '/' } + + 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) { - this.handleFiles(event.currentTarget.files, '') + checkConflict (files, items, base) { + 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') Array.from(items).forEach(file => { file.style.opacity = 1 }) }, - handleFiles: function (files, base) { - this.resetOpacity() - + handleFiles (files, base, overwrite = false) { buttons.loading('upload') let promises = [] 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) diff --git a/assets/src/components/files/ListingItem.vue b/assets/src/components/files/ListingItem.vue index 81c7be12..eb67d095 100644 --- a/assets/src/components/files/ListingItem.vue +++ b/assets/src/components/files/ListingItem.vue @@ -9,6 +9,7 @@ @click="click" @dblclick="open" @touchstart="touchstart" + :data-dir="isDir" :aria-label="name" :aria-selected="isSelected">
diff --git a/assets/src/components/prompts/Prompts.vue b/assets/src/components/prompts/Prompts.vue index f42d7369..781bbe16 100644 --- a/assets/src/components/prompts/Prompts.vue +++ b/assets/src/components/prompts/Prompts.vue @@ -11,6 +11,7 @@ + + + diff --git a/assets/src/i18n/en.yaml b/assets/src/i18n/en.yaml index 2157ad95..2326a176 100644 --- a/assets/src/i18n/en.yaml +++ b/assets/src/i18n/en.yaml @@ -13,6 +13,7 @@ buttons: new: New next: Next ok: OK + replace: Replace previous: Previous rename: Rename reportIssue: Report Issue @@ -84,6 +85,10 @@ prompts: newFileMessage: Write the name of the new file. numberDirs: Number of directories 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 renameMessage: Insert a new name for show: Show diff --git a/assets/src/i18n/pt.yaml b/assets/src/i18n/pt.yaml index 8edf4160..eb758cdd 100644 --- a/assets/src/i18n/pt.yaml +++ b/assets/src/i18n/pt.yaml @@ -15,6 +15,7 @@ buttons: ok: OK previous: Anterior rename: Renomear + replace: Substituir reportIssue: Reportar Erro save: Guardar search: Pesquisar @@ -86,6 +87,10 @@ prompts: numberFiles: Número de ficheiros rename: Renomear 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 size: Tamanho settings: diff --git a/assets/src/store/index.js b/assets/src/store/index.js index 6d851b91..9c68cb6a 100644 --- a/assets/src/store/index.js +++ b/assets/src/store/index.js @@ -20,7 +20,8 @@ const state = { selected: [], multiple: false, show: null, - showMessage: null + showMessage: null, + showConfirm: null } export default new Vuex.Store({ diff --git a/assets/src/store/mutations.js b/assets/src/store/mutations.js index 481845f2..d88ac91b 100644 --- a/assets/src/store/mutations.js +++ b/assets/src/store/mutations.js @@ -13,6 +13,7 @@ const mutations = { state.show = value.prompt state.showMessage = value.message + state.showConfirm = value.confirm }, showError: (state, value) => { state.show = 'error' diff --git a/assets/src/utils/api.js b/assets/src/utils/api.js index 04fff2cd..3ada5927 100644 --- a/assets/src/utils/api.js +++ b/assets/src/utils/api.js @@ -4,9 +4,11 @@ const ssl = (window.location.protocol === 'https:') export function removePrefix (url) { if (url.startsWith('/files')) { - return url.slice(6) + url = url.slice(6) } + if (url === '') url = '/' + if (url[0] !== '/') url = '/' + 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) 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.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`) + if (overwrite) { + request.setRequestHeader('Action', `override`) + } + request.onload = () => { if (request.status === 200) { resolve(request.responseText) + } else if (request.status === 409) { + reject(request.status) } else { reject(request.responseText) } } - request.onerror = (error) => reject(error) + request.onerror = (error) => { + reject(error) + } request.send(content) }) } diff --git a/resource.go b/resource.go index 6eff1db6..792c5687 100644 --- a/resource.go +++ b/resource.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "io/ioutil" "net/http" "net/url" "os" @@ -155,6 +156,12 @@ func resourcePostPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Re 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. if strings.HasSuffix(r.URL.Path, "/") { // 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 // desirable to override an already existent file. Thus, we check // 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 { return http.StatusConflict, errors.New("There is already a file on that path") }