Skip to content

Commit

Permalink
feat: full-screen file drag & drop
Browse files Browse the repository at this point in the history
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
  • Loading branch information
pedrolamas committed Oct 17, 2023
1 parent 6cffb99 commit a954be8
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 27 deletions.
86 changes: 83 additions & 3 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<v-app
v-else
class="fluidd"
:class="{ 'no-pointer-events': dragState }"
>
<app-tools-drawer v-model="toolsdrawer" />
<app-nav-drawer v-model="navdrawer" />
Expand Down Expand Up @@ -40,7 +41,7 @@
<v-container
fluid
:class="{
'fill-height': $route.meta && $route.meta.fillHeight,
'fill-height': $route.meta?.fillHeight ?? false,
[['single', 'double', 'triple', 'quad'][columnCount - 1]]: true
}"
class="constrained-width pa-2 pa-sm-4"
Expand Down Expand Up @@ -80,6 +81,12 @@
</v-main>
<app-footer />
<app-drag-overlay
v-model="dragState"
:message="$t('app.file_system.overlay.drag_files_folders_upload')"
icon="$fileUpload"
/>
</v-app>
</template>
Expand All @@ -93,6 +100,7 @@ import type { LinkPropertyHref } from 'vue-meta'
import FileSystemDownloadDialog from '@/components/widgets/filesystem/FileSystemDownloadDialog.vue'
import SpoolSelectionDialog from '@/components/widgets/spoolman/SpoolSelectionDialog.vue'
import type { FlashMessage } from '@/types'
import { getFilesFromDataTransfer, hasFilesInDataTransfer } from './util/file-system-entry'
@Component<App>({
metaInfo () {
Expand All @@ -116,6 +124,7 @@ export default class App extends Mixins(StateMixin, FilesMixin, BrowserMixin) {
toolsdrawer: boolean | null = null
navdrawer: boolean | null = null
showUpdateUI = false
dragState = false
customBackgroundImageStyle: Record<string, string> = {}
flashMessageState: FlashMessage = {
Expand All @@ -134,8 +143,12 @@ export default class App extends Mixins(StateMixin, FilesMixin, BrowserMixin) {
return (this.$store.state.config.layoutMode)
}
get columnCount () {
return this.$store.state.config.containerColumnCount
get columnCount (): number {
return this.$store.state.config.containerColumnCount as number
}
get fileDropRoot () {
return this.$route.meta?.fileDropRoot
}
get loading () {
Expand Down Expand Up @@ -272,6 +285,11 @@ export default class App extends Mixins(StateMixin, FilesMixin, BrowserMixin) {
}
mounted () {
window.addEventListener('dragover', this.handleDragOver)
window.addEventListener('dragenter', this.handleDragEnter)
window.addEventListener('dragleave', this.handleDragLeave)
window.addEventListener('drop', this.handleDrop)
// this.onLoadLocale(this.$i18n.locale)
EventBus.bus.$on('flashMessage', (payload: FlashMessage) => {
this.flashMessageState.text = (payload && payload.text) || undefined
Expand All @@ -298,12 +316,74 @@ export default class App extends Mixins(StateMixin, FilesMixin, BrowserMixin) {
}
}
beforeDestroy () {
window.removeEventListener('dragover', this.handleDragOver)
window.removeEventListener('dragenter', this.handleDragEnter)
window.removeEventListener('dragleave', this.handleDragLeave)
window.removeEventListener('drop', this.handleDrop)
}
handleToolsDrawerChange () {
this.toolsdrawer = !this.toolsdrawer
}
handleNavDrawerChange () {
this.navdrawer = !this.navdrawer
}
handleDragOver (event: DragEvent) {
if (
this.fileDropRoot &&
event.dataTransfer &&
hasFilesInDataTransfer(event.dataTransfer)
) {
event.preventDefault()
this.dragState = true
event.dataTransfer.dropEffect = 'copy'
}
}
handleDragEnter (event: DragEvent) {
if (this.fileDropRoot) {
event.preventDefault()
}
}
handleDragLeave (event: DragEvent) {
if (this.fileDropRoot) {
event.preventDefault()
if (
event.target instanceof HTMLElement &&
event.target.className.includes('fluidd')
) {
this.dragState = false
}
}
}
async handleDrop (event: DragEvent) {
if (this.fileDropRoot) {
event.preventDefault()
this.dragState = false
if (event.dataTransfer) {
const files = await getFilesFromDataTransfer(event.dataTransfer)
if (files) {
const wait = `${this.$waits.onFileSystem}/gcodes/`
this.$store.dispatch('wait/addWait', wait)
await this.uploadFiles(files, '', this.fileDropRoot, false)
this.$store.dispatch('wait/removeWait', wait)
}
}
}
}
}
</script>
2 changes: 1 addition & 1 deletion src/components/ui/AppDragOverlay.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<template>
<v-overlay
v-bind="$attrs"
class="dragOverlay"
:value="value"
:opacity="0.85"
absolute
>
<v-container>
<v-row
Expand Down
7 changes: 7 additions & 0 deletions src/components/widgets/filesystem/FileSystem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
v-model="dragState.overlay"
:message="$t('app.file_system.overlay.drag_files_folders_upload')"
icon="$fileUpload"
absolute
/>

<file-system-upload-dialog
Expand Down Expand Up @@ -444,6 +445,10 @@ export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesM
return this.$store.state.server.info.registered_directories || []
}
get fileDropRoot () {
return this.$route.meta?.fileDropRoot
}
includeTimelapseThumbnailFiles (items: FileBrowserEntry[]) {
const thumbnailFilenames = new Set(items
.filter((item): item is AppFileWithMeta => item.type === 'file' && item.extension !== 'jpg' && 'thumbnails' in item)
Expand Down Expand Up @@ -941,6 +946,7 @@ export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesM
*/
handleDragOver (event: DragEvent) {
if (
!this.fileDropRoot &&
!this.rootProperties.readonly &&
!this.dragState.browserState &&
event.dataTransfer &&
Expand All @@ -962,6 +968,7 @@ export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesM
this.dragState.overlay = false
if (
!this.fileDropRoot &&
event.dataTransfer &&
!this.rootProperties.readonly
) {
Expand Down
1 change: 0 additions & 1 deletion src/components/widgets/filesystem/FileSystemBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,6 @@ export default class FileSystemBrowser extends Mixins(FilesMixin) {
isItemWriteable (item: FileBrowserEntry) {
return (
item.name !== '..' &&
!this.readonly &&
(
item.permissions === undefined ||
Expand Down
1 change: 1 addition & 0 deletions src/components/widgets/gcode-preview/GcodePreviewCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
v-model="overlay"
:message="$t('app.gcode.overlay.drag_file_load')"
icon="$cubeScan"
absolute
/>
</v-card-text>
</collapsable-card>
Expand Down
1 change: 1 addition & 0 deletions src/components/widgets/job-queue/JobQueue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
v-model="overlay"
:message="$t('app.file_system.overlay.drag_files_enqueue')"
icon="$enqueueJob"
absolute
/>

<job-queue-context-menu
Expand Down
65 changes: 43 additions & 22 deletions src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,70 +28,81 @@ const ifAuthenticated = (to: Route, from: Route, next: NavigationGuardNext<Vue>)
!router.app.$store.state.socket.apiConnected
) {
next()
return
} else {
next('/login')
}
}

const defaultRouteConfig: Partial<RouteConfig> = {
beforeEnter: ifAuthenticated,
meta: {
fileDropRoot: 'gcodes'
}
next('/login')
}

const routes: Array<RouteConfig> = [
{
path: '/',
name: 'Dashboard',
component: Dashboard,
beforeEnter: ifAuthenticated
...defaultRouteConfig
},
{
path: '/console',
name: 'Console',
component: Console,
beforeEnter: ifAuthenticated
...defaultRouteConfig
},
{
path: '/jobs',
name: 'Jobs',
component: Jobs,
beforeEnter: ifAuthenticated
...defaultRouteConfig
},
{
path: '/tune',
name: 'Tune',
component: Tune,
beforeEnter: ifAuthenticated
...defaultRouteConfig
},
{
path: '/diagnostics',
name: 'Diagnostics',
component: Diagnostics,
beforeEnter: ifAuthenticated
...defaultRouteConfig
},
{
path: '/timelapse',
name: 'Timelapse',
component: Timelapse,
beforeEnter: ifAuthenticated
...defaultRouteConfig,
meta: {
fileDropRoot: 'timelapse'
}
},
{
path: '/history',
name: 'History',
component: History,
beforeEnter: ifAuthenticated
...defaultRouteConfig
},
{
path: '/system',
name: 'System',
component: System,
beforeEnter: ifAuthenticated
...defaultRouteConfig
},
{
path: '/configure',
name: 'Configuration',
component: Configure,
beforeEnter: ifAuthenticated
...defaultRouteConfig,
meta: {}
},
{
path: '/settings',
name: 'Settings',
beforeEnter: ifAuthenticated,
...defaultRouteConfig,
meta: {
hasSubNavigation: true
},
Expand All @@ -113,23 +124,25 @@ const routes: Array<RouteConfig> = [
}
]
},
{
path: '/login',
name: 'Login',
component: Login,
meta: {
fillHeight: true
}
},
{
path: '/camera/:cameraId',
name: 'Camera',
component: FullscreenCamera
component: FullscreenCamera,
...defaultRouteConfig
},
{
path: '/preview',
name: 'Gcode Preview',
component: GcodePreview
component: GcodePreview,
...defaultRouteConfig
},
{
path: '/login',
name: 'Login',
component: Login,
meta: {
fillHeight: true
}
},
{
path: '/icons',
Expand Down Expand Up @@ -164,4 +177,12 @@ router.beforeEach((to, from, next) => {
next()
})

declare module 'vue-router' {
interface RouteMeta {
fillHeight?: boolean
hasSubNavigation?: boolean
fileDropRoot?: string
}
}

export default router

0 comments on commit a954be8

Please sign in to comment.