feat(files): Allow uploading directories

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/45095/head
Ferdinand Thiessen 2024-06-22 13:56:30 +07:00
parent 879eaa7681
commit 079d026d39
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400
4 changed files with 162 additions and 89 deletions

@ -3,6 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
export default {
interceptors: {
response: {
use: () => {},
},
request: {
use: () => {},
},
},
get: async () => ({ status: 200, data: {} }),
delete: async () => ({ status: 200, data: {} }),
post: async () => ({ status: 200, data: {} }),

@ -37,10 +37,12 @@
<!-- Uploader -->
<UploadPicker v-else-if="currentFolder"
:content="dirContents"
:destination="currentFolder"
:multiple="true"
allow-folders
class="files-list__header-upload-button"
:content="getContent"
:destination="currentFolder"
:forbidden-characters="forbiddenCharacters"
multiple
@failed="onUploadFail"
@uploaded="onUpload" />
</template>
@ -79,9 +81,11 @@
<template v-if="dir !== '/'" #action>
<!-- Uploader -->
<UploadPicker v-if="currentFolder && canUpload && !isQuotaExceeded"
:content="dirContents"
:destination="currentFolder"
allow-folders
class="files-list__header-upload-button"
:content="getContent"
:destination="currentFolder"
:forbidden-characters="forbiddenCharacters"
multiple
@failed="onUploadFail"
@uploaded="onUpload" />
@ -118,10 +122,10 @@ import { getCapabilities } from '@nextcloud/capabilities'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
import { Folder, Node, Permission } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { join, dirname } from 'path'
import { showError } from '@nextcloud/dialogs'
import { join, dirname, normalize } from 'path'
import { showError, showWarning } from '@nextcloud/dialogs'
import { Type } from '@nextcloud/sharing'
import { UploadPicker } from '@nextcloud/upload'
import { UploadPicker, UploadStatus } from '@nextcloud/upload'
import { loadState } from '@nextcloud/initial-state'
import { defineComponent } from 'vue'
@ -190,6 +194,7 @@ export default defineComponent({
const { currentView } = useNavigation()
const enableGridView = (loadState('core', 'config', [])['enable_non-accessible_features'] ?? true)
const forbiddenCharacters = loadState<string[]>('files', 'forbiddenCharacters', [])
return {
currentView,
@ -200,9 +205,10 @@ export default defineComponent({
uploaderStore,
userConfigStore,
viewConfigStore,
enableGridView,
// non reactive data
enableGridView,
forbiddenCharacters,
Type,
}
},
@ -228,6 +234,19 @@ export default defineComponent({
}, 500)
},
/**
* Get a callback function for the uploader to fetch directory contents for conflict resolution
*/
getContent() {
const view = this.currentView
return async (path?: string) => {
// as the path is allowed to be undefined we need to normalize the path ('//' to '/')
const normalizedPath = normalize(`${this.currentFolder?.path ?? ''}/${path ?? ''}`)
// use the current view to fetch the content for the requested path
return (await view.getContents(normalizedPath)).contents
}
},
userConfig(): UserConfig {
return this.userConfigStore.userConfig
},
@ -590,8 +609,7 @@ export default defineComponent({
onUpload(upload: Upload) {
// Let's only refresh the current Folder
// Navigating to a different folder will refresh it anyway
const destinationSource = dirname(upload.source)
const needsRefresh = destinationSource === this.currentFolder?.source
const needsRefresh = dirname(upload.source) === this.currentFolder!.source
// TODO: fetch uploaded files data only
// Use parseInt(upload.response?.headers?.['oc-fileid']) to get the fileid
@ -604,6 +622,11 @@ export default defineComponent({
async onUploadFail(upload: Upload) {
const status = upload.response?.status || 0
if (upload.status === UploadStatus.CANCELLED) {
showWarning(t('files', 'Upload was cancelled by user'))
return
}
// Check known status codes
if (status === 507) {
showError(t('files', 'Not enough free space'))

188
package-lock.json generated

@ -29,13 +29,13 @@
"@nextcloud/paths": "^2.1.0",
"@nextcloud/router": "^3.0.0",
"@nextcloud/sharing": "^0.1.0",
"@nextcloud/upload": "^1.1.1",
"@nextcloud/upload": "^1.4.1",
"@nextcloud/vue": "^8.11.2",
"@simplewebauthn/browser": "^10.0.0",
"@skjnldsv/sanitize-svg": "^1.0.2",
"@vueuse/components": "^10.9.0",
"@vueuse/core": "^10.9.0",
"@vueuse/integrations": "^10.9.0",
"@vueuse/components": "^10.11.0",
"@vueuse/core": "^10.11.0",
"@vueuse/integrations": "^10.11.0",
"backbone": "^1.4.1",
"blueimp-md5": "^2.19.0",
"browserslist-useragent-regexp": "^4.1.1",
@ -85,7 +85,7 @@
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2",
"vuex-router-sync": "^5.0.0",
"webdav": "^5.5.0"
"webdav": "^5.6.0"
},
"devDependencies": {
"@babel/node": "^7.22.10",
@ -4632,20 +4632,21 @@
}
},
"node_modules/@nextcloud/upload": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@nextcloud/upload/-/upload-1.1.1.tgz",
"integrity": "sha512-cMEIL3dJtAJVdyQnEqVpBrNlH736KxepXykZZCRKWB5dNxoK3mChMV96a/bCGceR2bdCR9mOY9VQKn2CweA5cA==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@nextcloud/upload/-/upload-1.4.1.tgz",
"integrity": "sha512-5Y/Vred7hz/RJv5ftC206H0BXVgvQqNnXMOs+J3hTslNN0WU4ku3m0g4qlyg+zaDjThBsXIPJA1dpWuRuV8rHA==",
"dependencies": {
"@nextcloud/auth": "^2.2.1",
"@nextcloud/axios": "^2.4.0",
"@nextcloud/axios": "^2.5.0",
"@nextcloud/dialogs": "^5.2.0",
"@nextcloud/files": "^3.1.1",
"@nextcloud/l10n": "^2.2.0",
"@nextcloud/logger": "^2.7.0",
"@nextcloud/files": "^3.5.1",
"@nextcloud/l10n": "^3.1.0",
"@nextcloud/logger": "^3.0.2",
"@nextcloud/paths": "^2.1.0",
"@nextcloud/router": "^3.0.0",
"axios": "^1.6.8",
"buffer": "^6.0.3",
"@nextcloud/sharing": "^0.2.2",
"axios": "^1.7.2",
"axios-retry": "^4.4.0",
"crypto-browserify": "^3.12.0",
"p-cancelable": "^4.0.1",
"p-queue": "^8.0.0",
@ -4660,17 +4661,34 @@
"vue": "^2.7.16"
}
},
"node_modules/@nextcloud/upload/node_modules/@nextcloud/logger": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/@nextcloud/logger/-/logger-2.7.0.tgz",
"integrity": "sha512-DSJg9H1jT2zfr7uoP4tL5hKncyY+LOuxJzLauj0M/f6gnpoXU5WG1Zw8EFPOrRWjkC0ZE+NCqrMnITgdRRpXJQ==",
"node_modules/@nextcloud/upload/node_modules/@nextcloud/l10n": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-3.1.0.tgz",
"integrity": "sha512-unciqr8QSJ29vFBw9S1bquyoj1PTWHszNL8tcUNuxUAYpq0hX+8o7rpB5gimELA4sj4m9+VCJwgLtBZd1Yj0lg==",
"dependencies": {
"@nextcloud/auth": "^2.0.0",
"core-js": "^3.6.4"
"@nextcloud/router": "^3.0.1",
"@nextcloud/typings": "^1.8.0",
"@types/dompurify": "^3.0.5",
"@types/escape-html": "^1.0.4",
"dompurify": "^3.1.2",
"escape-html": "^1.0.3",
"node-gettext": "^3.0.0"
},
"engines": {
"node": "^20.0.0",
"npm": "^9.0.0"
"npm": "^10.0.0"
}
},
"node_modules/@nextcloud/upload/node_modules/@nextcloud/sharing": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@nextcloud/sharing/-/sharing-0.2.2.tgz",
"integrity": "sha512-ui0ZoVazroA+cF4+homhFSFAddd/P4uRYMfG3rw3QR8o6igrVFe0f0l21kYtUwXU0oC0K4v3k8j93zCTfz6v3g==",
"dependencies": {
"@nextcloud/initial-state": "^2.2.0"
},
"engines": {
"node": "^20.0.0",
"npm": "^10.0.0"
}
},
"node_modules/@nextcloud/upload/node_modules/eventemitter3": {
@ -6597,19 +6615,19 @@
}
},
"node_modules/@vueuse/components": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.9.0.tgz",
"integrity": "sha512-BHQpA0yIi3y7zKa1gYD0FUzLLkcRTqVhP8smnvsCK6GFpd94Nziq1XVPD7YpFeho0k5BzbBiNZF7V/DpkJ967A==",
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.11.0.tgz",
"integrity": "sha512-ZvLZI23d5ZAtva5fGyYh/jQtZO8l+zJ5tAXyYNqHJZkq1o5yWyqZhENvSv5mfDmN5IuAOp4tq02mRmX/ipFGcg==",
"dependencies": {
"@vueuse/core": "10.9.0",
"@vueuse/shared": "10.9.0",
"vue-demi": ">=0.14.7"
"@vueuse/core": "10.11.0",
"@vueuse/shared": "10.11.0",
"vue-demi": ">=0.14.8"
}
},
"node_modules/@vueuse/components/node_modules/vue-demi": {
"version": "0.14.7",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
"integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
"version": "0.14.8",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
"integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
@ -6632,23 +6650,23 @@
}
},
"node_modules/@vueuse/core": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.9.0.tgz",
"integrity": "sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==",
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.0.tgz",
"integrity": "sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==",
"dependencies": {
"@types/web-bluetooth": "^0.0.20",
"@vueuse/metadata": "10.9.0",
"@vueuse/shared": "10.9.0",
"vue-demi": ">=0.14.7"
"@vueuse/metadata": "10.11.0",
"@vueuse/shared": "10.11.0",
"vue-demi": ">=0.14.8"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/core/node_modules/vue-demi": {
"version": "0.14.7",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
"integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
"version": "0.14.8",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
"integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
@ -6671,30 +6689,30 @@
}
},
"node_modules/@vueuse/integrations": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.9.0.tgz",
"integrity": "sha512-acK+A01AYdWSvL4BZmCoJAcyHJ6EqhmkQEXbQLwev1MY7NBnS+hcEMx/BzVoR9zKI+UqEPMD9u6PsyAuiTRT4Q==",
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.11.0.tgz",
"integrity": "sha512-Pp6MtWEIr+NDOccWd8j59Kpjy5YDXogXI61Kb1JxvSfVBO8NzFQkmrKmSZz47i+ZqHnIzxaT38L358yDHTncZg==",
"dependencies": {
"@vueuse/core": "10.9.0",
"@vueuse/shared": "10.9.0",
"vue-demi": ">=0.14.7"
"@vueuse/core": "10.11.0",
"@vueuse/shared": "10.11.0",
"vue-demi": ">=0.14.8"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"async-validator": "*",
"axios": "*",
"change-case": "*",
"drauu": "*",
"focus-trap": "*",
"fuse.js": "*",
"idb-keyval": "*",
"jwt-decode": "*",
"nprogress": "*",
"qrcode": "*",
"sortablejs": "*",
"universal-cookie": "*"
"async-validator": "^4",
"axios": "^1",
"change-case": "^4",
"drauu": "^0.3",
"focus-trap": "^7",
"fuse.js": "^6",
"idb-keyval": "^6",
"jwt-decode": "^3",
"nprogress": "^0.2",
"qrcode": "^1.5",
"sortablejs": "^1",
"universal-cookie": "^6"
},
"peerDependenciesMeta": {
"async-validator": {
@ -6736,9 +6754,9 @@
}
},
"node_modules/@vueuse/integrations/node_modules/vue-demi": {
"version": "0.14.7",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
"integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
"version": "0.14.8",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
"integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
@ -6761,28 +6779,28 @@
}
},
"node_modules/@vueuse/metadata": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.9.0.tgz",
"integrity": "sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==",
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.0.tgz",
"integrity": "sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.9.0.tgz",
"integrity": "sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==",
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.0.tgz",
"integrity": "sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==",
"dependencies": {
"vue-demi": ">=0.14.7"
"vue-demi": ">=0.14.8"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared/node_modules/vue-demi": {
"version": "0.14.7",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
"integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
"version": "0.14.8",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
"integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
@ -7656,15 +7674,26 @@
}
},
"node_modules/axios": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axios-retry": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.4.1.tgz",
"integrity": "sha512-JGzNoglDHtHWIEvvAampB0P7jxQ/sT4COmW0FgSQkVg6o4KqNjNMBI6uFVOq517hkw/OAYYAG08ADtBlV8lvmQ==",
"dependencies": {
"is-retry-allowed": "^2.2.0"
},
"peerDependencies": {
"axios": "0.x || 1.x"
}
},
"node_modules/b4a": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz",
@ -8505,6 +8534,7 @@
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"dev": true,
"funding": [
{
"type": "github",
@ -8519,6 +8549,7 @@
"url": "https://feross.org/support"
}
],
"peer": true,
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
@ -15418,6 +15449,17 @@
"node": ">=0.10.0"
}
},
"node_modules/is-retry-allowed": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz",
"integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-set": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",

@ -57,13 +57,13 @@
"@nextcloud/paths": "^2.1.0",
"@nextcloud/router": "^3.0.0",
"@nextcloud/sharing": "^0.1.0",
"@nextcloud/upload": "^1.1.1",
"@nextcloud/upload": "^1.4.1",
"@nextcloud/vue": "^8.11.2",
"@simplewebauthn/browser": "^10.0.0",
"@skjnldsv/sanitize-svg": "^1.0.2",
"@vueuse/components": "^10.9.0",
"@vueuse/core": "^10.9.0",
"@vueuse/integrations": "^10.9.0",
"@vueuse/components": "^10.11.0",
"@vueuse/core": "^10.11.0",
"@vueuse/integrations": "^10.11.0",
"backbone": "^1.4.1",
"blueimp-md5": "^2.19.0",
"browserslist-useragent-regexp": "^4.1.1",
@ -113,7 +113,7 @@
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2",
"vuex-router-sync": "^5.0.0",
"webdav": "^5.5.0"
"webdav": "^5.6.0"
},
"devDependencies": {
"@babel/node": "^7.22.10",