From 4295c3c488bfbd533b6f1de7aa462cce6948a4d9 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Wed, 14 Oct 2020 18:06:40 -0400 Subject: [PATCH] Scope global file upload to file library Fixes #399, fixes #262 --- app/src/main.ts | 11 +- app/src/modules/files/routes/collection.vue | 201 +++++++++++++++++++- app/src/utils/upload-file/upload-file.ts | 2 +- app/src/utils/upload-files/upload-files.ts | 2 +- app/src/views/private/private-view.vue | 200 +------------------ 5 files changed, 208 insertions(+), 208 deletions(-) diff --git a/app/src/main.ts b/app/src/main.ts index e4f2fdfb4a..dcdd12aa82 100644 --- a/app/src/main.ts +++ b/app/src/main.ts @@ -52,12 +52,7 @@ import { registerDisplays } from './displays/register'; import App from './app.vue'; async function init() { - await Promise.all([ - registerInterfaces(), - registerDisplays(), - registerLayouts(), - loadModules(), - ]); + await Promise.all([registerInterfaces(), registerDisplays(), registerLayouts(), loadModules()]); Vue.config.productionTip = false; @@ -75,4 +70,8 @@ async function init() { console.groupEnd(); } +// Prevent the browser from opening files that are dragged on the window +window.addEventListener('dragover', (e) => e.preventDefault(), false); +window.addEventListener('drop', (e) => e.preventDefault(), false); + init(); diff --git a/app/src/modules/files/routes/collection.vue b/app/src/modules/files/routes/collection.vue index d920efec74..a45217e135 100644 --- a/app/src/modules/files/routes/collection.vue +++ b/app/src/modules/files/routes/collection.vue @@ -1,5 +1,5 @@ + + @@ -152,9 +159,11 @@ import FolderPicker from '../components/folder-picker.vue'; import emitter, { Events } from '@/events'; import router from '@/router'; import Vue from 'vue'; -import { useUserStore } from '@/stores'; +import { useNotificationsStore, useUserStore } from '@/stores'; import { subDays } from 'date-fns'; import useFolders from '../composables/use-folders'; +import useEventListener from '@/composables/use-event-listener'; +import uploadFiles from '@/utils/upload-files'; type Item = { [field: string]: any; @@ -179,6 +188,7 @@ export default defineComponent({ const selection = ref([]); const userStore = useUserStore(); + const notificationsStore = useNotificationsStore(); const { layout, layoutOptions, layoutQuery, filters, searchQuery } = usePreset(ref('directus_files')); const { batchLink } = useLinks(); @@ -242,6 +252,13 @@ export default defineComponent({ onMounted(() => emitter.on(Events.upload, refresh)); onUnmounted(() => emitter.off(Events.upload, refresh)); + const { onDragEnter, onDragLeave, onDrop, onDragOver, showDropEffect, dragging } = useFileUpload(); + + useEventListener(window, 'dragenter', onDragEnter); + useEventListener(window, 'dragover', onDragOver); + useEventListener(window, 'dragleave', onDragLeave); + useEventListener(window, 'drop', onDrop); + return { batchDelete, batchLink, @@ -264,6 +281,11 @@ export default defineComponent({ selectedFolder, refresh, clearFilters, + onDragEnter, + onDragLeave, + showDropEffect, + onDrop, + dragging, }; function useBatchDelete() { @@ -381,6 +403,133 @@ export default defineComponent({ filters.value = []; searchQuery.value = null; } + + function useFileUpload() { + const showDropEffect = ref(false); + + let dragNotificationID: string; + let fileUploadNotificationID: string; + + const dragCounter = ref(0); + + const dragging = computed(() => dragCounter.value > 0); + + return { onDragEnter, onDragLeave, onDrop, onDragOver, showDropEffect, dragging }; + + function enableDropEffect() { + showDropEffect.value = true; + + dragNotificationID = notificationsStore.add({ + title: i18n.t('drop_to_upload'), + icon: 'cloud_upload', + type: 'info', + persist: true, + closeable: false, + }); + } + + function disableDropEffect() { + showDropEffect.value = false; + + if (dragNotificationID) { + notificationsStore.remove(dragNotificationID); + } + } + + function onDragEnter(event: DragEvent) { + if (!event.dataTransfer) return; + if (event.dataTransfer?.types.indexOf('Files') === -1) return; + + event.preventDefault(); + dragCounter.value++; + + const isDropzone = event.target && (event.target as HTMLElement).getAttribute?.('data-dropzone') === ''; + + if (dragCounter.value === 1 && showDropEffect.value === false && isDropzone === false) { + enableDropEffect(); + } + + if (isDropzone) { + disableDropEffect(); + dragCounter.value = 0; + } + } + + function onDragOver(event: DragEvent) { + if (!event.dataTransfer) return; + if (event.dataTransfer?.types.indexOf('Files') === -1) return; + + event.preventDefault(); + } + + function onDragLeave(event: DragEvent) { + if (!event.dataTransfer) return; + if (event.dataTransfer?.types.indexOf('Files') === -1) return; + + event.preventDefault(); + dragCounter.value--; + + if (dragCounter.value === 0) { + disableDropEffect(); + } + + if (event.target && (event.target as HTMLElement).getAttribute?.('data-dropzone') === '') { + enableDropEffect(); + dragCounter.value = 1; + } + } + + async function onDrop(event: DragEvent) { + if (!event.dataTransfer) return; + if (event.dataTransfer?.types.indexOf('Files') === -1) return; + + event.preventDefault(); + showDropEffect.value = false; + + dragCounter.value = 0; + + if (dragNotificationID) { + notificationsStore.remove(dragNotificationID); + } + + const files = [...event.dataTransfer.files]; + + fileUploadNotificationID = notificationsStore.add({ + title: i18n.tc('upload_file_indeterminate', files.length, { + done: 0, + total: files.length, + }), + type: 'info', + persist: true, + closeable: false, + loading: true, + }); + + await uploadFiles(files, { + preset: { + folder: props.queryFilters?.folder || null, + }, + onProgressChange: (progress) => { + const percentageDone = progress.reduce((val, cur) => (val += cur)) / progress.length; + + const total = files.length; + const done = progress.filter((p) => p === 100).length; + + notificationsStore.update(fileUploadNotificationID, { + title: i18n.tc('upload_file_indeterminate', files.length, { + done, + total, + }), + loading: false, + progress: percentageDone, + }); + }, + }); + + notificationsStore.remove(fileUploadNotificationID); + emitter.emit(Events.upload); + } + } }, }); @@ -414,4 +563,52 @@ export default defineComponent({ .layout { --layout-offset-top: 64px; } + +.drop-border { + position: fixed; + z-index: 500; + background-color: var(--primary); + + &.top, + &.bottom { + width: 100%; + height: 4px; + } + + &.left, + &.right { + width: 4px; + height: 100%; + } + + &.top { + top: 0; + left: 0; + } + + &.right { + top: 0; + right: 0; + } + + &.bottom { + bottom: 0; + left: 0; + } + + &.left { + top: 0; + left: 0; + } +} + +.dragging { + ::v-deep * { + pointer-events: none; + } + + ::v-deep [data-dropzone] { + pointer-events: all; + } +} diff --git a/app/src/utils/upload-file/upload-file.ts b/app/src/utils/upload-file/upload-file.ts index d293cdcf10..138b4e8e3b 100644 --- a/app/src/utils/upload-file/upload-file.ts +++ b/app/src/utils/upload-file/upload-file.ts @@ -10,7 +10,7 @@ export default async function uploadFile( options?: { onProgressChange?: (percentage: number) => void; notifications?: boolean; - preset?: Record; + preset?: Record; } ) { const progressHandler = options?.onProgressChange || (() => undefined); diff --git a/app/src/utils/upload-files/upload-files.ts b/app/src/utils/upload-files/upload-files.ts index 27064096f8..018554e184 100644 --- a/app/src/utils/upload-files/upload-files.ts +++ b/app/src/utils/upload-files/upload-files.ts @@ -7,7 +7,7 @@ export default async function uploadFiles( options?: { onProgressChange?: (percentages: number[]) => void; notifications?: boolean; - preset?: Record; + preset?: Record; } ) { const progressHandler = options?.onProgressChange || (() => undefined); diff --git a/app/src/views/private/private-view.vue b/app/src/views/private/private-view.vue index 0b03b0dc15..6c2ff97460 100644 --- a/app/src/views/private/private-view.vue +++ b/app/src/views/private/private-view.vue @@ -1,5 +1,5 @@ @@ -62,11 +55,8 @@ import ProjectInfo from './components/project-info'; import DrawerButton from './components/drawer-button/'; import NotificationsGroup from './components/notifications-group/'; import NotificationsPreview from './components/notifications-preview/'; -import { useNotificationsStore, useUserStore, useAppStore } from '@/stores'; -import NotificationItem from './components/notification-item'; -import uploadFiles from '@/utils/upload-files'; +import { useUserStore, useAppStore } from '@/stores'; import i18n from '@/lang'; -import useEventListener from '@/composables/use-event-listener'; import emitter, { Events } from '@/events'; export default defineComponent({ @@ -78,7 +68,6 @@ export default defineComponent({ DrawerButton, NotificationsGroup, NotificationsPreview, - NotificationItem, }, props: { title: { @@ -90,7 +79,6 @@ export default defineComponent({ const navOpen = ref(false); const contentEl = ref(); const userStore = useUserStore(); - const notificationsStore = useNotificationsStore(); const appStore = useAppStore(); const notificationsPreviewActive = ref(false); @@ -103,151 +91,15 @@ export default defineComponent({ provide('main-element', contentEl); - const { onDragEnter, onDragLeave, onDrop, onDragOver, showDropEffect, dragging } = useFileUpload(); - - useEventListener(window, 'dragenter', onDragEnter); - useEventListener(window, 'dragover', onDragOver); - useEventListener(window, 'dragleave', onDragLeave); - useEventListener(window, 'drop', onDrop); - return { navOpen, contentEl, theme, - onDragEnter, - onDragLeave, - showDropEffect, - onDrop, - dragging, drawerOpen, openDrawer, notificationsPreviewActive, }; - function useFileUpload() { - const showDropEffect = ref(false); - - let dragNotificationID: string; - let fileUploadNotificationID: string; - - const dragCounter = ref(0); - - const dragging = computed(() => dragCounter.value > 0); - - return { onDragEnter, onDragLeave, onDrop, onDragOver, showDropEffect, dragging }; - - function enableDropEffect() { - showDropEffect.value = true; - - dragNotificationID = notificationsStore.add({ - title: i18n.t('drop_to_upload'), - icon: 'cloud_upload', - type: 'info', - persist: true, - closeable: false, - }); - } - - function disableDropEffect() { - showDropEffect.value = false; - - if (dragNotificationID) { - notificationsStore.remove(dragNotificationID); - } - } - - function onDragEnter(event: DragEvent) { - if (!event.dataTransfer) return; - if (event.dataTransfer?.types.indexOf('Files') === -1) return; - - event.preventDefault(); - dragCounter.value++; - - const isDropzone = event.target && (event.target as HTMLElement).getAttribute?.('data-dropzone') === ''; - - if (dragCounter.value === 1 && showDropEffect.value === false && isDropzone === false) { - enableDropEffect(); - } - - if (isDropzone) { - disableDropEffect(); - dragCounter.value = 0; - } - } - - function onDragOver(event: DragEvent) { - if (!event.dataTransfer) return; - if (event.dataTransfer?.types.indexOf('Files') === -1) return; - - event.preventDefault(); - } - - function onDragLeave(event: DragEvent) { - if (!event.dataTransfer) return; - if (event.dataTransfer?.types.indexOf('Files') === -1) return; - - event.preventDefault(); - dragCounter.value--; - - if (dragCounter.value === 0) { - disableDropEffect(); - } - - if (event.target && (event.target as HTMLElement).getAttribute?.('data-dropzone') === '') { - enableDropEffect(); - dragCounter.value = 1; - } - } - - async function onDrop(event: DragEvent) { - if (!event.dataTransfer) return; - if (event.dataTransfer?.types.indexOf('Files') === -1) return; - - event.preventDefault(); - showDropEffect.value = false; - - dragCounter.value = 0; - - if (dragNotificationID) { - notificationsStore.remove(dragNotificationID); - } - - const files = [...event.dataTransfer.files]; - - fileUploadNotificationID = notificationsStore.add({ - title: i18n.tc('upload_file_indeterminate', files.length, { - done: 0, - total: files.length, - }), - type: 'info', - persist: true, - closeable: false, - loading: true, - }); - - await uploadFiles(files, { - onProgressChange: (progress) => { - const percentageDone = progress.reduce((val, cur) => (val += cur)) / progress.length; - - const total = files.length; - const done = progress.filter((p) => p === 100).length; - - notificationsStore.update(fileUploadNotificationID, { - title: i18n.tc('upload_file_indeterminate', files.length, { - done, - total, - }), - loading: false, - progress: percentageDone, - }); - }, - }); - - notificationsStore.remove(fileUploadNotificationID); - emitter.emit(Events.upload); - } - } - function openDrawer(event: PointerEvent) { if (event.target && (event.target as HTMLElement).classList.contains('close') === false) { drawerOpen.value = true; @@ -391,52 +243,4 @@ export default defineComponent({ --content-padding-bottom: 132px; } } - -.dragging { - ::v-deep * { - pointer-events: none; - } - - ::v-deep [data-dropzone] { - pointer-events: all; - } -} - -.drop-border { - position: fixed; - z-index: 500; - background-color: var(--primary); - - &.top, - &.bottom { - width: 100%; - height: 4px; - } - - &.left, - &.right { - width: 4px; - height: 100%; - } - - &.top { - top: 0; - left: 0; - } - - &.right { - top: 0; - right: 0; - } - - &.bottom { - bottom: 0; - left: 0; - } - - &.left { - top: 0; - left: 0; - } -}