feat(ui): add a dropzone when uploading files from system
- add a dropzone on the whole screen - correct z-index according design system
This commit is contained in:
@@ -872,7 +872,10 @@
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 50;
|
||||
/* Search suggestions render above `.page-sticky-header` — otherwise the
|
||||
dropdown clips under the action bar on the content pages. Design-token
|
||||
`--z-dropdown` (1000) sits above `--z-sticky` (100) by construction. */
|
||||
z-index: var(--z-dropdown);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0.25rem;
|
||||
@@ -1117,7 +1120,8 @@
|
||||
position: absolute;
|
||||
bottom: calc(100% + 4px);
|
||||
right: 0;
|
||||
z-index: 60;
|
||||
/* Sits above `--z-sticky` for the same reason as `.suggest` above. */
|
||||
z-index: var(--z-dropdown);
|
||||
min-width: 12rem;
|
||||
max-height: 18rem;
|
||||
overflow: auto;
|
||||
|
||||
@@ -902,28 +902,44 @@
|
||||
// breadcrumb) are handled by the existing `onitemdrop` hooks and use
|
||||
// a private `application/x-oxi-item` MIME so the `Files` type check
|
||||
// below never matches them.
|
||||
let systemDropOver = $state(false);
|
||||
// Drag-enter/leave chatter is unavoidable when the drag pointer moves
|
||||
// between the wrapper and its descendants — the browser fires
|
||||
// `dragleave` on the parent BEFORE firing `dragenter` on the child,
|
||||
// so a naive `systemDropOver = false` in the leave handler produces
|
||||
// a false→true flash on every row hover during the drag. Counter
|
||||
// approach: increment on every dragenter, decrement on every
|
||||
// dragleave; the overlay is visible when the count is positive. The
|
||||
// count zeroes only when the drag has truly left the wrapper (or
|
||||
// hit `drop`/`dragend`), so the overlay stays stable throughout.
|
||||
let systemDragDepth = $state(0);
|
||||
const systemDropOver = $derived(systemDragDepth > 0);
|
||||
function isSystemDrag(e: DragEvent): boolean {
|
||||
return !!e.dataTransfer?.types?.includes('Files');
|
||||
}
|
||||
function onSystemDragEnter(e: DragEvent) {
|
||||
if (!isSystemDrag(e)) return;
|
||||
e.preventDefault();
|
||||
systemDropOver = true;
|
||||
systemDragDepth++;
|
||||
}
|
||||
function onSystemDragOver(e: DragEvent) {
|
||||
if (!isSystemDrag(e)) return;
|
||||
// preventDefault on `dragover` is what tells the browser this
|
||||
// element accepts drops — without it, `drop` never fires and
|
||||
// the pointer shows the OS "no-drop" cursor.
|
||||
e.preventDefault();
|
||||
if (e.dataTransfer) e.dataTransfer.dropEffect = enableSystemDrop ? 'copy' : 'none';
|
||||
}
|
||||
function onSystemDragLeave(e: DragEvent) {
|
||||
if (!isSystemDrag(e)) return;
|
||||
systemDropOver = false;
|
||||
if (systemDragDepth > 0) systemDragDepth--;
|
||||
}
|
||||
function onSystemDrop(e: DragEvent) {
|
||||
if (!isSystemDrag(e)) return;
|
||||
e.preventDefault();
|
||||
systemDropOver = false;
|
||||
// Drop ends the drag; force-clear regardless of counter state
|
||||
// (a stray unbalanced dragenter would otherwise leave the
|
||||
// overlay stuck on).
|
||||
systemDragDepth = 0;
|
||||
if (enableSystemDrop && onsystemdrop) {
|
||||
onsystemdrop(e);
|
||||
} else if (!enableSystemDrop) {
|
||||
@@ -1138,7 +1154,6 @@
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="rl-root"
|
||||
class:rl-root--drop-over={systemDropOver && enableSystemDrop}
|
||||
class:rl-root--rubberbanding={rubberband !== null}
|
||||
bind:this={rlRoot}
|
||||
onpointerdown={onRootPointerDown}
|
||||
@@ -1330,6 +1345,24 @@
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
{/if}
|
||||
|
||||
<!-- System-drop overlay. Renders on top of the list ONLY while the
|
||||
user is dragging OS files over the wrapper AND this surface
|
||||
accepts them (`enableSystemDrop`). Filled tint + centred
|
||||
icon+message so the drop target is unmistakable — the dashed
|
||||
border alone read as decoration on busy folders. `pointer-events:
|
||||
none` on the container keeps it inert (drag events still hit
|
||||
`.rl-root` underneath so `dragleave`/`drop` fire correctly). -->
|
||||
{#if systemDropOver && enableSystemDrop}
|
||||
<div class="rl-drop-overlay" aria-hidden="true">
|
||||
<div class="rl-drop-overlay__inner">
|
||||
<Icon name="cloud-arrow-up" class="rl-drop-overlay__icon" />
|
||||
<span class="rl-drop-overlay__label">
|
||||
{t('files.drop_to_upload', 'Drop files here to upload')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- /.rl-root -->
|
||||
|
||||
@@ -1419,13 +1452,50 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.rl-root--drop-over::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
/* Viewport-fixed drop overlay. `position: fixed` (not absolute) so
|
||||
it covers the whole visible browser window regardless of the
|
||||
user's scroll position — an absolute-inset-0 inside `.rl-root`
|
||||
would center the card at the middle of the FULL list height,
|
||||
which sits above the fold on a scrolled folder. The dashed border
|
||||
also gets painted at the true viewport edge, so the sticky
|
||||
action-bar + breadcrumb are covered rather than clipping the
|
||||
border. `pointer-events: none` so drag events still fall through
|
||||
to `.rl-root`'s handlers underneath.
|
||||
`--z-overlay` beats `--z-sticky` (page chrome) + `--z-dropdown`
|
||||
(search suggestions); stays below `--z-modal` so a modal opened
|
||||
concurrently still wins. */
|
||||
.rl-drop-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: color-mix(in srgb, var(--color-accent) 12%, transparent);
|
||||
border: 2px dashed var(--color-accent);
|
||||
border-radius: var(--radius-md);
|
||||
pointer-events: none;
|
||||
z-index: var(--z-overlay);
|
||||
}
|
||||
|
||||
.rl-drop-overlay__inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-6) var(--space-8);
|
||||
color: var(--color-accent);
|
||||
background: var(--color-bg-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.rl-drop-overlay :global(.rl-drop-overlay__icon) {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.rl-drop-overlay__label {
|
||||
font-weight: var(--weight-semibold);
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
/* ── Rubberband (marquee) selection ────────────────────────────
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "تاريخ التعديل",
|
||||
"no_files": "لا توجد ملفات في هذا المجلد",
|
||||
"empty_hint": "ارفع ملفات أو أنشئ مجلدات للبدء",
|
||||
"drop_to_upload": "أفلت الملفات هنا للرفع",
|
||||
"loading": "جارٍ تحميل الملفات…",
|
||||
"view_grid": "عرض شبكي",
|
||||
"view_list": "عرض قائمة",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "Geändert",
|
||||
"no_files": "Keine Dateien in diesem Ordner",
|
||||
"empty_hint": "Laden Sie Dateien hoch oder erstellen Sie Ordner, um loszulegen",
|
||||
"drop_to_upload": "Dateien zum Hochladen hier ablegen",
|
||||
"loading": "Dateien werden geladen…",
|
||||
"view_grid": "Rasteransicht",
|
||||
"view_list": "Listenansicht",
|
||||
|
||||
@@ -444,6 +444,7 @@
|
||||
"modified": "Modified",
|
||||
"no_files": "No files in this folder",
|
||||
"empty_hint": "Upload files or create folders to get started",
|
||||
"drop_to_upload": "Drop files here to upload",
|
||||
"loading": "Loading files…",
|
||||
"view_grid": "Grid view",
|
||||
"view_list": "List view",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "Modificado",
|
||||
"no_files": "No hay archivos en esta carpeta",
|
||||
"empty_hint": "Sube archivos o crea carpetas para comenzar",
|
||||
"drop_to_upload": "Arrastra archivos aquí para subirlos",
|
||||
"loading": "Cargando archivos…",
|
||||
"view_grid": "Vista de cuadrícula",
|
||||
"view_list": "Vista de lista",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "تاریخ تغییر",
|
||||
"no_files": "هنوز هیچ پروندهای در این پوشه وجود ندارد",
|
||||
"empty_hint": "برای شروع، فایلها را آپلود کنید یا پوشه بسازید",
|
||||
"drop_to_upload": "برای بارگذاری، فایلها را اینجا رها کنید",
|
||||
"loading": "در حال بارگذاری فایلها…",
|
||||
"view_grid": "نمای شبکهای",
|
||||
"view_list": "نمای فهرستی",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "Modifié",
|
||||
"no_files": "Aucun fichier dans ce dossier",
|
||||
"empty_hint": "Téléversez des fichiers ou créez des dossiers pour commencer",
|
||||
"drop_to_upload": "Déposez les fichiers ici pour les téléverser",
|
||||
"loading": "Chargement des fichiers…",
|
||||
"view_grid": "Vue en grille",
|
||||
"view_list": "Vue en liste",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "संशोधित",
|
||||
"no_files": "इस फ़ोल्डर में कोई फ़ाइल नहीं",
|
||||
"empty_hint": "शुरू करने के लिए ह़ैलें अपलोड करें या होल्डर बनाएँ",
|
||||
"drop_to_upload": "अपलोड करने के लिए फ़ाइलें यहाँ छोड़ें",
|
||||
"loading": "फ़ाइलें लोड हो रही हैं…",
|
||||
"view_grid": "ग्रिड दृश्य",
|
||||
"view_list": "सूची दृश्य",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "Modificato",
|
||||
"no_files": "Nessun file in questa cartella",
|
||||
"empty_hint": "Carica file o crea cartelle per iniziare",
|
||||
"drop_to_upload": "Trascina i file qui per caricarli",
|
||||
"loading": "Caricamento file…",
|
||||
"view_grid": "Visualizzazione griglia",
|
||||
"view_list": "Visualizzazione elenco",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "更新日",
|
||||
"no_files": "このフォルダにファイルはありません",
|
||||
"empty_hint": "ファイルをアップロードするかフォルダを作成して始めましょう",
|
||||
"drop_to_upload": "アップロードするファイルをここにドロップ",
|
||||
"loading": "ファイルを読み込み中…",
|
||||
"view_grid": "グリッド表示",
|
||||
"view_list": "リスト表示",
|
||||
|
||||
@@ -409,6 +409,7 @@
|
||||
"modified": "수정일",
|
||||
"no_files": "이 폴더에 파일이 없습니다",
|
||||
"empty_hint": "파일을 업로드하거나 폴더를 만들어 시작하세요",
|
||||
"drop_to_upload": "업로드할 파일을 여기에 놓으세요",
|
||||
"loading": "파일 로딩 중…",
|
||||
"view_grid": "그리드 보기",
|
||||
"view_list": "목록 보기",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "Gewijzigd",
|
||||
"no_files": "Geen bestanden in deze map",
|
||||
"empty_hint": "Upload bestanden of maak mappen aan om te beginnen",
|
||||
"drop_to_upload": "Sleep bestanden hier om te uploaden",
|
||||
"loading": "Bestanden laden…",
|
||||
"view_grid": "Rasterweergave",
|
||||
"view_list": "Lijstweergave",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "Zmodyfikowano",
|
||||
"no_files": "Brak plików w tym folderze",
|
||||
"empty_hint": "Prześlij pliki lub utwórz foldery, aby rozpocząć",
|
||||
"drop_to_upload": "Upuść pliki tutaj, aby wysłać",
|
||||
"loading": "Ładowanie plików…",
|
||||
"view_grid": "Widok siatki",
|
||||
"view_list": "Widok listy",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "Modificado",
|
||||
"no_files": "Nenhum arquivo nesta pasta",
|
||||
"empty_hint": "Envie arquivos ou crie pastas para começar",
|
||||
"drop_to_upload": "Solte arquivos aqui para enviar",
|
||||
"loading": "Carregando arquivos…",
|
||||
"view_grid": "Visualização em grade",
|
||||
"view_list": "Visualização em lista",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "Изменён",
|
||||
"no_files": "В этой папке нет файлов",
|
||||
"empty_hint": "Загрузите файлы или создайте папки, чтобы начать",
|
||||
"drop_to_upload": "Перетащите файлы сюда для загрузки",
|
||||
"loading": "Загрузка файлов…",
|
||||
"view_grid": "Сетка",
|
||||
"view_list": "Список",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "修改日期",
|
||||
"no_files": "此資料夾中沒有檔案",
|
||||
"empty_hint": "上傳檔案或建立資料夾以開始使用",
|
||||
"drop_to_upload": "將檔案拖放到此處上傳",
|
||||
"loading": "正在載入檔案…",
|
||||
"view_grid": "網格檢視",
|
||||
"view_list": "列表檢視",
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
"modified": "修改日期",
|
||||
"no_files": "此文件夹中没有文件",
|
||||
"empty_hint": "上传文件或创建文件夹以开始使用",
|
||||
"drop_to_upload": "将文件拖放到此处上传",
|
||||
"loading": "正在加载文件…",
|
||||
"view_grid": "网格视图",
|
||||
"view_list": "列表视图",
|
||||
|
||||
Reference in New Issue
Block a user