From bac8fd8d4e3d877f9690a104ca7f3ad03ef0a2d9 Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Tue, 3 May 2022 00:14:14 +0200 Subject: [PATCH 01/17] feat: init upload from global gcode file upload Signed-off-by: Stefan Dej --- src/App.vue | 3 + src/components/TheFullscreenUpload.vue | 151 ++++++++++++++++++ src/components/panels/GcodefilesPanel.vue | 119 ++------------ .../panels/Machine/ConfigFilesPanel.vue | 20 ++- src/store/gui/index.ts | 1 + src/store/gui/types.ts | 1 + 6 files changed, 183 insertions(+), 112 deletions(-) create mode 100644 src/components/TheFullscreenUpload.vue diff --git a/src/App.vue b/src/App.vue index 352c6207d..57c1c7138 100644 --- a/src/App.vue +++ b/src/App.vue @@ -42,6 +42,7 @@ + @@ -57,6 +58,7 @@ import TheSelectPrinterDialog from '@/components/TheSelectPrinterDialog.vue' import TheEditor from '@/components/TheEditor.vue' import { panelToolbarHeight, topbarHeight, navigationItemHeight } from '@/store/variables' import TheTimelapseRenderingSnackbar from '@/components/TheTimelapseRenderingSnackbar.vue' +import TheFullscreenUpload from '@/components/TheFullscreenUpload.vue' @Component({ components: { @@ -67,6 +69,7 @@ import TheTimelapseRenderingSnackbar from '@/components/TheTimelapseRenderingSna TheUpdateDialog, TheTopbar, TheSidebar, + TheFullscreenUpload, }, metaInfo() { const title = this.$store.getters['getTitle'] diff --git a/src/components/TheFullscreenUpload.vue b/src/components/TheFullscreenUpload.vue new file mode 100644 index 000000000..ec181830e --- /dev/null +++ b/src/components/TheFullscreenUpload.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/src/components/panels/GcodefilesPanel.vue b/src/components/panels/GcodefilesPanel.vue index ebb118fcb..63a185ac4 100644 --- a/src/components/panels/GcodefilesPanel.vue +++ b/src/components/panels/GcodefilesPanel.vue @@ -7,10 +7,6 @@ cursor: pointer; } -.fileupload-card { - position: relative; -} - .file-list--select-td { width: 20px; } @@ -19,36 +15,6 @@ padding-right: 0 !important; } -.file-list__dragzone { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - height: 100%; - z-index: 9999999999; - background-color: rgba(0, 0, 0, 0.5); - transition: visibility 175ms, opacity 175ms; - font: bold 42px Oswald, DejaVu Sans, Tahoma, sans-serif; -} - -.file-list__dragzone:before { - display: block; - content: ' '; - position: absolute; - top: 15px; - right: 15px; - bottom: 15px; - left: 15px; - border: 3px dashed white; - border-radius: 15px; -} - -.file-list__dragzone .textnode { - text-align: center; - transition: font-size 175ms; -} - .v-chip.minimum-chip { padding: 0; min-width: 24px; @@ -65,7 +31,7 @@ -
-
{{ $t('Files.DropFilesToAddGcode') }}
-
@@ -641,7 +602,6 @@ import { } from '@mdi/js' interface draggingFile { - status: boolean item: FileStateGcodefile } @@ -740,7 +700,6 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { opacity: 0, } private draggingFile: draggingFile = { - status: false, item: { isDirectory: false, filename: '', @@ -877,6 +836,14 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { private input_rules = [(value: string) => value.indexOf(' ') === -1 || 'Name contains spaces!'] + get blockFileUpload() { + return this.$store.state.gui.view.blockFileUpload ?? false + } + + set blockFileUpload(newVal) { + this.$store.dispatch('gui/saveSettingWithoutUpload', { name: 'view.blockFileUpload', value: newVal }) + } + get currentPath() { const path = this.$store.state.gui.view.gcodefiles.currentPath if (path === 'gcodes') return '' @@ -1130,64 +1097,6 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { return this.$store.getters['server/history/getPrintStatusIconColor'](status) } - dragOverUpload(e: Event) { - if (!this.draggingFile.status) { - e.preventDefault() - e.stopPropagation() - - this.dropzone.visibility = 'visible' - this.dropzone.opacity = 1 - } - } - - dragLeaveUpload(e: Event) { - if (!this.draggingFile.status) { - e.preventDefault() - e.stopPropagation() - - this.dropzone.visibility = 'hidden' - this.dropzone.opacity = 0 - } - } - - async dragDropUpload(e: any) { - if (!this.draggingFile.status) { - e.preventDefault() - - this.dropzone.visibility = 'hidden' - this.dropzone.opacity = 0 - - if (e.dataTransfer.files.length) { - const files = [...e.dataTransfer.files].filter((file: File) => { - const format = file.name.slice(file.name.lastIndexOf('.')) - - if (!validGcodeExtensions.includes(format)) { - this.$toast.error(this.$t('Files.WrongFileUploaded', { filename: file.name }).toString()) - - return false - } - - return true - }) - - this.$store.dispatch('socket/addLoading', { name: 'gcodeUpload' }) - let successFiles = [] - this.uploadSnackbar.number = 0 - this.uploadSnackbar.max = files.length - for (const file of files) { - this.uploadSnackbar.number++ - const result = await this.doUploadFile(file) - successFiles.push(result) - } - - this.$store.dispatch('socket/removeLoading', { name: 'gcodeUpload' }) - for (const file of successFiles) { - this.$toast.success(this.$t('Files.SuccessfullyUploaded', { filename: file }).toString()) - } - } - } - } - doUploadFile(file: File) { let formData = new FormData() let filename = file.name @@ -1235,7 +1144,7 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { } dragOverFilelist(e: any, row: any) { - if (this.draggingFile.status) { + if (this.blockFileUpload) { e.preventDefault() //e.stopPropagation() @@ -1246,7 +1155,7 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { } dragLeaveFilelist(e: any) { - if (this.draggingFile.status) { + if (this.blockFileUpload) { e.preventDefault() e.stopPropagation() @@ -1255,7 +1164,7 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { } async dragDropFilelist(e: any, row: any) { - if (this.draggingFile.status) { + if (this.blockFileUpload) { e.preventDefault() e.target.parentElement.style.backgroundColor = 'transparent' @@ -1521,13 +1430,13 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { dragFile(e: Event, item: FileStateGcodefile) { e.preventDefault() - this.draggingFile.status = true + this.blockFileUpload = true this.draggingFile.item = item } dragendFile(e: Event) { e.preventDefault() - this.draggingFile.status = false + this.blockFileUpload = false this.draggingFile.item = { isDirectory: false, filename: '', diff --git a/src/components/panels/Machine/ConfigFilesPanel.vue b/src/components/panels/Machine/ConfigFilesPanel.vue index 26c1f1207..20dd69271 100644 --- a/src/components/panels/Machine/ConfigFilesPanel.vue +++ b/src/components/panels/Machine/ConfigFilesPanel.vue @@ -463,7 +463,6 @@ interface uploadSnackbar { } interface draggingFile { - status: boolean item: FileStateFile } @@ -571,7 +570,6 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) { }, } private draggingFile: draggingFile = { - status: false, item: { isDirectory: false, filename: '', @@ -580,6 +578,14 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) { }, } + get blockFileUpload() { + return this.$store.state.gui.view.blockFileUpload ?? false + } + + set blockFileUpload(newVal) { + this.$store.dispatch('gui/saveSettingWithoutUpload', { name: 'view.blockFileUpload', value: newVal }) + } + get toolbarButtons() { return [ { @@ -1008,13 +1014,13 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) { dragFile(e: Event, item: FileStateFile) { e.preventDefault() - this.draggingFile.status = true + this.blockFileUpload = true this.draggingFile.item = item } dragendFile(e: Event) { e.preventDefault() - this.draggingFile.status = false + this.blockFileUpload = false this.draggingFile.item = { isDirectory: false, filename: '', @@ -1024,7 +1030,7 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) { } dragOverFilelist(e: any, row: any) { - if (this.draggingFile.status) { + if (this.blockFileUpload) { e.preventDefault() //e.stopPropagation() @@ -1033,7 +1039,7 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) { } dragLeaveFilelist(e: any) { - if (this.draggingFile.status) { + if (this.blockFileUpload) { e.preventDefault() e.stopPropagation() @@ -1042,7 +1048,7 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) { } async dragDropFilelist(e: any, row: any) { - if (this.draggingFile.status) { + if (this.blockFileUpload) { e.preventDefault() e.target.parentElement.style.backgroundColor = 'transparent' diff --git a/src/store/gui/index.ts b/src/store/gui/index.ts index 0ecb5d574..e8360d0e2 100644 --- a/src/store/gui/index.ts +++ b/src/store/gui/index.ts @@ -150,6 +150,7 @@ export const getDefaultState = (): GuiState => { navigationStyle: 'iconsAndText', }, view: { + blockFileUpload: false, configfiles: { countPerPage: 10, sortBy: 'filename', diff --git a/src/store/gui/types.ts b/src/store/gui/types.ts index 8622b03cf..0e66ce538 100644 --- a/src/store/gui/types.ts +++ b/src/store/gui/types.ts @@ -103,6 +103,7 @@ export interface GuiState { navigationStyle: 'iconsAndText' | 'iconsOnly' } view: { + blockFileUpload: boolean configfiles: { countPerPage: number sortBy: string From bd2d526ca7180c3844940dd42f4fc00b618d927c Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Thu, 5 May 2022 23:57:45 +0200 Subject: [PATCH 02/17] feat: global fileupload & fileupload status snackbar Signed-off-by: Stefan Dej --- src/App.vue | 3 + src/components/TheFullscreenUpload.vue | 93 +++++++-------- src/components/TheUploadSnackbar.vue | 57 +++++++++ src/components/panels/GcodefilesPanel.vue | 138 +++++----------------- src/store/files/actions.ts | 72 +++++++++++ src/store/files/index.ts | 9 ++ src/store/files/mutations.ts | 39 ++++++ src/store/files/types.ts | 11 ++ src/store/gui/index.ts | 2 +- 9 files changed, 266 insertions(+), 158 deletions(-) create mode 100644 src/components/TheUploadSnackbar.vue diff --git a/src/App.vue b/src/App.vue index 57c1c7138..886b03b9e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -43,6 +43,7 @@ + @@ -59,6 +60,7 @@ import TheEditor from '@/components/TheEditor.vue' import { panelToolbarHeight, topbarHeight, navigationItemHeight } from '@/store/variables' import TheTimelapseRenderingSnackbar from '@/components/TheTimelapseRenderingSnackbar.vue' import TheFullscreenUpload from '@/components/TheFullscreenUpload.vue' +import TheUploadSnackbar from '@/components/TheUploadSnackbar.vue' @Component({ components: { @@ -70,6 +72,7 @@ import TheFullscreenUpload from '@/components/TheFullscreenUpload.vue' TheTopbar, TheSidebar, TheFullscreenUpload, + TheUploadSnackbar, }, metaInfo() { const title = this.$store.getters['getTitle'] diff --git a/src/components/TheFullscreenUpload.vue b/src/components/TheFullscreenUpload.vue index ec181830e..4c243e4dc 100644 --- a/src/components/TheFullscreenUpload.vue +++ b/src/components/TheFullscreenUpload.vue @@ -1,11 +1,5 @@ @@ -14,15 +8,12 @@ import { Mixins } from 'vue-property-decorator' import BaseMixin from '@/components/mixins/base' import Component from 'vue-class-component' +import { validGcodeExtensions } from '@/store/variables' @Component export default class TheFullscreenUpload extends Mixins(BaseMixin) { private visable = false - get blockFileUpload() { - return this.$store.state.gui.view.blockFileUpload ?? false - } - get dropzoneClasses() { return { 'fullscreen-upload__dragzone': true, @@ -30,20 +21,28 @@ export default class TheFullscreenUpload extends Mixins(BaseMixin) { } } - onDrop(e: any) { - e.preventDefault() - this.hideDropZone() - window.console.log('handleDrop', e, e.dataTransfer.files) + get currentRoute() { + return this.$route.path ?? '' + } + + get currentPathGcodes() { + return this.$store.state.gui.view.gcodefiles.currentPath ?? '' + } + + get currentPathConfig() { + return this.$store.state.gui.view.configfiles.currentPath ?? '' } mounted() { window.addEventListener('dragenter', this.onDragOverWindow) window.addEventListener('dragover', this.onDragOverWindow) + window.addEventListener('dragleave', this.onDragLeaveWindow) } beforeDestroy() { window.removeEventListener('dragenter', this.onDragOverWindow) window.removeEventListener('dragover', this.onDragOverWindow) + window.removeEventListener('dragleave', this.onDragLeaveWindow) } showDropZone() { @@ -55,59 +54,53 @@ export default class TheFullscreenUpload extends Mixins(BaseMixin) { } onDragOverWindow(e: any) { - if (this.blockFileUpload) return + const type = e.dataTransfer?.types[0] ?? '' + if (type !== 'Files') return + e.preventDefault() if (this.visable) return this.showDropZone() } - onDragEnter(e: any) { - e.dataTransfer.dropEffect = 'copy' + onDragLeaveWindow(e: any) { e.preventDefault() + this.hideDropZone() } - onDragOver(e: any) { + async onDrop(e: any) { e.preventDefault() - } - - /*async dragDropUpload(e: any) { - if (!this.blockFileUpload) { - e.preventDefault() - - this.dropzone.visibility = 'hidden' - this.dropzone.opacity = 0 + this.hideDropZone() - if (e.dataTransfer.files.length) { - const files = [...e.dataTransfer.files].filter((file: File) => { - const format = file.name.slice(file.name.lastIndexOf('.')) + if (e.dataTransfer?.files?.length) { + const files = [...e.dataTransfer.files] - if (!validGcodeExtensions.includes(format)) { - this.$toast.error(this.$t('Files.WrongFileUploaded', { filename: file.name }).toString()) + await this.$store.dispatch('socket/addLoading', { name: 'gcodeUpload' }) + let successFiles = [] + await this.$store.dispatch('files/uploadSetCurrentNumber', 0) + await this.$store.dispatch('files/uploadSetMaxNumber', files.length) - return false - } + for (const file of files) { + const extensionPos = file.name.lastIndexOf('.') + const extension = file.name.slice(extensionPos) + const isGcode = validGcodeExtensions.includes(extension) - return true - }) + let path = '' + if (this.currentRoute === '/files' && isGcode) path = this.currentPathGcodes + else if (this.currentRoute === '/config' && !isGcode) path = this.currentPathConfig - this.$store.dispatch('socket/addLoading', { name: 'gcodeUpload' }) - let successFiles = [] - this.uploadSnackbar.number = 0 - this.uploadSnackbar.max = files.length - for (const file of files) { - this.uploadSnackbar.number++ - const result = await this.doUploadFile(file) - successFiles.push(result) - } + const root = isGcode ? 'gcodes' : 'config' + await this.$store.dispatch('files/uploadIncrementCurrentNumber') + const result = await this.$store.dispatch('files/uploadFile', { file, path, root }) + successFiles.push(result) + } - this.$store.dispatch('socket/removeLoading', { name: 'gcodeUpload' }) - for (const file of successFiles) { - this.$toast.success(this.$t('Files.SuccessfullyUploaded', { filename: file }).toString()) - } + await this.$store.dispatch('socket/removeLoading', { name: 'gcodeUpload' }) + for (const file of successFiles) { + this.$toast.success(this.$t('Files.SuccessfullyUploaded', { filename: file }).toString()) } } - }*/ + } } diff --git a/src/components/TheUploadSnackbar.vue b/src/components/TheUploadSnackbar.vue new file mode 100644 index 000000000..079c9dea3 --- /dev/null +++ b/src/components/TheUploadSnackbar.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/components/panels/GcodefilesPanel.vue b/src/components/panels/GcodefilesPanel.vue index 63a185ac4..c836ee14b 100644 --- a/src/components/panels/GcodefilesPanel.vue +++ b/src/components/panels/GcodefilesPanel.vue @@ -340,21 +340,6 @@ - - - ({{ uploadSnackbar.number }}/{{ uploadSnackbar.max }}) - - {{ $t('Files.Uploading') + ' ' + uploadSnackbar.filename }} -
- {{ Math.round(uploadSnackbar.percent) }} % @ {{ formatFilesize(Math.round(uploadSnackbar.speed)) }}/s -
- - -
value.indexOf(' ') === -1 || 'Name contains spaces!'] - get blockFileUpload() { - return this.$store.state.gui.view.blockFileUpload ?? false - } - - set blockFileUpload(newVal) { - this.$store.dispatch('gui/saveSettingWithoutUpload', { name: 'view.blockFileUpload', value: newVal }) - } - get currentPath() { const path = this.$store.state.gui.view.gcodefiles.currentPath if (path === 'gcodes') return '' @@ -1097,108 +1074,57 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { return this.$store.getters['server/history/getPrintStatusIconColor'](status) } - doUploadFile(file: File) { - let formData = new FormData() - let filename = file.name - - this.uploadSnackbar.filename = filename - this.uploadSnackbar.status = true - this.uploadSnackbar.percent = 0 - this.uploadSnackbar.speed = 0 - this.uploadSnackbar.lastProgress.loaded = 0 - this.uploadSnackbar.lastProgress.time = 0 - - formData.append('file', file, this.currentPath + '/' + filename) - - return new Promise((resolve) => { - this.uploadSnackbar.cancelTokenSource = axios.CancelToken.source() - axios - .post(this.apiUrl + '/server/files/upload', formData, { - cancelToken: this.uploadSnackbar.cancelTokenSource.token, - headers: { 'Content-Type': 'multipart/form-data' }, - onUploadProgress: (progressEvent: any) => { - this.uploadSnackbar.percent = (progressEvent.loaded * 100) / progressEvent.total - if (this.uploadSnackbar.lastProgress.time) { - const time = progressEvent.timeStamp - this.uploadSnackbar.lastProgress.time - const data = progressEvent.loaded - this.uploadSnackbar.lastProgress.loaded - - if (time) this.uploadSnackbar.speed = data / (time / 1000) - } - - this.uploadSnackbar.lastProgress.time = progressEvent.timeStamp - this.uploadSnackbar.lastProgress.loaded = progressEvent.loaded - this.uploadSnackbar.total = progressEvent.total - }, - }) - .then((result: any) => { - const filename = result.data.item.path.slice(result.data.item.path.indexOf('/') + 1) - this.uploadSnackbar.status = false - resolve(filename) - }) - .catch(() => { - this.uploadSnackbar.status = false - this.$store.dispatch('socket/removeLoading', { name: 'gcodeUpload' }) - this.$toast.error('Cannot upload the file!') - }) - }) - } - dragOverFilelist(e: any, row: any) { - if (this.blockFileUpload) { - e.preventDefault() - //e.stopPropagation() + e.preventDefault() - if (row.isDirectory) { - e.target.parentElement.style.backgroundColor = '#43A04720' - } - } + if (row.isDirectory) e.target.parentElement.style.backgroundColor = '#43A04720' } dragLeaveFilelist(e: any) { - if (this.blockFileUpload) { - e.preventDefault() - e.stopPropagation() + e.preventDefault() + e.stopPropagation() - e.target.parentElement.style.backgroundColor = 'transparent' - } + e.target.parentElement.style.backgroundColor = 'transparent' } async dragDropFilelist(e: any, row: any) { - if (this.blockFileUpload) { - e.preventDefault() - e.target.parentElement.style.backgroundColor = 'transparent' + e.preventDefault() + e.target.parentElement.style.backgroundColor = 'transparent' - let dest = '' - if (row.filename === '..') { - dest = - this.currentPath.substring(0, this.currentPath.lastIndexOf('/') + 1) + - this.draggingFile.item.filename - } else dest = this.currentPath + '/' + row.filename + '/' + this.draggingFile.item.filename + let dest = '' + if (row.filename === '..') { + dest = + this.currentPath.substring(0, this.currentPath.lastIndexOf('/') + 1) + this.draggingFile.item.filename + } else dest = this.currentPath + '/' + row.filename + '/' + this.draggingFile.item.filename - this.$socket.emit( - 'server.files.move', - { - source: 'gcodes' + this.currentPath + '/' + this.draggingFile.item.filename, - dest: 'gcodes' + dest, - }, - { action: 'files/getMove' } - ) - } + this.$socket.emit( + 'server.files.move', + { + source: 'gcodes' + this.currentPath + '/' + this.draggingFile.item.filename, + dest: 'gcodes' + dest, + }, + { action: 'files/getMove' } + ) } async uploadFile() { if (this.$refs.fileUpload.files?.length) { - this.$store.dispatch('socket/addLoading', { name: 'gcodeUpload' }) + await this.$store.dispatch('socket/addLoading', { name: 'gcodeUpload' }) let successFiles = [] - this.uploadSnackbar.number = 0 - this.uploadSnackbar.max = this.$refs.fileUpload.files.length + await this.$store.dispatch('files/uploadSetCurrentNumber', 0) + await this.$store.dispatch('files/uploadSetMaxNumber', this.$refs.fileUpload.files.length) for (const file of this.$refs.fileUpload.files) { - this.uploadSnackbar.number++ - const result = await this.doUploadFile(file) + await this.$store.dispatch('files/uploadIncrementCurrentNumber') + const path = this.currentPath.slice(0, 1) === '/' ? this.currentPath.slice(1) : this.currentPath + const result = await this.$store.dispatch('files/uploadFile', { + file, + path, + root: 'gcodes', + }) successFiles.push(result) } - this.$store.dispatch('socket/removeLoading', { name: 'gcodeUpload' }) + await this.$store.dispatch('socket/removeLoading', { name: 'gcodeUpload' }) for (const file of successFiles) { this.$toast.success(this.$t('Files.SuccessfullyUploaded', { filename: file }).toString()) } @@ -1430,13 +1356,11 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { dragFile(e: Event, item: FileStateGcodefile) { e.preventDefault() - this.blockFileUpload = true this.draggingFile.item = item } dragendFile(e: Event) { e.preventDefault() - this.blockFileUpload = false this.draggingFile.item = { isDirectory: false, filename: '', diff --git a/src/store/files/actions.ts b/src/store/files/actions.ts index 1e1ce919d..6b09a5956 100644 --- a/src/store/files/actions.ts +++ b/src/store/files/actions.ts @@ -10,6 +10,7 @@ import { import { RootState } from '@/store/types' import i18n from '@/plugins/i18n' import { validGcodeExtensions } from '@/store/variables' +import axios from 'axios' export const actions: ActionTree = { reset({ commit }) { @@ -258,4 +259,75 @@ export const actions: ActionTree = { Vue.$toast.success(i18n.t('Files.SuccessfullyDeleted', { filename: delPath })) } }, + + async uploadFile( + { dispatch, commit, rootGetters }, + payload: { file: File; path: string; root: 'gcodes' | 'config' } + ) { + const apiUrl = rootGetters['socket/getUrl'] + const formData = new FormData() + formData.append('file', payload.file, payload.file.name) + formData.append('root', payload.root) + formData.append('path', payload.path) + const cancelTokenSource = axios.CancelToken.source() + + await commit('uploadClearState') + await commit('uploadSetCancelTokenSource', cancelTokenSource) + await commit('uploadSetFilename', payload.file.name) + await commit('uploadSetShow', true) + + let lastTime = 0 + let lastLoaded = 0 + + return new Promise((resolve) => { + axios + .post(apiUrl + '/server/files/upload', formData, { + cancelToken: cancelTokenSource.token, + headers: { 'Content-Type': 'multipart/form-data' }, + onUploadProgress: (progressEvent: any) => { + const percent = (progressEvent.loaded * 100) / progressEvent.total + commit('uploadSetPercent', percent) + + if (lastTime === 0) { + lastTime = progressEvent.timeStamp + lastLoaded = progressEvent.loaded + + return + } + + const time = progressEvent.timeStamp - lastTime + if (time < 1000) return + + const data = progressEvent.loaded - lastLoaded + const speed = data / (time / 1000) + commit('uploadSetSpeed', speed) + + lastTime = progressEvent.timeStamp + lastLoaded = progressEvent.loaded + }, + }) + .then((result: any) => { + commit('uploadSetShow', false) + const lastPos = result.data.item.path.lastIndexOf('/') + const filename = result.data.item.path.slice(lastPos + 1) + resolve(filename) + }) + .catch(() => { + commit('uploadSetShow', false) + Vue.$toast.error('Cannot upload the file!') + }) + }) + }, + + uploadSetCurrentNumber({ commit }, payload) { + commit('uploadSetCurrentNumber', payload) + }, + + uploadIncrementCurrentNumber({ state, commit }) { + commit('uploadSetCurrentNumber', state.upload.currentNumber + 1) + }, + + uploadSetMaxNumber({ commit }, payload) { + commit('uploadSetMaxNumber', payload) + }, } diff --git a/src/store/files/index.ts b/src/store/files/index.ts index baee37c0d..ec9c8d772 100644 --- a/src/store/files/index.ts +++ b/src/store/files/index.ts @@ -7,6 +7,15 @@ import { getters } from '@/store/files/getters' export const getDefaultState = (): FileState => { return { filetree: [], + upload: { + show: false, + filename: '', + currentNumber: 0, + maxNumber: 0, + cancelTokenSource: null, + percent: 0, + speed: 0, + }, } } diff --git a/src/store/files/mutations.ts b/src/store/files/mutations.ts index 00027fb47..b4f98babf 100644 --- a/src/store/files/mutations.ts +++ b/src/store/files/mutations.ts @@ -255,4 +255,43 @@ export const mutations: MutationTree = { const rootState = state.filetree.find((dir: FileStateFile) => dir.filename === payload.name) if (rootState) Vue.set(rootState, 'permissions', payload.permissions) }, + + uploadClearState(state) { + const upload = { ...state.upload } + upload.show = false + upload.filename = '' + upload.cancelTokenSource = null + upload.speed = 0 + upload.percent = 0 + + Vue.set(state, 'upload', upload) + }, + + uploadSetShow(state, payload) { + Vue.set(state.upload, 'show', payload) + }, + + uploadSetFilename(state, payload) { + Vue.set(state.upload, 'filename', payload) + }, + + uploadSetCancelTokenSource(state, payload) { + Vue.set(state.upload, 'cancelTokenSource', payload) + }, + + uploadSetCurrentNumber(state, payload) { + Vue.set(state.upload, 'currentNumber', payload) + }, + + uploadSetMaxNumber(state, payload) { + Vue.set(state.upload, 'maxNumber', payload) + }, + + uploadSetPercent(state, payload) { + if (state.upload.percent !== payload) Vue.set(state.upload, 'percent', payload) + }, + + uploadSetSpeed(state, payload) { + if (state.upload.speed !== payload) Vue.set(state.upload, 'speed', payload) + }, } diff --git a/src/store/files/types.ts b/src/store/files/types.ts index 2f64aaec1..e8d85aa77 100644 --- a/src/store/files/types.ts +++ b/src/store/files/types.ts @@ -1,5 +1,16 @@ +import { CancelTokenSource } from 'axios' + export interface FileState { filetree: FileStateFile[] + upload: { + show: boolean + filename: string + currentNumber: number + maxNumber: number + cancelTokenSource: CancelTokenSource | null + percent: number + speed: number + } } export interface FileStateFile { diff --git a/src/store/gui/index.ts b/src/store/gui/index.ts index e8360d0e2..381ae77cd 100644 --- a/src/store/gui/index.ts +++ b/src/store/gui/index.ts @@ -181,7 +181,7 @@ export const getDefaultState = (): GuiState => { 'last_print_duration', 'slicer', ], - currentPath: 'gcodes', + currentPath: '', selectedFiles: [], }, heightmap: { From 895aeba24bd2fdaccab3ce0792f0f14eec745126 Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Fri, 6 May 2022 00:02:39 +0200 Subject: [PATCH 03/17] feat: implement cancel upload Signed-off-by: Stefan Dej --- src/components/TheUploadSnackbar.vue | 10 +++++++++- src/components/panels/GcodefilesPanel.vue | 6 ------ src/store/files/actions.ts | 4 ++++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/TheUploadSnackbar.vue b/src/components/TheUploadSnackbar.vue index 079c9dea3..1c8d636eb 100644 --- a/src/components/TheUploadSnackbar.vue +++ b/src/components/TheUploadSnackbar.vue @@ -32,6 +32,10 @@ export default class TheUploadSnackbar extends Mixins(BaseMixin) { return this.$store.state.files.upload.show ?? false } + get cancelTokenSource() { + return this.$store.state.files.upload.cancelTokenSource + } + get filename() { return this.$store.state.files.upload.filename ?? '' } @@ -52,6 +56,10 @@ export default class TheUploadSnackbar extends Mixins(BaseMixin) { return Math.round(this.$store.state.files.upload.percent ?? 0) } - cancelUpload() {} + cancelUpload() { + this.cancelTokenSource?.cancel() + this.$store.dispatch('files/uploadSetShow', false) + this.$store.dispatch('socket/removeLoading', { name: 'gcodeUpload' }) + } } diff --git a/src/components/panels/GcodefilesPanel.vue b/src/components/panels/GcodefilesPanel.vue index c836ee14b..09a204587 100644 --- a/src/components/panels/GcodefilesPanel.vue +++ b/src/components/panels/GcodefilesPanel.vue @@ -1220,12 +1220,6 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { this.$store.dispatch('gui/setGcodefilesMetadata', { name: name, value: value }) } - cancelUpload() { - this.uploadSnackbar.cancelTokenSource.cancel() - this.uploadSnackbar.status = false - this.$refs.fileUpload.value = '' - } - showContextMenu(e: any, item: FileStateFile) { if (!this.contextMenu.shown) { e?.preventDefault() diff --git a/src/store/files/actions.ts b/src/store/files/actions.ts index 6b09a5956..d4bc036a7 100644 --- a/src/store/files/actions.ts +++ b/src/store/files/actions.ts @@ -319,6 +319,10 @@ export const actions: ActionTree = { }) }, + uploadSetShow({ commit }, payload) { + commit('uploadSetShow', payload) + }, + uploadSetCurrentNumber({ commit }, payload) { commit('uploadSetCurrentNumber', payload) }, From 196150e4de1682ec491de1f497f2ad909ad30cac Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Fri, 6 May 2022 00:10:46 +0200 Subject: [PATCH 04/17] chore: update config file upload to global file upload Signed-off-by: Stefan Dej --- src/components/TheUploadSnackbar.vue | 1 + src/components/panels/GcodefilesPanel.vue | 7 +- .../panels/Machine/ConfigFilesPanel.vue | 66 ++++--------------- 3 files changed, 16 insertions(+), 58 deletions(-) diff --git a/src/components/TheUploadSnackbar.vue b/src/components/TheUploadSnackbar.vue index 1c8d636eb..6527cddfb 100644 --- a/src/components/TheUploadSnackbar.vue +++ b/src/components/TheUploadSnackbar.vue @@ -60,6 +60,7 @@ export default class TheUploadSnackbar extends Mixins(BaseMixin) { this.cancelTokenSource?.cancel() this.$store.dispatch('files/uploadSetShow', false) this.$store.dispatch('socket/removeLoading', { name: 'gcodeUpload' }) + this.$store.dispatch('socket/removeLoading', { name: 'configFileUpload' }) } } diff --git a/src/components/panels/GcodefilesPanel.vue b/src/components/panels/GcodefilesPanel.vue index 09a204587..95f5d6314 100644 --- a/src/components/panels/GcodefilesPanel.vue +++ b/src/components/panels/GcodefilesPanel.vue @@ -1109,11 +1109,14 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { async uploadFile() { if (this.$refs.fileUpload.files?.length) { + const files = [...this.$refs.fileUpload.files] + this.$refs.fileUpload.value = '' + await this.$store.dispatch('socket/addLoading', { name: 'gcodeUpload' }) let successFiles = [] await this.$store.dispatch('files/uploadSetCurrentNumber', 0) await this.$store.dispatch('files/uploadSetMaxNumber', this.$refs.fileUpload.files.length) - for (const file of this.$refs.fileUpload.files) { + for (const file of files) { await this.$store.dispatch('files/uploadIncrementCurrentNumber') const path = this.currentPath.slice(0, 1) === '/' ? this.currentPath.slice(1) : this.currentPath const result = await this.$store.dispatch('files/uploadFile', { @@ -1128,8 +1131,6 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { for (const file of successFiles) { this.$toast.success(this.$t('Files.SuccessfullyUploaded', { filename: file }).toString()) } - - this.$refs.fileUpload.value = '' } } diff --git a/src/components/panels/Machine/ConfigFilesPanel.vue b/src/components/panels/Machine/ConfigFilesPanel.vue index 20dd69271..fb4337318 100644 --- a/src/components/panels/Machine/ConfigFilesPanel.vue +++ b/src/components/panels/Machine/ConfigFilesPanel.vue @@ -939,17 +939,22 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) { async uploadFile() { if (this.$refs.fileUpload.files?.length) { - this.$store.dispatch('socket/addLoading', { name: 'configFileUpload' }) + await this.$store.dispatch('socket/addLoading', { name: 'configFileUpload' }) let successFiles = [] - this.uploadSnackbar.number = 0 - this.uploadSnackbar.max = this.$refs.fileUpload.files.length + await this.$store.dispatch('files/uploadSetCurrentNumber', 0) + await this.$store.dispatch('files/uploadSetMaxNumber', this.$refs.fileUpload.files.length) for (const file of this.$refs.fileUpload.files) { - this.uploadSnackbar.number++ - const result = await this.doUploadFile(file) + await this.$store.dispatch('files/uploadIncrementCurrentNumber') + const path = this.currentPath.slice(0, 1) === '/' ? this.currentPath.slice(1) : this.currentPath + const result = await this.$store.dispatch('files/uploadFile', { + file, + path, + root: 'config', + }) successFiles.push(result) } - this.$store.dispatch('socket/removeLoading', { name: 'configFileUpload' }) + await this.$store.dispatch('socket/removeLoading', { name: 'configFileUpload' }) for (const file of successFiles) { this.$toast.success('Upload of ' + file + ' successful!') } @@ -958,55 +963,6 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) { } } - doUploadFile(file: File) { - let toast = this.$toast - let formData = new FormData() - let filename = file.name.replace(' ', '_') - - this.uploadSnackbar.filename = filename - this.uploadSnackbar.status = true - this.uploadSnackbar.percent = 0 - this.uploadSnackbar.speed = 0 - this.uploadSnackbar.lastProgress.loaded = 0 - this.uploadSnackbar.lastProgress.time = 0 - - formData.append('root', this.root) - formData.append('file', file, this.currentPath + '/' + filename) - this.$store.dispatch('socket/addLoading', { name: 'configFileUpload' }) - - return new Promise((resolve) => { - this.uploadSnackbar.cancelTokenSource = axios.CancelToken.source() - axios - .post(this.apiUrl + '/server/files/upload', formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - cancelToken: this.uploadSnackbar.cancelTokenSource.token, - onUploadProgress: (progressEvent) => { - this.uploadSnackbar.percent = (progressEvent.loaded * 100) / progressEvent.total - if (this.uploadSnackbar.lastProgress.time) { - const time = progressEvent.timeStamp - this.uploadSnackbar.lastProgress.time - const data = progressEvent.loaded - this.uploadSnackbar.lastProgress.loaded - - if (time) this.uploadSnackbar.speed = data / (time / 1000) - } - - this.uploadSnackbar.lastProgress.time = progressEvent.timeStamp - this.uploadSnackbar.lastProgress.loaded = progressEvent.loaded - this.uploadSnackbar.total = progressEvent.total - }, - }) - .then((result) => { - const filename = result.data.item.path.substr(result.data.item.path.indexOf('/') + 1) - this.uploadSnackbar.status = false - resolve(filename) - }) - .catch(() => { - this.uploadSnackbar.status = false - this.$store.dispatch('socket/removeLoading', { name: 'configFileUpload' }) - toast.error('Cannot upload the file!') - }) - }) - } - cancelUpload() { this.uploadSnackbar.cancelTokenSource.cancel() this.uploadSnackbar.status = false From 2110b21c231b3fc143062dae2be00030d1904e69 Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Fri, 6 May 2022 22:11:27 +0200 Subject: [PATCH 05/17] chore: fix eslint issue Signed-off-by: Stefan Dej --- src/components/panels/GcodefilesPanel.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/panels/GcodefilesPanel.vue b/src/components/panels/GcodefilesPanel.vue index 95f5d6314..0a5c3ee45 100644 --- a/src/components/panels/GcodefilesPanel.vue +++ b/src/components/panels/GcodefilesPanel.vue @@ -554,7 +554,6 @@ diff --git a/src/components/panels/GcodefilesPanel.vue b/src/components/panels/GcodefilesPanel.vue index 0a5c3ee45..8110f87ac 100644 --- a/src/components/panels/GcodefilesPanel.vue +++ b/src/components/panels/GcodefilesPanel.vue @@ -1112,7 +1112,6 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { this.$refs.fileUpload.value = '' await this.$store.dispatch('socket/addLoading', { name: 'gcodeUpload' }) - let successFiles = [] await this.$store.dispatch('files/uploadSetCurrentNumber', 0) await this.$store.dispatch('files/uploadSetMaxNumber', this.$refs.fileUpload.files.length) for (const file of files) { @@ -1123,13 +1122,12 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) { path, root: 'gcodes', }) - successFiles.push(result) + + if (result !== false) + this.$toast.success(this.$t('Files.SuccessfullyUploaded', { filename: result }).toString()) } await this.$store.dispatch('socket/removeLoading', { name: 'gcodeUpload' }) - for (const file of successFiles) { - this.$toast.success(this.$t('Files.SuccessfullyUploaded', { filename: file }).toString()) - } } } diff --git a/src/components/panels/Machine/ConfigFilesPanel.vue b/src/components/panels/Machine/ConfigFilesPanel.vue index fb4337318..8fc00894c 100644 --- a/src/components/panels/Machine/ConfigFilesPanel.vue +++ b/src/components/panels/Machine/ConfigFilesPanel.vue @@ -939,11 +939,14 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) { async uploadFile() { if (this.$refs.fileUpload.files?.length) { + const files = [...this.$refs.fileUpload.files] + this.$refs.fileUpload.value = '' + await this.$store.dispatch('socket/addLoading', { name: 'configFileUpload' }) - let successFiles = [] await this.$store.dispatch('files/uploadSetCurrentNumber', 0) await this.$store.dispatch('files/uploadSetMaxNumber', this.$refs.fileUpload.files.length) - for (const file of this.$refs.fileUpload.files) { + + for (const file of files) { await this.$store.dispatch('files/uploadIncrementCurrentNumber') const path = this.currentPath.slice(0, 1) === '/' ? this.currentPath.slice(1) : this.currentPath const result = await this.$store.dispatch('files/uploadFile', { @@ -951,15 +954,12 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) { path, root: 'config', }) - successFiles.push(result) - } - await this.$store.dispatch('socket/removeLoading', { name: 'configFileUpload' }) - for (const file of successFiles) { - this.$toast.success('Upload of ' + file + ' successful!') + if (result !== false) + this.$toast.success(this.$t('Files.SuccessfullyUploaded', { filename: result }).toString()) } - this.$refs.fileUpload.value = '' + await this.$store.dispatch('socket/removeLoading', { name: 'configFileUpload' }) } } diff --git a/src/store/files/actions.ts b/src/store/files/actions.ts index f1ae218a8..8326299cf 100644 --- a/src/store/files/actions.ts +++ b/src/store/files/actions.ts @@ -315,6 +315,7 @@ export const actions: ActionTree = { .catch(() => { commit('uploadSetShow', false) Vue.$toast.error(i18n.t('FullscreenUpload.CannotUploadFile').toString()) + resolve(false) }) }) }, From 2667d659ee1f14fb81f57a3df6274268890a4d9a Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Sun, 8 May 2022 01:41:57 +0200 Subject: [PATCH 08/17] chore: fix IDE code analyzer issues Signed-off-by: Stefan Dej --- src/App.vue | 7 ++++--- src/components/TheFullscreenUpload.vue | 6 ++++-- src/components/panels/Machine/ConfigFilesPanel.vue | 12 +++++------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/App.vue b/src/App.vue index 0abfb4dd3..7dcd52fb3 100644 --- a/src/App.vue +++ b/src/App.vue @@ -16,6 +16,7 @@ background-repeat: no-repeat; } +/*noinspection CssUnusedSymbol*/ .v-btn:not(.v-btn--outlined).primary { /*noinspection CssUnresolvedCustomProperty*/ color: var(--v-btn-text-primary); @@ -245,8 +246,8 @@ export default class App extends Mixins(BaseMixin) { } else { const favicon = 'data:image/svg+xml;base64,' + - btoa( - '' + + Buffer.from( + '' + '' + '' + '' + '' - ) + ).toString('base64') favicon16.href = favicon favicon32.href = favicon diff --git a/src/components/TheFullscreenUpload.vue b/src/components/TheFullscreenUpload.vue index deebcf3cf..afaeee273 100644 --- a/src/components/TheFullscreenUpload.vue +++ b/src/components/TheFullscreenUpload.vue @@ -1,5 +1,5 @@
+
{{ $t('FullscreenUpload.DropFilesToUploadFiles') }}
@@ -16,7 +16,6 @@ export default class TheFullscreenUpload extends Mixins(BaseMixin) { get dropzoneClasses() { return { - 'fullscreen-upload__dragzone': true, 'fullscreen-upload__dragzone--visable': this.visable, } } @@ -103,6 +102,7 @@ export default class TheFullscreenUpload extends Mixins(BaseMixin) { From 245309fd3d6bf7a4e12759fb434f9a12669d490c Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Sun, 8 May 2022 14:26:58 +0200 Subject: [PATCH 15/17] locale(en): remove blank line Signed-off-by: Stefan Dej --- src/locales/en.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/locales/en.json b/src/locales/en.json index b06db1df7..f60e9f193 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -253,7 +253,6 @@ "Later": "Later", "Mesh": "Mesh", "Name": "Name", - "NoBedMeshHasBeenLoadedYet": "No bed mesh has been loaded yet.", "NoProfile": "No profile available", "Ok": "OK", From 25edb17315351f048245b4b7109cf3efff93556d Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Sun, 8 May 2022 14:29:33 +0200 Subject: [PATCH 16/17] locale(en): add missing translation Signed-off-by: Stefan Dej --- src/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/en.json b/src/locales/en.json index f60e9f193..40dcb7d41 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -203,7 +203,7 @@ "WrongFileUploaded": "Upload was denied for {filename}. Wrong file format!" }, "FullscreenUpload": { - "CannotUploadFile": "Drop files to upload", + "CannotUploadFile": "Cannot upload file!", "DropFilesToUploadFiles": "Drop files to upload" }, "GCodeViewer": { From af0d3aa70642fbd6b039d2148fd8b22797be08b6 Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Sun, 8 May 2022 15:19:42 +0200 Subject: [PATCH 17/17] locale(de): add DE translations Signed-off-by: Stefan Dej --- src/locales/de.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/locales/de.json b/src/locales/de.json index 40d9eab15..7b6dbcc16 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -205,6 +205,10 @@ "View3D": "3D Betrachtung", "WrongFileUploaded": "Hochladen von {filename} verweigert. Falsches Dateiformat!" }, + "FullscreenUpload": { + "CannotUploadFile": "Datei konnte nicht hochgeladen werden!", + "DropFilesToUploadFiles": "Datei ablegen zum Hochladen" + }, "GCodeViewer": { "ClearLoadedFile": "Löschen", "ColorMode": "Farbmodus",