diff --git a/frontend/src/components/upload/UploadIntakeControls.vue b/frontend/src/components/upload/UploadIntakeControls.vue
index c0fd2ef..cf29350 100644
--- a/frontend/src/components/upload/UploadIntakeControls.vue
+++ b/frontend/src/components/upload/UploadIntakeControls.vue
@@ -38,6 +38,16 @@
:metadata-error-count="metadataErrorCount"
@upload="onUploadFromModal"
/>
+
+
+
+
@@ -77,19 +87,28 @@ const emit = defineEmits<{
const fileInputRef = ref(null);
const configureModalOpen = ref(false);
+const fileDragDepth = ref(0);
onMounted(() => {
+ window.addEventListener('dragenter', onWindowDragEnter);
+ window.addEventListener('dragleave', onWindowDragLeave);
window.addEventListener('dragover', onWindowDragOver);
window.addEventListener('drop', onWindowDrop);
+ window.addEventListener('dragend', onWindowDragEnd);
window.addEventListener('paste', onWindowPaste);
});
onBeforeUnmount(() => {
+ window.removeEventListener('dragenter', onWindowDragEnter);
+ window.removeEventListener('dragleave', onWindowDragLeave);
window.removeEventListener('dragover', onWindowDragOver);
window.removeEventListener('drop', onWindowDrop);
+ window.removeEventListener('dragend', onWindowDragEnd);
window.removeEventListener('paste', onWindowPaste);
});
+const showFileDragOverlay = computed(() => fileDragDepth.value > 0 && !props.disabled);
+
const maxImageSizeLabel = computed(() => prettyBytes(maxImageBytes, { binary: true, maximumFractionDigits: 1 }));
const emitPicked = (picked: File[]): void => {
@@ -117,15 +136,37 @@ const isFileDrag = (event: DragEvent): boolean => {
return Boolean(event.dataTransfer?.types.includes('Files'));
};
+const onWindowDragEnter = (event: DragEvent): void => {
+ if (props.disabled || !isFileDrag(event)) {
+ return;
+ }
+
+ fileDragDepth.value += 1;
+};
+
+const onWindowDragLeave = (event: DragEvent): void => {
+ if (!isFileDrag(event)) {
+ return;
+ }
+
+ fileDragDepth.value = Math.max(0, fileDragDepth.value - 1);
+};
+
const onWindowDragOver = (event: DragEvent): void => {
if (props.disabled || !isFileDrag(event)) {
return;
}
event.preventDefault();
+
+ if (fileDragDepth.value === 0) {
+ fileDragDepth.value = 1;
+ }
};
const onWindowDrop = (event: DragEvent): void => {
+ resetFileDragDepth();
+
if (props.disabled) {
return;
}
@@ -137,6 +178,10 @@ const onWindowDrop = (event: DragEvent): void => {
emitPicked(picked);
};
+const onWindowDragEnd = (): void => {
+ resetFileDragDepth();
+};
+
const isPasswordInputTarget = (target: EventTarget | null): boolean => {
if (!(target instanceof HTMLElement)) {
return false;
@@ -175,5 +220,9 @@ const closeConfigureModal = (): void => {
configureModalOpen.value = false;
};
+const resetFileDragDepth = (): void => {
+ fileDragDepth.value = 0;
+};
+
defineExpose({ clearInput, closeConfigureModal });