diff --git a/src/App.vue b/src/App.vue
index 898dfea320..bbbd06450d 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -3,6 +3,7 @@
@@ -40,7 +41,7 @@
+
+
@@ -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({
metaInfo () {
@@ -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 = {}
flashMessageState: FlashMessage = {
@@ -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 () {
@@ -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
@@ -298,6 +316,13 @@ 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
}
@@ -305,5 +330,60 @@ export default class App extends Mixins(StateMixin, FilesMixin, BrowserMixin) {
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)
+ }
+ }
+ }
+ }
}
diff --git a/src/components/ui/AppDragOverlay.vue b/src/components/ui/AppDragOverlay.vue
index 236238ef26..8a2666803f 100644
--- a/src/components/ui/AppDragOverlay.vue
+++ b/src/components/ui/AppDragOverlay.vue
@@ -1,9 +1,9 @@
item.type === 'file' && item.extension !== 'jpg' && 'thumbnails' in item)
@@ -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 &&
@@ -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
) {
diff --git a/src/components/widgets/filesystem/FileSystemBrowser.vue b/src/components/widgets/filesystem/FileSystemBrowser.vue
index 1847301326..59e9ea1821 100644
--- a/src/components/widgets/filesystem/FileSystemBrowser.vue
+++ b/src/components/widgets/filesystem/FileSystemBrowser.vue
@@ -421,7 +421,6 @@ export default class FileSystemBrowser extends Mixins(FilesMixin) {
isItemWriteable (item: FileBrowserEntry) {
return (
- item.name !== '..' &&
!this.readonly &&
(
item.permissions === undefined ||
diff --git a/src/components/widgets/gcode-preview/GcodePreviewCard.vue b/src/components/widgets/gcode-preview/GcodePreviewCard.vue
index 0b483b1a22..18e4f23348 100644
--- a/src/components/widgets/gcode-preview/GcodePreviewCard.vue
+++ b/src/components/widgets/gcode-preview/GcodePreviewCard.vue
@@ -134,6 +134,7 @@
v-model="overlay"
:message="$t('app.gcode.overlay.drag_file_load')"
icon="$cubeScan"
+ absolute
/>
diff --git a/src/components/widgets/job-queue/JobQueue.vue b/src/components/widgets/job-queue/JobQueue.vue
index 2a170fc2bd..4b2e1fe947 100644
--- a/src/components/widgets/job-queue/JobQueue.vue
+++ b/src/components/widgets/job-queue/JobQueue.vue
@@ -30,6 +30,7 @@
v-model="overlay"
:message="$t('app.file_system.overlay.drag_files_enqueue')"
icon="$enqueueJob"
+ absolute
/>
)
!router.app.$store.state.socket.apiConnected
) {
next()
- return
+ } else {
+ next('/login')
+ }
+}
+
+const defaultRouteConfig: Partial = {
+ beforeEnter: ifAuthenticated,
+ meta: {
+ fileDropRoot: 'gcodes'
}
- next('/login')
}
const routes: Array = [
@@ -38,60 +45,64 @@ const routes: Array = [
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
},
@@ -113,23 +124,25 @@ const routes: Array = [
}
]
},
- {
- 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',
@@ -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