feat: global fullscreen fileupload (#777)

* feat: init upload from global gcode file upload

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* feat: global fileupload & fileupload status snackbar

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* feat: implement cancel upload

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* chore: update config file upload to global file upload

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* chore: fix eslint issue

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* lcoale: add fullscreenUpload to i18n

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* chore: fix toast position with upload snackbar

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* chore: fix IDE code analyzer issues

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* fix: app favicon draw function

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* style: fix favicon source code

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* chore: fix type in TheFullscreenUpload.vue

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* chore: fix type in TheFullscreenUpload.vue

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* fix: fullscreen upload dont work in firefox

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* feat: add icon to TheFullscreenUpload.vue

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* locale(en): remove blank line

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* locale(en): add missing translation

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* locale(de): add DE translations

Signed-off-by: Stefan Dej <meteyou@gmail.com>
This commit is contained in:
Stefan Dej 2022-05-08 17:29:59 +02:00 committed by GitHub
parent c94e70f876
commit 654785fedf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 477 additions and 314 deletions

View File

@ -16,7 +16,9 @@
background-repeat: no-repeat;
}
/*noinspection CssUnusedSymbol*/
.v-btn:not(.v-btn--outlined).primary {
/*noinspection CssUnresolvedCustomProperty*/
color: var(--v-btn-text-primary);
}
@ -42,6 +44,8 @@
<the-update-dialog></the-update-dialog>
<the-editor></the-editor>
<the-timelapse-rendering-snackbar></the-timelapse-rendering-snackbar>
<the-fullscreen-upload></the-fullscreen-upload>
<the-upload-snackbar></the-upload-snackbar>
</v-app>
</template>
@ -57,6 +61,8 @@ 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'
import TheUploadSnackbar from '@/components/TheUploadSnackbar.vue'
@Component({
components: {
@ -67,6 +73,8 @@ import TheTimelapseRenderingSnackbar from '@/components/TheTimelapseRenderingSna
TheUpdateDialog,
TheTopbar,
TheSidebar,
TheFullscreenUpload,
TheUploadSnackbar,
},
metaInfo() {
const title = this.$store.getters['getTitle']
@ -76,10 +84,6 @@ import TheTimelapseRenderingSnackbar from '@/components/TheTimelapseRenderingSna
},
})
export default class App extends Mixins(BaseMixin) {
panelToolbarHeight = panelToolbarHeight
topbarHeight = topbarHeight
navigationItemHeight = navigationItemHeight
get title(): string {
return this.$store.getters['getTitle']
}
@ -242,21 +246,15 @@ export default class App extends Mixins(BaseMixin) {
} else {
const favicon =
'data:image/svg+xml;base64,' +
btoa(
'<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 599.38 523.11" xml:space="preserve">' +
'<g>' +
'<path style="fill:' +
this.logoColor +
';" d="M382.29,142.98L132.98,522.82L0,522.68L344.3,0l0,0C352.18,49.06,365.2,97.68,382.29,142.98"/>' +
'<path style="fill:' +
this.logoColor +
';" d="M413.28,213.54L208.5,522.92l132.94,0.19l135.03-206.33l0,0C452.69,284.29,431.53,249.77,413.28,213.54 L413.28,213.54"/>' +
'<path style="fill:' +
this.logoColor +
';" d="M599.38,447.69l-49.25,75.42L417,522.82l101.6-153.67l0,0C543.48,397.35,570.49,423.61,599.38,447.69 L599.38,447.69z"/>' +
'</g>' +
'</svg>'
)
window.btoa(`
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 599.38 523.11" xml:space="preserve">
<g>
<path style="fill:${this.logoColor};" d="M382.29,142.98L132.98,522.82L0,522.68L344.3,0l0,0C352.18,49.06,365.2,97.68,382.29,142.98"/>
<path style="fill:${this.logoColor};" d="M413.28,213.54L208.5,522.92l132.94,0.19l135.03-206.33l0,0C452.69,284.29,431.53,249.77,413.28,213.54 L413.28,213.54"/>
<path style="fill:${this.logoColor};" d="M599.38,447.69l-49.25,75.42L417,522.82l101.6-153.67l0,0C543.48,397.35,570.49,423.61,599.38,447.69 L599.38,447.69z"/>
</g>
</svg>
`)
favicon16.href = favicon
favicon32.href = favicon

View File

@ -13,3 +13,7 @@
.v-toast__text {
font-family: 'Roboto', sans-serif;
}
.fullscreenUpload--active .v-toast--bottom {
bottom: 88px;
}

View File

@ -52,6 +52,10 @@
animation-timing-function: linear;
}
.v-toast {
padding: 8px !important;
}
@keyframes spin {
from {
transform: rotate(0deg);

View File

@ -0,0 +1,153 @@
<template>
<div class="d-flex justify-center flex-column fullscreen-upload__dragzone" :class="dropzoneClasses" @drop="onDrop">
<v-icon class="fullscreen-upload__icon">{{ mdiTrayArrowDown }}</v-icon>
<div class="textnode">{{ $t('FullscreenUpload.DropFilesToUploadFiles') }}</div>
</div>
</template>
<script lang="ts">
import { Mixins } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import Component from 'vue-class-component'
import { validGcodeExtensions } from '@/store/variables'
import { mdiTrayArrowDown } from '@mdi/js'
@Component
export default class TheFullscreenUpload extends Mixins(BaseMixin) {
mdiTrayArrowDown = mdiTrayArrowDown
private visible = false
get dropzoneClasses() {
return {
'fullscreen-upload__dragzone--visible': this.visible,
}
}
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() {
this.visible = true
}
hideDropZone() {
this.visible = false
}
onDragOverWindow(e: any) {
const types = e.dataTransfer?.types ?? []
if (!types.includes('Files')) return
e.preventDefault()
if (this.visible) return
this.showDropZone()
}
onDragLeaveWindow(e: any) {
e.preventDefault()
this.hideDropZone()
}
async onDrop(e: any) {
e.preventDefault()
this.hideDropZone()
if (e.dataTransfer?.files?.length) {
const files = [...e.dataTransfer.files]
await this.$store.dispatch('socket/addLoading', { name: 'gcodeUpload' })
await this.$store.dispatch('files/uploadSetCurrentNumber', 0)
await this.$store.dispatch('files/uploadSetMaxNumber', files.length)
for (const file of files) {
const extensionPos = file.name.lastIndexOf('.')
const extension = file.name.slice(extensionPos)
const isGcode = validGcodeExtensions.includes(extension)
let path = ''
if (this.currentRoute === '/files' && isGcode) path = this.currentPathGcodes
else if (this.currentRoute === '/config' && !isGcode) path = this.currentPathConfig
const root = isGcode ? 'gcodes' : 'config'
await this.$store.dispatch('files/uploadIncrementCurrentNumber')
const result = await this.$store.dispatch('files/uploadFile', { file, path, root })
if (result !== false)
this.$toast.success(this.$t('Files.SuccessfullyUploaded', { filename: result }).toString())
}
await this.$store.dispatch('socket/removeLoading', { name: 'gcodeUpload' })
}
}
}
</script>
<style>
.fullscreen-upload__dragzone {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 99999;
visibility: hidden;
opacity: 0;
transition: visibility 200ms, opacity 200ms;
font: bold 42px Oswald, DejaVu Sans, Tahoma, sans-serif;
}
/*noinspection CssUnusedSymbol*/
.fullscreen-upload__dragzone--visible {
opacity: 1;
visibility: visible;
}
/*noinspection CssUnusedSymbol*/
.fullscreen-upload__dragzone:before {
display: block;
content: ' ';
position: absolute;
top: 1em;
right: 1em;
bottom: 1em;
left: 1em;
border: 3px dashed white;
border-radius: 1em;
}
/*noinspection CssUnusedSymbol*/
.fullscreen-upload__icon .v-icon__svg {
width: 250px;
height: 250px;
}
.fullscreen-upload__dragzone .textnode {
text-align: center;
transition: font-size 175ms;
font-size: 82px;
}
</style>

View File

@ -0,0 +1,74 @@
<template>
<v-snackbar v-if="show" :timeout="-1" :value="true" fixed right bottom dark>
<span v-if="maxNumber > 1" class="mr-1">({{ currentNumber }}/{{ maxNumber }})</span>
<strong>{{ $t('Files.Uploading') + ' ' + filename }}</strong>
<br />
{{ percent }} % @ {{ speed }}/s
<br />
<v-progress-linear class="mt-2" :value="percent"></v-progress-linear>
<template #action="{ attrs }">
<v-btn color="red" text v-bind="attrs" style="min-width: auto" @click="cancelUpload">
<v-icon class="0">{{ mdiClose }}</v-icon>
</v-btn>
</template>
</v-snackbar>
</template>
<script lang="ts">
import Component from 'vue-class-component'
import { Mixins, Watch } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import { mdiClose } from '@mdi/js'
import { formatFilesize } from '@/plugins/helpers'
@Component({
components: {},
})
export default class TheUploadSnackbar extends Mixins(BaseMixin) {
mdiClose = mdiClose
formatFilesize = formatFilesize
get show() {
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 ?? ''
}
get currentNumber() {
return this.$store.state.files.upload.currentNumber ?? 0
}
get maxNumber() {
return this.$store.state.files.upload.maxNumber ?? 0
}
get speed() {
return formatFilesize(Math.round(this.$store.state.files.upload.speed ?? 0))
}
get percent() {
return Math.round(this.$store.state.files.upload.percent ?? 0)
}
cancelUpload() {
this.cancelTokenSource?.cancel()
this.$store.dispatch('files/uploadSetShow', false)
this.$store.dispatch('socket/removeLoading', { name: 'gcodeUpload' })
this.$store.dispatch('socket/removeLoading', { name: 'configFileUpload' })
}
@Watch('show')
showChanged(newVal: boolean) {
const body = document.getElementsByTagName('body')[0]
if (newVal) body.classList.add('fullscreenUpload--active')
else body.classList.remove('fullscreenUpload--active')
}
}
</script>

View File

@ -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 @@
</style>
<template>
<div @dragover="dragOverUpload" @dragleave="dragLeaveUpload" @drop.prevent.stop="dragDropUpload">
<div>
<panel :title="$t('Files.GCodeFiles')" :icon="mdiFileDocumentMultipleOutline" card-class="gcode-files-panel">
<v-card-text>
<v-row>
@ -373,27 +339,7 @@
</tr>
</template>
</v-data-table>
<div
class="file-list__dragzone d-flex justify-center flex-column"
:style="'visibility: ' + dropzone.visibility + '; opacity: ' + dropzone.hidden">
<div class="textnode">{{ $t('Files.DropFilesToAddGcode') }}</div>
</div>
</panel>
<v-snackbar v-model="uploadSnackbar.status" :timeout="-1" :value="true" fixed right bottom dark>
<span v-if="uploadSnackbar.max > 1" class="mr-1">
({{ uploadSnackbar.number }}/{{ uploadSnackbar.max }})
</span>
<strong>{{ $t('Files.Uploading') + ' ' + uploadSnackbar.filename }}</strong>
<br />
{{ Math.round(uploadSnackbar.percent) }} % @ {{ formatFilesize(Math.round(uploadSnackbar.speed)) }}/s
<br />
<v-progress-linear class="mt-2" :value="uploadSnackbar.percent"></v-progress-linear>
<template #action="{ attrs }">
<v-btn color="red" text v-bind="attrs" style="min-width: auto" @click="cancelUpload">
<v-icon class="0">{{ mdiClose }}</v-icon>
</v-btn>
</template>
</v-snackbar>
<v-dialog v-model="dialogPrintFile.show" :max-width="dialogPrintFile.item.big_thumbnail_width">
<v-card>
<v-img
@ -608,7 +554,6 @@
<script lang="ts">
import { Component, Mixins, Watch } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import axios from 'axios'
import { validGcodeExtensions } from '@/store/variables'
import { formatFilesize, formatDate, sortFiles, formatPrintTime } from '@/plugins/helpers'
import { FileStateFile, FileStateGcodefile } from '@/store/files/types'
@ -641,7 +586,6 @@ import {
} from '@mdi/js'
interface draggingFile {
status: boolean
item: FileStateGcodefile
}
@ -740,7 +684,6 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) {
opacity: 0,
}
private draggingFile: draggingFile = {
status: false,
item: {
isDirectory: false,
filename: '',
@ -1130,171 +1073,61 @@ 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
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.draggingFile.status) {
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.draggingFile.status) {
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.draggingFile.status) {
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' })
let successFiles = []
this.uploadSnackbar.number = 0
this.uploadSnackbar.max = this.$refs.fileUpload.files.length
for (const file of this.$refs.fileUpload.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())
}
const files = [...this.$refs.fileUpload.files]
this.$refs.fileUpload.value = ''
await this.$store.dispatch('socket/addLoading', { name: 'gcodeUpload' })
await this.$store.dispatch('files/uploadSetCurrentNumber', 0)
await this.$store.dispatch('files/uploadSetMaxNumber', this.$refs.fileUpload.files.length)
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', {
file,
path,
root: 'gcodes',
})
if (result !== false)
this.$toast.success(this.$t('Files.SuccessfullyUploaded', { filename: result }).toString())
}
await this.$store.dispatch('socket/removeLoading', { name: 'gcodeUpload' })
}
}
@ -1385,12 +1218,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()
@ -1521,13 +1348,11 @@ export default class GcodefilesPanel extends Mixins(BaseMixin) {
dragFile(e: Event, item: FileStateGcodefile) {
e.preventDefault()
this.draggingFile.status = true
this.draggingFile.item = item
}
dragendFile(e: Event) {
e.preventDefault()
this.draggingFile.status = false
this.draggingFile.item = {
isDirectory: false,
filename: '',

View File

@ -11,12 +11,13 @@
<v-col class="col-12 col-lg pr-lg-0">
<v-select
v-model="root"
class="machine-configfiles-panel__root-select"
:items="registeredDirectories"
:label="$t('Machine.ConfigFilesPanel.Root')"
outlined
hide-details
dense
attach
attach=".machine-configfiles-panel__root-select"
@change="changeRoot"></v-select>
</v-col>
<v-col class="col col-lg-auto pl-lg-0 text-right">
@ -34,10 +35,10 @@
<span>{{ button.text }}</span>
</v-tooltip>
</v-btn>
<v-menu offset-y left :title="$t('Machine.ConfigFilesPanel.SetupCurrentList')" attach>
<v-menu offset-y left :title="$t('Machine.ConfigFilesPanel.SetupCurrentList')">
<template #activator="{ on, attrs }">
<v-btn class="px-2 minwidth-0 ml-3" v-bind="attrs" v-on="on">
<v-icon>{{ mdiCog }}</v-icon>
<v-icon class="machine-configfiles-panel__settings-icon">{{ mdiCog }}</v-icon>
</v-btn>
</template>
<v-list>
@ -93,7 +94,6 @@
:items="files"
class="files-table"
:headers="headers"
:options="options"
:page.sync="currentPage"
:custom-sort="sortFiles"
:sort-by.sync="sortBy"
@ -464,7 +464,6 @@ interface uploadSnackbar {
}
interface draggingFile {
status: boolean
item: FileStateFile
}
@ -496,8 +495,6 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
inputDialogRenameDirectoryName: HTMLInputElement
}
private selected = []
private options = {}
private currentPage = 1
private contextMenu: contextMenu = {
shown: false,
@ -572,7 +569,6 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
},
}
private draggingFile: draggingFile = {
status: false,
item: {
isDirectory: false,
filename: '',
@ -581,6 +577,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 [
{
@ -934,74 +938,30 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
async uploadFile() {
if (this.$refs.fileUpload.files?.length) {
this.$store.dispatch('socket/addLoading', { name: 'configFileUpload' })
let successFiles = []
this.uploadSnackbar.number = 0
this.uploadSnackbar.max = this.$refs.fileUpload.files.length
for (const file of this.$refs.fileUpload.files) {
this.uploadSnackbar.number++
const result = await this.doUploadFile(file)
successFiles.push(result)
}
this.$store.dispatch('socket/removeLoading', { name: 'configFileUpload' })
for (const file of successFiles) {
this.$toast.success('Upload of ' + file + ' successful!')
}
const files = [...this.$refs.fileUpload.files]
this.$refs.fileUpload.value = ''
await this.$store.dispatch('socket/addLoading', { name: 'configFileUpload' })
await this.$store.dispatch('files/uploadSetCurrentNumber', 0)
await this.$store.dispatch('files/uploadSetMaxNumber', this.$refs.fileUpload.files.length)
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', {
file,
path,
root: 'config',
})
if (result !== false)
this.$toast.success(this.$t('Files.SuccessfullyUploaded', { filename: result }).toString())
}
await this.$store.dispatch('socket/removeLoading', { name: 'configFileUpload' })
}
}
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
@ -1009,13 +969,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: '',
@ -1025,7 +985,7 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
}
dragOverFilelist(e: any, row: any) {
if (this.draggingFile.status) {
if (this.blockFileUpload) {
e.preventDefault()
//e.stopPropagation()
@ -1034,7 +994,7 @@ export default class ConfigFilesPanel extends Mixins(BaseMixin) {
}
dragLeaveFilelist(e: any) {
if (this.draggingFile.status) {
if (this.blockFileUpload) {
e.preventDefault()
e.stopPropagation()
@ -1043,11 +1003,11 @@ 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'
let dest = ''
let dest: string
if (row.filename === '..') {
dest =
this.absolutePath.slice(1, this.absolutePath.lastIndexOf('/') + 1) + this.draggingFile.item.filename

View File

@ -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",

View File

@ -157,7 +157,6 @@
"Download": "Download",
"Downloading": "Downloading",
"DoYouWantToStartFilename": "Do you want to start {filename}?",
"DropFilesToAddGcode": "Drag & Drop files to add G-Code.",
"EditFile": "Edit File",
"Empty": "Empty",
"Filament": "Filament",
@ -203,6 +202,10 @@
"View3D": "View 3D",
"WrongFileUploaded": "Upload was denied for {filename}. Wrong file format!"
},
"FullscreenUpload": {
"CannotUploadFile": "Cannot upload file!",
"DropFilesToUploadFiles": "Drop files to upload"
},
"GCodeViewer": {
"ClearLoadedFile": "Clear",
"ColorMode": "Color Mode",

View File

@ -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<FileState, RootState> = {
reset({ commit }) {
@ -258,4 +259,80 @@ export const actions: ActionTree<FileState, RootState> = {
Vue.$toast.success(<string>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(i18n.t('FullscreenUpload.CannotUploadFile').toString())
resolve(false)
})
})
},
uploadSetShow({ commit }, payload) {
commit('uploadSetShow', payload)
},
uploadSetCurrentNumber({ commit }, payload) {
commit('uploadSetCurrentNumber', payload)
},
uploadIncrementCurrentNumber({ state, commit }) {
commit('uploadSetCurrentNumber', state.upload.currentNumber + 1)
},
uploadSetMaxNumber({ commit }, payload) {
commit('uploadSetMaxNumber', payload)
},
}

View File

@ -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,
},
}
}

View File

@ -255,4 +255,43 @@ export const mutations: MutationTree<FileState> = {
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)
},
}

View File

@ -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 {

View File

@ -150,6 +150,7 @@ export const getDefaultState = (): GuiState => {
navigationStyle: 'iconsAndText',
},
view: {
blockFileUpload: false,
configfiles: {
countPerPage: 10,
sortBy: 'filename',
@ -180,7 +181,7 @@ export const getDefaultState = (): GuiState => {
'last_print_duration',
'slicer',
],
currentPath: 'gcodes',
currentPath: '',
selectedFiles: [],
},
heightmap: {

View File

@ -103,6 +103,7 @@ export interface GuiState {
navigationStyle: 'iconsAndText' | 'iconsOnly'
}
view: {
blockFileUpload: boolean
configfiles: {
countPerPage: number
sortBy: string