feat: add drag-and-drop file upload to Cloud folders
Drag files over the native file browser area to see a drop zone overlay with dashed orange border. Dropping files triggers the existing upload handler. Uses debounced dragleave to prevent flicker between children. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,7 @@ After getting Claude Max OAuth working on the live server, hardening the deploy
|
||||
- **Change**: Add `getUsage()` method to filebrowser-client. In Home.vue, replace hardcoded "2.4 GB" and "5" folders with real data from FileBrowser API. Add `formatBytes()` helper. Show loading state while fetching.
|
||||
- **Verify**: Home Cloud card shows real storage numbers from FileBrowser
|
||||
|
||||
### Task 8: Cloud file drag-and-drop upload
|
||||
### Task 8: Cloud file drag-and-drop upload [DONE]
|
||||
- **Files**: `neode-ui/src/views/CloudFolder.vue`, `neode-ui/src/style.css`
|
||||
- **Change**: Add drag-and-drop overlay with `@dragover.prevent` and `@drop.prevent`. Show visual drop zone when dragging. Extract files from `dataTransfer` and call existing `handleUpload()`.
|
||||
- **Verify**: Drag a file over CloudFolder — drop zone overlay appears, dropping uploads file
|
||||
|
||||
@@ -1220,3 +1220,31 @@ html:has(body.video-background-active)::before {
|
||||
border-radius: 2px;
|
||||
transition: width 0.3s linear;
|
||||
}
|
||||
|
||||
/* ── Cloud Drag-and-Drop Overlay ──── */
|
||||
|
||||
.cloud-drop-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
border-radius: 0.75rem;
|
||||
border: 2px dashed rgba(251, 146, 60, 0.6);
|
||||
animation: drop-overlay-in 0.2s ease-out;
|
||||
}
|
||||
.cloud-drop-overlay-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
@keyframes drop-overlay-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@@ -72,7 +72,23 @@
|
||||
</div>
|
||||
|
||||
<!-- Native File Browser (for FileBrowser-backed sections) -->
|
||||
<div v-else-if="useNativeUI" class="flex-1 min-h-0 flex flex-col">
|
||||
<div
|
||||
v-else-if="useNativeUI"
|
||||
class="flex-1 min-h-0 flex flex-col relative"
|
||||
@dragover.prevent="onDragOver"
|
||||
@dragleave="onDragLeave"
|
||||
@drop.prevent="onDrop"
|
||||
>
|
||||
<!-- Drag-and-drop overlay -->
|
||||
<div v-if="draggingOver" class="cloud-drop-overlay">
|
||||
<div class="cloud-drop-overlay-inner">
|
||||
<svg class="w-12 h-12 text-white/80 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||
</svg>
|
||||
<p class="text-lg font-medium text-white/90">Drop files to upload</p>
|
||||
<p class="text-sm text-white/50">Files will be added to the current folder</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Upload progress -->
|
||||
<div v-if="uploading" class="glass-card p-3 mb-3 flex items-center gap-3">
|
||||
<div class="w-5 h-5 border-2 border-white/20 border-t-white/80 rounded-full animate-spin"></div>
|
||||
@@ -274,6 +290,27 @@ watch(useNativeUI, async (native) => {
|
||||
}, { immediate: true })
|
||||
|
||||
const uploadError = ref<string | null>(null)
|
||||
const draggingOver = ref(false)
|
||||
let dragLeaveTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
function onDragOver() {
|
||||
if (dragLeaveTimer) { clearTimeout(dragLeaveTimer); dragLeaveTimer = null }
|
||||
draggingOver.value = true
|
||||
}
|
||||
|
||||
function onDragLeave() {
|
||||
// Debounce to avoid flicker when dragging between child elements
|
||||
if (dragLeaveTimer) clearTimeout(dragLeaveTimer)
|
||||
dragLeaveTimer = setTimeout(() => { draggingOver.value = false }, 100)
|
||||
}
|
||||
|
||||
function onDrop(e: DragEvent) {
|
||||
draggingOver.value = false
|
||||
if (dragLeaveTimer) { clearTimeout(dragLeaveTimer); dragLeaveTimer = null }
|
||||
const dt = e.dataTransfer
|
||||
if (!dt?.files?.length) return
|
||||
handleUpload(Array.from(dt.files))
|
||||
}
|
||||
|
||||
async function handleUpload(files: File[]) {
|
||||
uploading.value = true
|
||||
|
||||
Reference in New Issue
Block a user