From cf85404dd25cd7fdd73aa32878b4dc5f85ee3e96 Mon Sep 17 00:00:00 2001 From: Filippo Finke <37296364+filippofinke@users.noreply.github.com> Date: Mon, 21 Feb 2022 19:30:42 +0100 Subject: [PATCH] feat: add upload file list with progress (#1825) --- .../src/components/prompts/UploadFiles.vue | 62 +++++ frontend/src/css/base.css | 32 ++- frontend/src/css/listing-icons.css | 252 +++++++++--------- frontend/src/css/styles.css | 1 + frontend/src/css/upload-files.css | 61 +++++ frontend/src/i18n/en.json | 3 +- frontend/src/store/getters.js | 30 ++- frontend/src/store/modules/upload.js | 7 +- frontend/src/utils/upload.js | 10 + frontend/src/views/Layout.vue | 5 +- frontend/src/views/Share.vue | 2 +- frontend/src/views/files/Listing.vue | 7 +- 12 files changed, 325 insertions(+), 147 deletions(-) create mode 100644 frontend/src/components/prompts/UploadFiles.vue create mode 100644 frontend/src/css/upload-files.css diff --git a/frontend/src/components/prompts/UploadFiles.vue b/frontend/src/components/prompts/UploadFiles.vue new file mode 100644 index 00000000..cad8bc0f --- /dev/null +++ b/frontend/src/components/prompts/UploadFiles.vue @@ -0,0 +1,62 @@ + + + diff --git a/frontend/src/css/base.css b/frontend/src/css/base.css index bf34798b..05d799c9 100644 --- a/frontend/src/css/base.css +++ b/frontend/src/css/base.css @@ -1,5 +1,5 @@ body { - font-family: 'Roboto', sans-serif; + font-family: "Roboto", sans-serif; padding-top: 4em; background-color: #fafafa; color: #333333; @@ -13,7 +13,7 @@ body { *:hover, *:active, *:focus { - outline: 0 + outline: 0; } a { @@ -44,7 +44,7 @@ i.spin { } #app { - transition: .2s ease padding; + transition: 0.2s ease padding; } #app.multiple { @@ -63,17 +63,17 @@ nav .action { display: block; border-radius: 0; font-size: 1.1em; - padding: .5em; + padding: 0.5em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -nav>div { +nav > div { border-top: 1px solid rgba(0, 0, 0, 0.05); } -nav .action>* { +nav .action > * { vertical-align: middle; } @@ -97,19 +97,25 @@ main { .breadcrumbs a { color: inherit; - transition: .1s ease-in; - border-radius: .125em; + transition: 0.1s ease-in; + border-radius: 0.125em; } .breadcrumbs a:hover { - background-color: rgba(0,0,0, 0.05); + background-color: rgba(0, 0, 0, 0.05); } .breadcrumbs span a { - padding: .2em; + padding: 0.2em; } -#progress { +.files { + position: absolute; + bottom: 30px; + width: 100%; +} + +.progress { position: fixed; top: 0; left: 0; @@ -118,11 +124,11 @@ main { z-index: 9999999999; } -#progress div { +.progress div { height: 100%; background-color: #40c4ff; width: 0; - transition: .2s ease width; + transition: 0.2s ease width; } .break-word { diff --git a/frontend/src/css/listing-icons.css b/frontend/src/css/listing-icons.css index bb8822c6..2de6baab 100644 --- a/frontend/src/css/listing-icons.css +++ b/frontend/src/css/listing-icons.css @@ -2,202 +2,202 @@ /* General */ -#listing [aria-label^="."] { opacity: 0.33 } -#listing [aria-label$=".bak"] { opacity: 0.33 } +.file-icons [aria-label^="."] { opacity: 0.33 } +.file-icons [aria-label$=".bak"] { opacity: 0.33 } -#listing [data-type=audio] i::before { content: 'volume_up' } -#listing [data-type=blob] i::before { content: 'insert_drive_file' } -#listing [data-type=image] i::before { content: 'image' } -#listing [data-type=pdf] i::before { content: 'description' } -#listing [data-type=text] i::before { content: 'description' } -#listing [data-type=video] i::before { content: 'movie' } +.file-icons [data-type=audio] i::before { content: 'volume_up' } +.file-icons [data-type=blob] i::before { content: 'insert_drive_file' } +.file-icons [data-type=image] i::before { content: 'image' } +.file-icons [data-type=pdf] i::before { content: 'description' } +.file-icons [data-type=text] i::before { content: 'description' } +.file-icons [data-type=video] i::before { content: 'movie' } /* #f90 - Image */ -#listing [aria-label$=".ai"] i::before, -#listing [aria-label$=".odg"] i::before, -#listing [aria-label$=".xcf"] i::before +.file-icons [aria-label$=".ai"] i::before, +.file-icons [aria-label$=".odg"] i::before, +.file-icons [aria-label$=".xcf"] i::before { content: 'image' } /* #f90 - Presentation */ -#listing [aria-label$=".odp"] i::before, -#listing [aria-label$=".ppt"] i::before, -#listing [aria-label$=".pptx"] i::before +.file-icons [aria-label$=".odp"] i::before, +.file-icons [aria-label$=".ppt"] i::before, +.file-icons [aria-label$=".pptx"] i::before { content: 'slideshow' } /* #0f0 - Spreadsheet/Database */ -#listing [aria-label$=".csv"] i::before, -#listing [aria-label$=".db"] i::before, -#listing [aria-label$=".odb"] i::before, -#listing [aria-label$=".ods"] i::before, -#listing [aria-label$=".xls"] i::before, -#listing [aria-label$=".xlsx"] i::before +.file-icons [aria-label$=".csv"] i::before, +.file-icons [aria-label$=".db"] i::before, +.file-icons [aria-label$=".odb"] i::before, +.file-icons [aria-label$=".ods"] i::before, +.file-icons [aria-label$=".xls"] i::before, +.file-icons [aria-label$=".xlsx"] i::before { content: 'border_all' } /* #00f - Document */ -#listing [aria-label$=".doc"] i::before, -#listing [aria-label$=".docx"] i::before, -#listing [aria-label$=".log"] i::before, -#listing [aria-label$=".odt"] i::before, -#listing [aria-label$=".rtf"] i::before +.file-icons [aria-label$=".doc"] i::before, +.file-icons [aria-label$=".docx"] i::before, +.file-icons [aria-label$=".log"] i::before, +.file-icons [aria-label$=".odt"] i::before, +.file-icons [aria-label$=".rtf"] i::before { content: 'description' } /* #999 - Code */ -#listing [aria-label$=".c"] i::before, -#listing [aria-label$=".cpp"] i::before, -#listing [aria-label$=".cs"] i::before, -#listing [aria-label$=".css"] i::before, -#listing [aria-label$=".go"] i::before, -#listing [aria-label$=".h"] i::before, -#listing [aria-label$=".html"] i::before, -#listing [aria-label$=".java"] i::before, -#listing [aria-label$=".js"] i::before, -#listing [aria-label$=".json"] i::before, -#listing [aria-label$=".kt"] i::before, -#listing [aria-label$=".php"] i::before, -#listing [aria-label$=".py"] i::before, -#listing [aria-label$=".rb"] i::before, -#listing [aria-label$=".rs"] i::before, -#listing [aria-label$=".vue"] i::before, -#listing [aria-label$=".xml"] i::before, -#listing [aria-label$=".yml"] i::before +.file-icons [aria-label$=".c"] i::before, +.file-icons [aria-label$=".cpp"] i::before, +.file-icons [aria-label$=".cs"] i::before, +.file-icons [aria-label$=".css"] i::before, +.file-icons [aria-label$=".go"] i::before, +.file-icons [aria-label$=".h"] i::before, +.file-icons [aria-label$=".html"] i::before, +.file-icons [aria-label$=".java"] i::before, +.file-icons [aria-label$=".js"] i::before, +.file-icons [aria-label$=".json"] i::before, +.file-icons [aria-label$=".kt"] i::before, +.file-icons [aria-label$=".php"] i::before, +.file-icons [aria-label$=".py"] i::before, +.file-icons [aria-label$=".rb"] i::before, +.file-icons [aria-label$=".rs"] i::before, +.file-icons [aria-label$=".vue"] i::before, +.file-icons [aria-label$=".xml"] i::before, +.file-icons [aria-label$=".yml"] i::before { content: 'code' } /* #999 - Executable */ -#listing [aria-label$=".apk"] i::before, -#listing [aria-label$=".bat"] i::before, -#listing [aria-label$=".exe"] i::before, -#listing [aria-label$=".jar"] i::before, -#listing [aria-label$=".ps1"] i::before, -#listing [aria-label$=".sh"] i::before +.file-icons [aria-label$=".apk"] i::before, +.file-icons [aria-label$=".bat"] i::before, +.file-icons [aria-label$=".exe"] i::before, +.file-icons [aria-label$=".jar"] i::before, +.file-icons [aria-label$=".ps1"] i::before, +.file-icons [aria-label$=".sh"] i::before { content: 'web_asset' } /* #999 - Installer */ -#listing [aria-label$=".deb"] i::before, -#listing [aria-label$=".msi"] i::before, -#listing [aria-label$=".pkg"] i::before, -#listing [aria-label$=".rpm"] i::before +.file-icons [aria-label$=".deb"] i::before, +.file-icons [aria-label$=".msi"] i::before, +.file-icons [aria-label$=".pkg"] i::before, +.file-icons [aria-label$=".rpm"] i::before { content: 'archive' } /* #999 - Compressed */ -#listing [aria-label$=".7z"] i::before, -#listing [aria-label$=".bz2"] i::before, -#listing [aria-label$=".cab"] i::before, -#listing [aria-label$=".gz"] i::before, -#listing [aria-label$=".rar"] i::before, -#listing [aria-label$=".tar"] i::before, -#listing [aria-label$=".xz"] i::before, -#listing [aria-label$=".zip"] i::before, -#listing [aria-label$=".zst"] i::before +.file-icons [aria-label$=".7z"] i::before, +.file-icons [aria-label$=".bz2"] i::before, +.file-icons [aria-label$=".cab"] i::before, +.file-icons [aria-label$=".gz"] i::before, +.file-icons [aria-label$=".rar"] i::before, +.file-icons [aria-label$=".tar"] i::before, +.file-icons [aria-label$=".xz"] i::before, +.file-icons [aria-label$=".zip"] i::before, +.file-icons [aria-label$=".zst"] i::before { content: 'folder_zip' } /* #999 - Disk */ -#listing [aria-label$=".ccd"] i::before, -#listing [aria-label$=".dmg"] i::before, -#listing [aria-label$=".iso"] i::before, -#listing [aria-label$=".mdf"] i::before, -#listing [aria-label$=".vdi"] i::before, -#listing [aria-label$=".vhd"] i::before, -#listing [aria-label$=".vmdk"] i::before, -#listing [aria-label$=".wim"] i::before +.file-icons [aria-label$=".ccd"] i::before, +.file-icons [aria-label$=".dmg"] i::before, +.file-icons [aria-label$=".iso"] i::before, +.file-icons [aria-label$=".mdf"] i::before, +.file-icons [aria-label$=".vdi"] i::before, +.file-icons [aria-label$=".vhd"] i::before, +.file-icons [aria-label$=".vmdk"] i::before, +.file-icons [aria-label$=".wim"] i::before { content: 'album' } /* #999 - Font */ -#listing [aria-label$=".otf"] i::before, -#listing [aria-label$=".ttf"] i::before, -#listing [aria-label$=".woff"] i::before, -#listing [aria-label$=".woff2"] i::before +.file-icons [aria-label$=".otf"] i::before, +.file-icons [aria-label$=".ttf"] i::before, +.file-icons [aria-label$=".woff"] i::before, +.file-icons [aria-label$=".woff2"] i::before { content: 'font_download' } /* Colors */ /* General */ -#listing [data-type=audio] i { color: var(--icon-yellow) } -#listing [data-type=image] i { color: var(--icon-orange) } -#listing [data-type=video] i { color: var(--icon-violet) } +.file-icons [data-type=audio] i { color: var(--icon-yellow) } +.file-icons [data-type=image] i { color: var(--icon-orange) } +.file-icons [data-type=video] i { color: var(--icon-violet) } /* #f00 - Adobe/Oracle */ -#listing [aria-label$=".ai"] i, -#listing [aria-label$=".java"] i, -#listing [aria-label$=".jar"] i, -#listing [aria-label$=".psd"] i, -#listing [aria-label$=".rb"] i, -#listing [data-type=pdf] i +.file-icons [aria-label$=".ai"] i, +.file-icons [aria-label$=".java"] i, +.file-icons [aria-label$=".jar"] i, +.file-icons [aria-label$=".psd"] i, +.file-icons [aria-label$=".rb"] i, +.file-icons [data-type=pdf] i { color: var(--icon-red) } /* #f90 - Image/Presentation */ -#listing [aria-label$=".html"] i, -#listing [aria-label$=".odg"] i, -#listing [aria-label$=".odp"] i, -#listing [aria-label$=".ppt"] i, -#listing [aria-label$=".pptx"] i, -#listing [aria-label$=".vue"] i, -#listing [aria-label$=".xcf"] i +.file-icons [aria-label$=".html"] i, +.file-icons [aria-label$=".odg"] i, +.file-icons [aria-label$=".odp"] i, +.file-icons [aria-label$=".ppt"] i, +.file-icons [aria-label$=".pptx"] i, +.file-icons [aria-label$=".vue"] i, +.file-icons [aria-label$=".xcf"] i { color: var(--icon-orange) } /* #ff0 - Various */ -#listing [aria-label$=".css"] i, -#listing [aria-label$=".js"] i, -#listing [aria-label$=".json"] i, -#listing [aria-label$=".zip"] i +.file-icons [aria-label$=".css"] i, +.file-icons [aria-label$=".js"] i, +.file-icons [aria-label$=".json"] i, +.file-icons [aria-label$=".zip"] i { color: var(--icon-yellow) } /* #0f0 - Spreadsheet/Google */ -#listing [aria-label$=".apk"] i, -#listing [aria-label$=".dex"] i, -#listing [aria-label$=".go"] i, -#listing [aria-label$=".ods"] i, -#listing [aria-label$=".xls"] i, -#listing [aria-label$=".xlsx"] i +.file-icons [aria-label$=".apk"] i, +.file-icons [aria-label$=".dex"] i, +.file-icons [aria-label$=".go"] i, +.file-icons [aria-label$=".ods"] i, +.file-icons [aria-label$=".xls"] i, +.file-icons [aria-label$=".xlsx"] i { color: var(--icon-green) } /* #00f - Document/Microsoft/Apple/Closed */ -#listing [aria-label$=".aac"] i, -#listing [aria-label$=".bat"] i, -#listing [aria-label$=".cab"] i, -#listing [aria-label$=".cs"] i, -#listing [aria-label$=".dmg"] i, -#listing [aria-label$=".doc"] i, -#listing [aria-label$=".docx"] i, -#listing [aria-label$=".emf"] i, -#listing [aria-label$=".exe"] i, -#listing [aria-label$=".ico"] i, -#listing [aria-label$=".mp2"] i, -#listing [aria-label$=".mp3"] i, -#listing [aria-label$=".mp4"] i, -#listing [aria-label$=".mpg"] i, -#listing [aria-label$=".msi"] i, -#listing [aria-label$=".odt"] i, -#listing [aria-label$=".ps1"] i, -#listing [aria-label$=".rtf"] i, -#listing [aria-label$=".vob"] i, -#listing [aria-label$=".wim"] i +.file-icons [aria-label$=".aac"] i, +.file-icons [aria-label$=".bat"] i, +.file-icons [aria-label$=".cab"] i, +.file-icons [aria-label$=".cs"] i, +.file-icons [aria-label$=".dmg"] i, +.file-icons [aria-label$=".doc"] i, +.file-icons [aria-label$=".docx"] i, +.file-icons [aria-label$=".emf"] i, +.file-icons [aria-label$=".exe"] i, +.file-icons [aria-label$=".ico"] i, +.file-icons [aria-label$=".mp2"] i, +.file-icons [aria-label$=".mp3"] i, +.file-icons [aria-label$=".mp4"] i, +.file-icons [aria-label$=".mpg"] i, +.file-icons [aria-label$=".msi"] i, +.file-icons [aria-label$=".odt"] i, +.file-icons [aria-label$=".ps1"] i, +.file-icons [aria-label$=".rtf"] i, +.file-icons [aria-label$=".vob"] i, +.file-icons [aria-label$=".wim"] i { color: var(--icon-blue) } /* #60f - Various */ -#listing [aria-label$=".iso"] i, -#listing [aria-label$=".php"] i, -#listing [aria-label$=".rar"] i +.file-icons [aria-label$=".iso"] i, +.file-icons [aria-label$=".php"] i, +.file-icons [aria-label$=".rar"] i { color: var(--icon-violet) } /* Overrides */ -#listing [data-dir=true] i { color: var(--icon-blue) } -#listing [data-dir=true] i::before { content: 'folder' } -#listing [aria-selected=true] i { color: var(--item-selected) } +.file-icons [data-dir=true] i { color: var(--icon-blue) } +.file-icons [data-dir=true] i::before { content: 'folder' } +.file-icons [aria-selected=true] i { color: var(--item-selected) } diff --git a/frontend/src/css/styles.css b/frontend/src/css/styles.css index 10427a9d..8b3b608c 100644 --- a/frontend/src/css/styles.css +++ b/frontend/src/css/styles.css @@ -11,6 +11,7 @@ @import "./header.css"; @import "./listing.css"; @import "./listing-icons.css"; +@import "./upload-files.css"; @import "./dashboard.css"; @import "./login.css"; diff --git a/frontend/src/css/upload-files.css b/frontend/src/css/upload-files.css new file mode 100644 index 00000000..ce864182 --- /dev/null +++ b/frontend/src/css/upload-files.css @@ -0,0 +1,61 @@ +.upload-files .card.floating { + left: auto; + top: auto; + margin: 0; + right: 0; + bottom: 0; + transform: none; +} + +.upload-files .file { + margin-bottom: 8px; +} + +.upload-files .file .file-name { + font-size: 1.1em; + display: flex; + align-items: center; +} + +.upload-files .file .file-name i { + margin-right: 5px; +} + +.upload-files .file .file-progress { + margin-top: 2px; + width: 100%; + height: 5px; +} + +.upload-files .file .file-progress div { + height: 100%; + background-color: #40c4ff; + width: 0; + transition: 0.2s ease width; + border-radius: 10px; +} + +.upload-files.closed .card-content { + display: none; + padding: 0em 1em 1em 1em; +} + +.upload-files .card .card-title { + display: flex; + align-items: center; + justify-content: center; + font-size: 0.8em; + padding: 1em 1em 0em; +} + +.upload-files.closed .card-title { + font-size: 0.7em; + padding: 0.5em 1em; +} + +@media (max-width: 450px) { + .upload-files .card.floating { + max-width: 100%; + width: 100%; + } +} diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 76a1d06a..182c6c08 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -100,7 +100,7 @@ "ru": "Русский", "sk": "Slovenčina", "svSE": "Swedish (Sweden)", - "tr" : "Türkçe", + "tr": "Türkçe", "ua": "Українська", "zhCN": "中文 (简体)", "zhTW": "中文 (繁體)" @@ -151,6 +151,7 @@ "show": "Show", "size": "Size", "upload": "Upload", + "uploadFiles": "Uploading {files} files...", "uploadMessage": "Select an option to upload.", "optionalPassword": "Optional password" }, diff --git a/frontend/src/store/getters.js b/frontend/src/store/getters.js index 8f1a8a4c..eee94fe2 100644 --- a/frontend/src/store/getters.js +++ b/frontend/src/store/getters.js @@ -8,8 +8,36 @@ const getters = { return 0; } + let totalSize = state.upload.sizes.reduce((a, b) => a + b, 0); + let sum = state.upload.progress.reduce((acc, val) => acc + val); - return Math.ceil((sum / state.upload.size) * 100); + return Math.ceil((sum / totalSize) * 100); + }, + filesInUploadCount: (state) => { + let total = + Object.keys(state.upload.uploads).length + state.upload.queue.length; + return total; + }, + filesInUpload: (state) => { + let files = []; + + for (let index in state.upload.uploads) { + let upload = state.upload.uploads[index]; + let id = upload.id; + let type = upload.type; + let name = decodeURIComponent(upload.path.replace(/^.*[\\/]/, "")); + let progress = state.upload.progress[id]; + let size = state.upload.sizes[id]; + + files.push({ + id, + name, + progress: Math.ceil((progress / size) * 100), + type, + }); + } + + return files.sort((a, b) => a.progress - b.progress); }, }; diff --git a/frontend/src/store/modules/upload.js b/frontend/src/store/modules/upload.js index b1a59bc8..4110cb33 100644 --- a/frontend/src/store/modules/upload.js +++ b/frontend/src/store/modules/upload.js @@ -7,7 +7,7 @@ const UPLOADS_LIMIT = 5; const state = { id: 0, - size: 0, + sizes: [], progress: [], queue: [], uploads: {}, @@ -19,12 +19,12 @@ const mutations = { }, reset: (state) => { state.id = 0; - state.size = 0; + state.sizes = []; state.progress = []; }, addJob: (state, item) => { state.queue.push(item); - state.size += item.file.size; + state.sizes[state.id] = item.file.size; state.id++; }, moveJob(state) { @@ -33,6 +33,7 @@ const mutations = { Vue.set(state.uploads, item.id, item); }, removeJob(state, id) { + Vue.delete(state.uploads, id); delete state.uploads[id]; }, }; diff --git a/frontend/src/utils/upload.js b/frontend/src/utils/upload.js index 1ab4bb65..e1222024 100644 --- a/frontend/src/utils/upload.js +++ b/frontend/src/utils/upload.js @@ -99,6 +99,15 @@ export function scanFiles(dt) { }); } +function detectType(mimetype) { + if (mimetype.startsWith("video")) return "video"; + if (mimetype.startsWith("audio")) return "audio"; + if (mimetype.startsWith("image")) return "image"; + if (mimetype.startsWith("pdf")) return "pdf"; + if (mimetype.startsWith("text")) return "text"; + return "blob"; +} + export function handleFiles(files, base, overwrite = false) { for (let i = 0; i < files.length; i++) { let id = store.state.upload.id; @@ -120,6 +129,7 @@ export function handleFiles(files, base, overwrite = false) { path, file, overwrite, + type: detectType(file.type), }; store.dispatch("upload/upload", item); diff --git a/frontend/src/views/Layout.vue b/frontend/src/views/Layout.vue index e9f306dc..4ccc441f 100644 --- a/frontend/src/views/Layout.vue +++ b/frontend/src/views/Layout.vue @@ -1,6 +1,6 @@ @@ -17,6 +18,7 @@ import { mapState, mapGetters } from "vuex"; import Sidebar from "@/components/Sidebar"; import Prompts from "@/components/prompts/Prompts"; import Shell from "@/components/Shell"; +import UploadFiles from "../components/prompts/UploadFiles"; import { enableExec } from "@/utils/constants"; export default { @@ -25,6 +27,7 @@ export default { Sidebar, Prompts, Shell, + UploadFiles, }, computed: { ...mapGetters(["isLogged", "progress"]), diff --git a/frontend/src/views/Share.vue b/frontend/src/views/Share.vue index c1b79e65..eefc6453 100644 --- a/frontend/src/views/Share.vue +++ b/frontend/src/views/Share.vue @@ -114,7 +114,7 @@
{{ $t("files.files") }}
-
+
-
+