From cf3264423a311d660759ee914a47ca2a8a11301f Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Fri, 17 Nov 2023 12:37:17 +0000 Subject: [PATCH] feat: adds drag&drop across File Manager cards Signed-off-by: Pedro Lamas --- .../widgets/filesystem/FileSystem.vue | 53 +++++++++++++------ .../widgets/filesystem/FileSystemBrowser.vue | 15 ++---- .../gcode-preview/GcodePreviewCard.vue | 16 ++++-- src/components/widgets/job-queue/JobQueue.vue | 16 ++++-- src/globals.ts | 4 ++ src/locales/af.yaml | 1 - src/locales/de.yaml | 1 - src/locales/en.yaml | 2 +- src/locales/hu.yaml | 1 - src/locales/ja.yaml | 1 - src/locales/pl.yaml | 1 - src/locales/pt.yaml | 1 - src/locales/ru.yaml | 1 - src/locales/sl.yaml | 1 - src/locales/zh-CN.yaml | 1 - src/locales/zh-HK.yaml | 1 - src/scss/file-system.scss | 4 +- src/util/file-data-transfer.ts | 22 ++++++++ 18 files changed, 94 insertions(+), 48 deletions(-) create mode 100644 src/util/file-data-transfer.ts diff --git a/src/components/widgets/filesystem/FileSystem.vue b/src/components/widgets/filesystem/FileSystem.vue index 22c30f072d..253d15fc8b 100644 --- a/src/components/widgets/filesystem/FileSystem.vue +++ b/src/components/widgets/filesystem/FileSystem.vue @@ -156,6 +156,7 @@ import FileSystemGoToFileDialog from './FileSystemGoToFileDialog.vue' import FilePreviewDialog from './FilePreviewDialog.vue' import type { AppTableHeader, FileWithPath } from '@/types' import { getFilesFromDataTransfer, hasFilesInDataTransfer } from '@/util/file-system-entry' +import { getFileDataTransferDataFromDataTransfer, hasFileDataTransferTypeInDataTransfer, setFileDataTransferDataInDataTransfer } from '@/util/file-data-transfer' /** * Represents the filesystem, bound to moonrakers supplied roots. @@ -796,22 +797,29 @@ export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesM }) } - handleDragStart (source: FileBrowserEntry| FileBrowserEntry[], dataTransfer: DataTransfer) { - if (this.currentRoot === 'gcodes') { - const items = Array.isArray(source) - ? source.filter(item => item.name !== '..') - : [source] + handleDragStart (item: FileBrowserEntry, items: FileBrowserEntry[], dataTransfer: DataTransfer) { + if (item.type === 'file') { + const url = this.createFileUrl(item.name, this.currentPath) + + dataTransfer.setData('text/html', `${item.filename}`) + dataTransfer.setData('text/plain', url) + dataTransfer.setData('text/uri-list', url) + } + setFileDataTransferDataInDataTransfer(dataTransfer, 'files', { + path: this.currentPath, + items: items.map(file => file.name) + }) + + if (this.currentRoot === 'gcodes') { const files = items .filter((item): item is AppFile => item.type === 'file' && this.rootProperties.accepts.includes(`.${item.extension}`)) if (files.length > 0) { - const data = { + setFileDataTransferDataInDataTransfer(dataTransfer, 'jobs', { path: files[0].path, - jobs: files.map(file => file.name) - } - - dataTransfer.setData('x-fluidd-jobs', JSON.stringify(data)) + items: files.map(file => file.name) + }) } } } @@ -950,7 +958,10 @@ export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesM !this.rootProperties.readonly && !this.dragState.browserState && event.dataTransfer && - hasFilesInDataTransfer(event.dataTransfer) + ( + hasFilesInDataTransfer(event.dataTransfer) || + hasFileDataTransferTypeInDataTransfer(event.dataTransfer, 'files') + ) ) { event.preventDefault() @@ -969,13 +980,23 @@ export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesM if ( !this.fileDropRoot && - event.dataTransfer && - !this.rootProperties.readonly + !this.rootProperties.readonly && + event.dataTransfer ) { - const files = await getFilesFromDataTransfer(event.dataTransfer) + if (hasFileDataTransferTypeInDataTransfer(event.dataTransfer, 'files')) { + const files = getFileDataTransferDataFromDataTransfer(event.dataTransfer, 'files') - if (files) { - this.handleUpload(files, false) + for (const file of files.items) { + const src = `${files.path}/${file}` + const dest = `${this.currentPath}/${file}` + SocketActions.serverFilesCopy(src, dest) + } + } else if (hasFilesInDataTransfer(event.dataTransfer)) { + const files = await getFilesFromDataTransfer(event.dataTransfer) + + if (files) { + this.handleUpload(files, false) + } } } } diff --git a/src/components/widgets/filesystem/FileSystemBrowser.vue b/src/components/widgets/filesystem/FileSystemBrowser.vue index d7da58d634..619b45c9ea 100644 --- a/src/components/widgets/filesystem/FileSystemBrowser.vue +++ b/src/components/widgets/filesystem/FileSystemBrowser.vue @@ -442,19 +442,14 @@ export default class FileSystemBrowser extends Mixins(FilesMixin) { this.ghost = document.createElement('div') this.ghost.classList.add('bulk-drag') this.ghost.classList.add((this.$vuetify.theme.dark) ? 'theme--dark' : 'theme--light') - this.ghost.innerHTML = this.$tc('app.file_system.tooltip.move_item', draggedItems.length) + this.ghost.innerHTML = draggedItems.length > 1 + ? this.$tc('app.file_system.tooltip.items_count', draggedItems.length) + : item.name document.body.appendChild(this.ghost) - event.dataTransfer.effectAllowed = 'linkMove' + event.dataTransfer.effectAllowed = 'all' event.dataTransfer.setDragImage(this.ghost, 0, 0) - if (item.type === 'file') { - const filepath = item.path ? `${this.root}/${item.path}` : this.root - const url = this.createFileUrl(item.filename, filepath) - event.dataTransfer.setData('text/html', `${item.filename}`) - event.dataTransfer.setData('text/plain', url) - event.dataTransfer.setData('text/uri-list', url) - } - this.$emit('drag-start', draggedItems, event.dataTransfer) + this.$emit('drag-start', item, draggedItems, event.dataTransfer) } } diff --git a/src/components/widgets/gcode-preview/GcodePreviewCard.vue b/src/components/widgets/gcode-preview/GcodePreviewCard.vue index 998fab33c0..30b1210e45 100644 --- a/src/components/widgets/gcode-preview/GcodePreviewCard.vue +++ b/src/components/widgets/gcode-preview/GcodePreviewCard.vue @@ -149,6 +149,7 @@ import GcodePreview from './GcodePreview.vue' import GcodePreviewParserProgressDialog from './GcodePreviewParserProgressDialog.vue' import type { AppFile } from '@/store/files/types' import type { MinMax } from '@/store/gcodePreview/types' +import { getFileDataTransferDataFromDataTransfer, hasFileDataTransferTypeInDataTransfer } from '@/util/file-data-transfer' @Component({ components: { @@ -396,7 +397,10 @@ export default class GcodePreviewCard extends Mixins(StateMixin, FilesMixin, Bro } handleDragOver (event: DragEvent) { - if (event.dataTransfer?.types.includes('x-fluidd-jobs')) { + if ( + event.dataTransfer && + hasFileDataTransferTypeInDataTransfer(event.dataTransfer, 'jobs') + ) { event.preventDefault() event.dataTransfer.dropEffect = 'link' @@ -412,12 +416,14 @@ export default class GcodePreviewCard extends Mixins(StateMixin, FilesMixin, Bro handleDrop (event: DragEvent) { this.overlay = false - if (event.dataTransfer?.types.includes('x-fluidd-jobs')) { - const data = event.dataTransfer.getData('x-fluidd-jobs') - const files: { path: string, jobs: string[] } = JSON.parse(data) + if ( + event.dataTransfer && + hasFileDataTransferTypeInDataTransfer(event.dataTransfer, 'jobs') + ) { + const files = getFileDataTransferDataFromDataTransfer(event.dataTransfer, 'jobs') const path = files.path ? `gcodes/${files.path}` : 'gcodes' - const file = this.$store.getters['files/getFile'](path, files.jobs[0]) as AppFile | undefined + const file = this.$store.getters['files/getFile'](path, files.items[0]) as AppFile | undefined if (file) { this.loadFile(file) diff --git a/src/components/widgets/job-queue/JobQueue.vue b/src/components/widgets/job-queue/JobQueue.vue index bc3d0221f6..28455c7467 100644 --- a/src/components/widgets/job-queue/JobQueue.vue +++ b/src/components/widgets/job-queue/JobQueue.vue @@ -63,6 +63,7 @@ import JobQueueBrowser from './JobQueueBrowser.vue' import JobQueueContextMenu from './JobQueueContextMenu.vue' import JobQueueMultiplyJobDialog from './JobQueueMultiplyJobDialog.vue' import type { AppTableHeader } from '@/types' +import { getFileDataTransferDataFromDataTransfer, hasFileDataTransferTypeInDataTransfer } from '@/util/file-data-transfer' @Component({ components: { @@ -180,7 +181,10 @@ export default class JobQueue extends Vue { } handleDragOver (event: DragEvent) { - if (event.dataTransfer?.types.includes('x-fluidd-jobs')) { + if ( + event.dataTransfer && + hasFileDataTransferTypeInDataTransfer(event.dataTransfer, 'jobs') + ) { event.preventDefault() event.dataTransfer.dropEffect = 'link' @@ -196,11 +200,13 @@ export default class JobQueue extends Vue { handleDrop (event: DragEvent) { this.overlay = false - if (event.dataTransfer?.types.includes('x-fluidd-jobs')) { - const data = event.dataTransfer.getData('x-fluidd-jobs') - const files: { path: string, jobs: string[] } = JSON.parse(data) + if ( + event.dataTransfer && + hasFileDataTransferTypeInDataTransfer(event.dataTransfer, 'jobs') + ) { + const files = getFileDataTransferDataFromDataTransfer(event.dataTransfer, 'jobs') const filePath = files.path ? `${files.path}/` : '' - const filenames = files.jobs + const filenames = files.items .map(file => `${filePath}${file}`) SocketActions.serverJobQueuePostJob(filenames) diff --git a/src/globals.ts b/src/globals.ts index 1eeff36e50..21ec52d128 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -226,6 +226,10 @@ export const Globals = Object.freeze({ { filename: 'telegram.conf', service: 'moonraker-telegram-bot', link: 'https://github.com/nlef/moonraker-telegram-bot/wiki/Sample-config' }, { suffix: '.cfg', service: 'klipper', link: 'https://www.klipper3d.org/Config_Reference.html' } ], + FILE_DATA_TRANSFER_TYPES: { + files: 'x-fluidd-files', + jobs: 'x-fluidd-jobs' + }, FILTERED_FOLDER_NAMES: ['.git'], FILTERED_FILES_PREFIX: ['.thumbs', 'thumbs'], FILTERED_FILES_EXTENSION: ['.ignoreme'], diff --git a/src/locales/af.yaml b/src/locales/af.yaml index bcec52a57d..ea7ba03d38 100644 --- a/src/locales/af.yaml +++ b/src/locales/af.yaml @@ -102,7 +102,6 @@ app: upload_file: Lêer word opgelaai | Lêers word opgelaai tooltip: low_on_space: Stoorspasie is min - move_item: Skuif item | Skuif {count} items root_disabled: '{root} root is nie beskikbaar nie. Sien die logs.' url: klipper_config: 'https://www.klipper3d.org/Config_Reference.html#%{hash}' diff --git a/src/locales/de.yaml b/src/locales/de.yaml index 5c8946eb6d..6a1107ce3e 100644 --- a/src/locales/de.yaml +++ b/src/locales/de.yaml @@ -115,7 +115,6 @@ app: tooltip: low_on_space: Wenig Speicherplatz auf der Festplatte root_disabled: '{root} ist nicht verfügbar. Bitte überprüfen Sie Ihre Logdateien.' - move_item: Eintrag verschieben | {count} Einträge verschieben url: klipper_config: 'https://www.klipper3d.org/Config_Reference.html#%{hash}' moonraker_config: 'https://moonraker.readthedocs.io/en/latest/configuration/#%{hash}' diff --git a/src/locales/en.yaml b/src/locales/en.yaml index 3a893e8405..84e5e643a3 100644 --- a/src/locales/en.yaml +++ b/src/locales/en.yaml @@ -104,7 +104,7 @@ app: upload_file: Uploading file | Uploading files tooltip: low_on_space: Low on disk space - move_item: Move item | Move {count} items + items_count: '{count} item | {count} items' root_disabled: '{root} root is not available. Please check your logs.' url: klipper_config: 'https://www.klipper3d.org/Config_Reference.html#%{hash}' diff --git a/src/locales/hu.yaml b/src/locales/hu.yaml index 5e88dc7796..86e2f2c6ae 100644 --- a/src/locales/hu.yaml +++ b/src/locales/hu.yaml @@ -105,7 +105,6 @@ app: upload_file: Fájl feltöltése tooltip: low_on_space: Kevés a lemezterület - move_item: Elem mozgatása | {count} elem mozgatása root_disabled: '{root} nem elérhető. Kérjük, ellenőrizd a naplókat.' url: klipper_config: 'https://www.klipper3d.org/Config_Reference.html#%{hash}' diff --git a/src/locales/ja.yaml b/src/locales/ja.yaml index afe0abb69f..b6529cb0c6 100644 --- a/src/locales/ja.yaml +++ b/src/locales/ja.yaml @@ -94,7 +94,6 @@ app: upload_file: ファイルをアップロードする|ファイルをアップロードする tooltip: low_on_space: ディスク容量が少なくなりました。 - move_item: 'アイテムの移動| {count} 個のアイテムを移動' root_disabled: '{root} rootは使用できません。ログを確認してください。' url: klipper_config: 'https://www.klipper3d.org/Config_Reference.html#%{hash}' diff --git a/src/locales/pl.yaml b/src/locales/pl.yaml index 3d3e7cdd7c..e5e5c73ef4 100644 --- a/src/locales/pl.yaml +++ b/src/locales/pl.yaml @@ -102,7 +102,6 @@ app: aby je wgrać tooltip: low_on_space: Mało miejsca na dysku - move_item: Przenieś element | Przenieś {count} elementy root_disabled: '{root} root jest niedostępny. Sprawdź logi.' url: klipper_config: https://www.klipper3d.org/Config_Reference.html#%{hash} diff --git a/src/locales/pt.yaml b/src/locales/pt.yaml index d21274dc4c..bbfa4c0fa3 100644 --- a/src/locales/pt.yaml +++ b/src/locales/pt.yaml @@ -101,7 +101,6 @@ app: duplicate_file: Duplicar ficheiro tooltip: low_on_space: Pouco espaço em disco - move_item: Mover item | Mover {count} itemes general: btn: add: Adicionar diff --git a/src/locales/ru.yaml b/src/locales/ru.yaml index fdee21f56b..d0cb311966 100644 --- a/src/locales/ru.yaml +++ b/src/locales/ru.yaml @@ -99,7 +99,6 @@ app: upload_file: Загрузка файла | Загрузка файлов tooltip: low_on_space: Мало места на диске - move_item: Переместить элемент | Переместить {count} root_disabled: Root недоступен. Проверьте файлы логов. url: klipper_config: 'https://www.klipper3d.org/Config_Reference.html#%{hash}' diff --git a/src/locales/sl.yaml b/src/locales/sl.yaml index 0361eb0f93..a5e1152fa4 100644 --- a/src/locales/sl.yaml +++ b/src/locales/sl.yaml @@ -98,7 +98,6 @@ app: upload_file: Nalaganje datoteke | Nalaganje datotek tooltip: low_on_space: Na disku primanjkuje prostora - move_item: Premakni element | Premakni {count} elementov root_disabled: '{root} root ni na voljo. Prosimo, preverite dnevnike.' url: klipper_config: 'https://www.klipper3d.org/Config_Reference.html#%{hash}' diff --git a/src/locales/zh-CN.yaml b/src/locales/zh-CN.yaml index ba67a309d4..4e0a30a3b0 100644 --- a/src/locales/zh-CN.yaml +++ b/src/locales/zh-CN.yaml @@ -91,7 +91,6 @@ app: upload_file: 上传文件 tooltip: low_on_space: 磁盘空间不足 - move_item: 移动项目|移动{count}个项目 root_disabled: '{root} G代码文件目录不可用,请首先连接打印机或者检查G代码存放目录的路径与读权限' url: klipper_config: 'https://www.klipper3d.org/zh/Config_Reference.html#%{hash}' diff --git a/src/locales/zh-HK.yaml b/src/locales/zh-HK.yaml index 04ed5e68aa..71db93367c 100644 --- a/src/locales/zh-HK.yaml +++ b/src/locales/zh-HK.yaml @@ -87,7 +87,6 @@ app: upload_file: 上傳文件 tooltip: low_on_space: 磁盤空間不足 - move_item: '移動項目 | 移動 {count} 項' root_disabled: '{root} G代碼文件目錄不可用,請首先連接打印機或者檢查G代碼存放目錄的路徑與讀權限' url: klipper_config: 'https://www.klipper3d.org/zh-Hant/Config_Reference.html#%{hash}' diff --git a/src/scss/file-system.scss b/src/scss/file-system.scss index e651185dbd..6771bbf055 100644 --- a/src/scss/file-system.scss +++ b/src/scss/file-system.scss @@ -133,9 +133,11 @@ position: absolute; top: -1000px; right: 0; - width: 170px; + width: auto; + min-width: 170px; height: 40px; align-items: center; padding: 0 16px 0 32px; border-radius: 6px; + text-wrap: nowrap; } diff --git a/src/util/file-data-transfer.ts b/src/util/file-data-transfer.ts new file mode 100644 index 0000000000..72ae4524cd --- /dev/null +++ b/src/util/file-data-transfer.ts @@ -0,0 +1,22 @@ +import { Globals } from '@/globals' + +export type FileDataTransferData = { + path: string, + items: string[] +} + +export const hasFileDataTransferTypeInDataTransfer = (dataTransfer: DataTransfer, type: keyof typeof Globals.FILE_DATA_TRANSFER_TYPES) => { + return dataTransfer.types.includes(Globals.FILE_DATA_TRANSFER_TYPES[type]) +} + +export const setFileDataTransferDataInDataTransfer = (dataTransfer: DataTransfer, type: keyof typeof Globals.FILE_DATA_TRANSFER_TYPES, fileDataTransferData: FileDataTransferData) => { + dataTransfer.setData(Globals.FILE_DATA_TRANSFER_TYPES[type], JSON.stringify(fileDataTransferData)) +} + +export const getFileDataTransferDataFromDataTransfer = (dataTransfer: DataTransfer, type: keyof typeof Globals.FILE_DATA_TRANSFER_TYPES) => { + const data = dataTransfer.getData(Globals.FILE_DATA_TRANSFER_TYPES[type]) + + const fileDataTransferData: FileDataTransferData = JSON.parse(data) + + return fileDataTransferData +}