Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: global fullscreen fileupload #777

Merged
merged 17 commits into from
May 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 17 additions & 19 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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>

Expand All @@ -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: {
Expand All @@ -67,6 +73,8 @@ import TheTimelapseRenderingSnackbar from '@/components/TheTimelapseRenderingSna
TheUpdateDialog,
TheTopbar,
TheSidebar,
TheFullscreenUpload,
TheUploadSnackbar,
},
metaInfo() {
const title = this.$store.getters['getTitle']
Expand All @@ -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']
}
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/assets/styles/toastr.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@
.v-toast__text {
font-family: 'Roboto', sans-serif;
}

.fullscreenUpload--active .v-toast--bottom {
bottom: 88px;
}
4 changes: 4 additions & 0 deletions src/assets/styles/utils.scss
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
animation-timing-function: linear;
}

.v-toast {
padding: 8px !important;
}

@keyframes spin {
from {
transform: rotate(0deg);
Expand Down
153 changes: 153 additions & 0 deletions src/components/TheFullscreenUpload.vue
Original file line number Diff line number Diff line change
@@ -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>
74 changes: 74 additions & 0 deletions src/components/TheUploadSnackbar.vue
Original file line number Diff line number Diff line change
@@ -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>
Loading