Fix direct uploads and downloads, transfer bar UI
This commit is contained in:
		| @@ -10,6 +10,10 @@ | |||||||
|   <main> |   <main> | ||||||
|     <RouterView :path="path.pathList" :query="path.query" /> |     <RouterView :path="path.pathList" :query="path.query" /> | ||||||
|   </main> |   </main> | ||||||
|  |   <footer> | ||||||
|  |     <TransferBar :status=store.uprogress @cancel=store.cancelUploads class=upload /> | ||||||
|  |     <TransferBar :status=store.dprogress @cancel=store.cancelDownloads class=download /> | ||||||
|  |   </footer> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| <template> | <template> | ||||||
|   <SvgButton name="download" data-tooltip="Download" @click="download" /> |   <SvgButton name="download" data-tooltip="Download" @click="download" /> | ||||||
|   <TransferBar :status=progress @cancel=cancelDownloads /> |  | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| @@ -26,22 +25,22 @@ const status_init = { | |||||||
|   filepos: 0, |   filepos: 0, | ||||||
|   status: 'idle', |   status: 'idle', | ||||||
| } | } | ||||||
| const progress = reactive({...status_init}) | store.dprogress = {...status_init} | ||||||
| setInterval(() => { | setInterval(() => { | ||||||
|   if (Date.now() - progress.tlast > 3000) { |   if (Date.now() - store.dprogress.tlast > 3000) { | ||||||
|     // Reset |     // Reset | ||||||
|     progress.statbytes = 0 |     store.dprogress.statbytes = 0 | ||||||
|     progress.statdur = 1 |     store.dprogress.statdur = 1 | ||||||
|   } else { |   } else { | ||||||
|     // Running average by decay |     // Running average by decay | ||||||
|     progress.statbytes *= .9 |     store.dprogress.statbytes *= .9 | ||||||
|     progress.statdur *= .9 |     store.dprogress.statdur *= .9 | ||||||
|   } |   } | ||||||
| }, 100) | }, 100) | ||||||
| const statReset = () => { | const statReset = () => { | ||||||
|   Object.assign(progress, status_init) |   Object.assign(store.dprogress, status_init) | ||||||
|   progress.t0 = Date.now() |   store.dprogress.t0 = Date.now() | ||||||
|   progress.tlast = progress.t0 + 1 |   store.dprogress.tlast = store.dprogress.t0 + 1 | ||||||
| } | } | ||||||
| const cancelDownloads = () => { | const cancelDownloads = () => { | ||||||
|   location.reload()  // FIXME |   location.reload()  // FIXME | ||||||
| @@ -61,9 +60,9 @@ const filesystemdl = async (sel: SelectedItems, handle: FileSystemDirectoryHandl | |||||||
|   console.log('Downloading to filesystem', sel.recursive) |   console.log('Downloading to filesystem', sel.recursive) | ||||||
|   for (const [rel, full, doc] of sel.recursive) { |   for (const [rel, full, doc] of sel.recursive) { | ||||||
|     if (doc.dir) continue |     if (doc.dir) continue | ||||||
|     progress.files.push(rel) |     store.dprogress.files.push(rel) | ||||||
|     ++progress.filecount |     ++store.dprogress.filecount | ||||||
|     progress.total += doc.size |     store.dprogress.total += doc.size | ||||||
|   } |   } | ||||||
|   for (const [rel, full, doc] of sel.recursive) { |   for (const [rel, full, doc] of sel.recursive) { | ||||||
|     // Create any missing directories |     // Create any missing directories | ||||||
| @@ -73,6 +72,7 @@ const filesystemdl = async (sel: SelectedItems, handle: FileSystemDirectoryHandl | |||||||
|     } |     } | ||||||
|     const r = rel.slice(hdir.length) |     const r = rel.slice(hdir.length) | ||||||
|     for (const dir of r.split('/').slice(0, doc.dir ? undefined : -1)) { |     for (const dir of r.split('/').slice(0, doc.dir ? undefined : -1)) { | ||||||
|  |       if (!dir) continue | ||||||
|       hdir += `${dir}/` |       hdir += `${dir}/` | ||||||
|       try { |       try { | ||||||
|         h = await h.getDirectoryHandle(dir.normalize('NFC'), { create: true }) |         h = await h.getDirectoryHandle(dir.normalize('NFC'), { create: true }) | ||||||
| @@ -101,22 +101,22 @@ const filesystemdl = async (sel: SelectedItems, handle: FileSystemDirectoryHandl | |||||||
|       throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`) |       throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`) | ||||||
|     } |     } | ||||||
|     if (res.body) { |     if (res.body) { | ||||||
|       ++progress.fileidx |       ++store.dprogress.fileidx | ||||||
|       const reader = res.body.getReader() |       const reader = res.body.getReader() | ||||||
|       await writable.truncate(0) |       await writable.truncate(0) | ||||||
|       store.error = "Direct download." |       store.error = "Direct download." | ||||||
|       progress.tlast = Date.now() |       store.dprogress.tlast = Date.now() | ||||||
|       while (true) { |       while (true) { | ||||||
|         const { value, done } = await reader.read() |         const { value, done } = await reader.read() | ||||||
|         if (done) break |         if (done) break | ||||||
|         await writable.write(value) |         await writable.write(value) | ||||||
|         const now = Date.now() |         const now = Date.now() | ||||||
|         const size = value.byteLength |         const size = value.byteLength | ||||||
|         progress.xfer += size |         store.dprogress.xfer += size | ||||||
|         progress.filepos += size |         store.dprogress.filepos += size | ||||||
|         progress.statbytes += size |         store.dprogress.statbytes += size | ||||||
|         progress.statdur += now - progress.tlast |         store.dprogress.statdur += now - store.dprogress.tlast | ||||||
|         progress.tlast = now |         store.dprogress.tlast = now | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     await writable.close() |     await writable.close() | ||||||
|   | |||||||
| @@ -57,13 +57,12 @@ const speeddisp = computed(() => speed.value ? speed.value.toFixed(speed.value < | |||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   color: var(--primary-color); |   color: var(--primary-color); | ||||||
|   position: fixed; |   width: 100%; | ||||||
|   left: 0; |  | ||||||
|   bottom: 0; |  | ||||||
|   width: 100vw; |  | ||||||
| } | } | ||||||
| .statustext { | .statustext { | ||||||
|   display: flex; |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   margin: 0 .5em; | ||||||
|   padding: 0.5rem 0; |   padding: 0.5rem 0; | ||||||
| } | } | ||||||
| span { | span { | ||||||
| @@ -84,4 +83,12 @@ span { | |||||||
| .position { min-width: 4em } | .position { min-width: 4em } | ||||||
| .speed { min-width: 4em } | .speed { min-width: 4em } | ||||||
|  |  | ||||||
|  | .upload .statustext::before { | ||||||
|  |   font-size: 1.5em; | ||||||
|  |   content: '🔺' | ||||||
|  | } | ||||||
|  | .download .statustext::before { | ||||||
|  |   font-size: 1.5em; | ||||||
|  |   content: '🔻' | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -1,3 +1,12 @@ | |||||||
|  | <template> | ||||||
|  |   <template> | ||||||
|  |     <input ref="fileInput" @change="uploadHandler" type="file" multiple> | ||||||
|  |     <input ref="folderInput" @change="uploadHandler" type="file" webkitdirectory> | ||||||
|  |   </template> | ||||||
|  |   <SvgButton name="add-file" data-tooltip="Upload files" @click="fileInput.click()" /> | ||||||
|  |   <SvgButton name="add-folder" data-tooltip="Upload folder" @click="folderInput.click()" /> | ||||||
|  | </template> | ||||||
|  |  | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { connect, uploadUrl } from '@/repositories/WS'; | import { connect, uploadUrl } from '@/repositories/WS'; | ||||||
| import { useMainStore } from '@/stores/main' | import { useMainStore } from '@/stores/main' | ||||||
| @@ -108,50 +117,50 @@ const uprogress_init = { | |||||||
|   filepos: 0, |   filepos: 0, | ||||||
|   status: 'idle', |   status: 'idle', | ||||||
| } | } | ||||||
| const uprogress = reactive({...uprogress_init}) | store.uprogress = {...uprogress_init} | ||||||
| setInterval(() => { | setInterval(() => { | ||||||
|   if (Date.now() - uprogress.tlast > 3000) { |   if (Date.now() - store.uprogress.tlast > 3000) { | ||||||
|     // Reset |     // Reset | ||||||
|     uprogress.statbytes = 0 |     store.uprogress.statbytes = 0 | ||||||
|     uprogress.statdur = 1 |     store.uprogress.statdur = 1 | ||||||
|   } else { |   } else { | ||||||
|     // Running average by decay |     // Running average by decay | ||||||
|     uprogress.statbytes *= .9 |     store.uprogress.statbytes *= .9 | ||||||
|     uprogress.statdur *= .9 |     store.uprogress.statdur *= .9 | ||||||
|   } |   } | ||||||
| }, 100) | }, 100) | ||||||
| const statUpdate = ({name, size, start, end}: {name: string, size: number, start: number, end: number}) => { | const statUpdate = ({name, size, start, end}: {name: string, size: number, start: number, end: number}) => { | ||||||
|   if (name !== uprogress.filename) return  // If stats have been reset |   if (name !== store.uprogress.filename) return  // If stats have been reset | ||||||
|   const now = Date.now() |   const now = Date.now() | ||||||
|   uprogress.xfer = uprogress.filestart + end |   store.uprogress.xfer = store.uprogress.filestart + end | ||||||
|   uprogress.filepos = end |   store.uprogress.filepos = end | ||||||
|   uprogress.statbytes += end - start |   store.uprogress.statbytes += end - start | ||||||
|   uprogress.statdur += now - uprogress.tlast |   store.uprogress.statdur += now - store.uprogress.tlast | ||||||
|   uprogress.tlast = now |   store.uprogress.tlast = now | ||||||
|   // File finished? |   // File finished? | ||||||
|   if (end === size) { |   if (end === size) { | ||||||
|     uprogress.filestart += size |     store.uprogress.filestart += size | ||||||
|     statNextFile() |     statNextFile() | ||||||
|     if (++uprogress.fileidx >= uprogress.filecount) statReset() |     if (++store.uprogress.fileidx >= store.uprogress.filecount) statReset() | ||||||
|   } |   } | ||||||
| } | } | ||||||
| const statNextFile = () => { | const statNextFile = () => { | ||||||
|   const f = uprogress.files.shift() |   const f = store.uprogress.files.shift() | ||||||
|   if (!f) return statReset() |   if (!f) return statReset() | ||||||
|   uprogress.filepos = 0 |   store.uprogress.filepos = 0 | ||||||
|   uprogress.filesize = f.file.size |   store.uprogress.filesize = f.file.size | ||||||
|   uprogress.filename = f.cloudName |   store.uprogress.filename = f.cloudName | ||||||
| } | } | ||||||
| const statReset = () => { | const statReset = () => { | ||||||
|   Object.assign(uprogress, uprogress_init) |   Object.assign(store.uprogress, uprogress_init) | ||||||
|   uprogress.t0 = Date.now() |   store.uprogress.t0 = Date.now() | ||||||
|   uprogress.tlast = uprogress.t0 + 1 |   store.uprogress.tlast = store.uprogress.t0 + 1 | ||||||
| } | } | ||||||
| const statsAdd = (f: CloudFile[]) => { | const statsAdd = (f: CloudFile[]) => { | ||||||
|   if (uprogress.files.length === 0) statReset() |   if (store.uprogress.files.length === 0) statReset() | ||||||
|   uprogress.total += f.reduce((a, b) => a + b.file.size, 0) |   store.uprogress.total += f.reduce((a, b) => a + b.file.size, 0) | ||||||
|   uprogress.filecount += f.length |   store.uprogress.filecount += f.length | ||||||
|   uprogress.files = [...uprogress.files, ...f] |   store.uprogress.files = [...store.uprogress.files, ...f] | ||||||
|   statNextFile() |   statNextFile() | ||||||
| } | } | ||||||
| let upqueue = [] as CloudFile[] | let upqueue = [] as CloudFile[] | ||||||
| @@ -181,7 +190,7 @@ const WSCreate = async () => await new Promise<WebSocket>(resolve => { | |||||||
|   // @ts-ignore |   // @ts-ignore | ||||||
|   ws.sendData = async (data: any) => { |   ws.sendData = async (data: any) => { | ||||||
|     // Wait until the WS is ready to send another message |     // Wait until the WS is ready to send another message | ||||||
|     uprogress.status = "uploading" |     store.uprogress.status = "uploading" | ||||||
|     await new Promise(resolve => { |     await new Promise(resolve => { | ||||||
|       const t = setInterval(() => { |       const t = setInterval(() => { | ||||||
|         if (ws.bufferedAmount > 1<<20) return |         if (ws.bufferedAmount > 1<<20) return | ||||||
| @@ -189,7 +198,7 @@ const WSCreate = async () => await new Promise<WebSocket>(resolve => { | |||||||
|         clearInterval(t) |         clearInterval(t) | ||||||
|       }, 1) |       }, 1) | ||||||
|     }) |     }) | ||||||
|     uprogress.status = "processing" |     store.uprogress.status = "processing" | ||||||
|     ws.send(data) |     ws.send(data) | ||||||
|   } |   } | ||||||
| }) | }) | ||||||
| @@ -210,7 +219,7 @@ const worker = async () => { | |||||||
|     if (f.cloudPos === f.file.size) upqueue.shift() |     if (f.cloudPos === f.file.size) upqueue.shift() | ||||||
|   } |   } | ||||||
|   if (upqueue.length) startWorker() |   if (upqueue.length) startWorker() | ||||||
|   uprogress.status = "idle" |   store.uprogress.status = "idle" | ||||||
|   workerRunning = false |   workerRunning = false | ||||||
| } | } | ||||||
| let workerRunning: any = false | let workerRunning: any = false | ||||||
| @@ -233,12 +242,3 @@ onUnmounted(() => { | |||||||
|   removeEventListener('drop', uploadHandler) |   removeEventListener('drop', uploadHandler) | ||||||
| }) | }) | ||||||
| </script> | </script> | ||||||
| <template> |  | ||||||
|   <template> |  | ||||||
|     <input ref="fileInput" @change="uploadHandler" type="file" multiple> |  | ||||||
|     <input ref="folderInput" @change="uploadHandler" type="file" webkitdirectory> |  | ||||||
|   </template> |  | ||||||
|   <SvgButton name="add-file" data-tooltip="Upload files" @click="fileInput.click()" /> |  | ||||||
|   <SvgButton name="add-folder" data-tooltip="Upload folder" @click="folderInput.click()" /> |  | ||||||
|   <TransferBar :status=uprogress @cancel=cancelUploads /> |  | ||||||
| </template> |  | ||||||
|   | |||||||
| @@ -19,6 +19,8 @@ export const useMainStore = defineStore({ | |||||||
|     cursor: '' as string, |     cursor: '' as string, | ||||||
|     server: {} as Record<string, any>, |     server: {} as Record<string, any>, | ||||||
|     dialog: '' as '' | 'login' | 'settings', |     dialog: '' as '' | 'login' | 'settings', | ||||||
|  |     uprogress: {} as any, | ||||||
|  |     dprogress: {} as any, | ||||||
|     prefs: { |     prefs: { | ||||||
|       gallery: false, |       gallery: false, | ||||||
|       sortListing: '' as SortOrder, |       sortListing: '' as SortOrder, | ||||||
| @@ -89,7 +91,13 @@ export const useMainStore = defineStore({ | |||||||
|     }, |     }, | ||||||
|     focusBreadcrumb() { |     focusBreadcrumb() { | ||||||
|       (document.querySelector('.breadcrumb') as HTMLAnchorElement).focus() |       (document.querySelector('.breadcrumb') as HTMLAnchorElement).focus() | ||||||
|     } |     }, | ||||||
|  |     cancelDownloads() { | ||||||
|  |       location.reload()  // FIXME | ||||||
|  |     }, | ||||||
|  |     cancelUploads() { | ||||||
|  |       location.reload()  // FIXME | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|   getters: { |   getters: { | ||||||
|     sortOrder(): SortOrder { return this.query ? this.prefs.sortFiltered : this.prefs.sortListing }, |     sortOrder(): SortOrder { return this.query ? this.prefs.sortFiltered : this.prefs.sortListing }, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Leo Vasanko
					Leo Vasanko