feat(ui): show a notification if user try to drop a file in another section than /files
This commit is contained in:
@@ -92,6 +92,8 @@
|
||||
import DisplayModeControls from '$lib/components/DisplayModeControls.svelte';
|
||||
import UserVignette from '$lib/components/UserVignette.svelte';
|
||||
import VirtualList from '$lib/components/VirtualList.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import { t } from '$lib/i18n/index.svelte';
|
||||
import { ui } from '$lib/stores/ui.svelte';
|
||||
import { files as filesStore } from '$lib/stores/files.svelte';
|
||||
@@ -927,7 +929,13 @@
|
||||
// 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';
|
||||
// `dropEffect = 'none'` would tell the browser to REJECT the
|
||||
// drop before `drop` fires — the toast/notification path in
|
||||
// `onSystemDrop` would never run for wrong-zone drops. Always
|
||||
// accept at the pointer level; the drop handler decides
|
||||
// whether to upload (`enableSystemDrop`) or fire the
|
||||
// "go to Files" toast.
|
||||
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
function onSystemDragLeave(e: DragEvent) {
|
||||
if (!isSystemDrag(e)) return;
|
||||
@@ -949,7 +957,19 @@
|
||||
'Uploads only work in Files — open the Files section and drop there.'
|
||||
),
|
||||
'warning',
|
||||
6000
|
||||
6000,
|
||||
true,
|
||||
{
|
||||
action: {
|
||||
label: t('resource_list.wrong_drop_zone_action', 'Go to Files'),
|
||||
// One-click recovery from a mis-drop: land the user in
|
||||
// /files so they can re-drag from the OS. We don't
|
||||
// re-attach the dropped files (browsers throw away
|
||||
// DataTransfer once the drop event returns), so this
|
||||
// is the best we can offer without a second drag.
|
||||
onClick: () => goto(resolve('/files'))
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,18 @@
|
||||
{#each ui.toasts as toast (toast.id)}
|
||||
<div class="toast toast--{toast.kind}" role="status" data-testid={`toaster-toast-${toast.id}`}>
|
||||
<span class="toast__msg">{toast.message}</span>
|
||||
{#if toast.action}
|
||||
<button
|
||||
class="toast__action"
|
||||
data-testid={`toaster-action-btn-${toast.id}`}
|
||||
onclick={() => {
|
||||
toast.action?.onClick();
|
||||
ui.dismiss(toast.id);
|
||||
}}
|
||||
>
|
||||
{toast.action.label}
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
class="toast__close"
|
||||
data-testid={`toaster-dismiss-btn-${toast.id}`}
|
||||
@@ -73,6 +85,22 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.toast__action {
|
||||
flex-shrink: 0;
|
||||
background: var(--color-accent);
|
||||
color: var(--color-accent-contrast);
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-1-5) var(--space-3);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--weight-medium);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toast__action:hover {
|
||||
filter: brightness(0.95);
|
||||
}
|
||||
|
||||
.toast__close {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
@@ -6,10 +6,19 @@
|
||||
*/
|
||||
export type ToastKind = 'info' | 'success' | 'error' | 'warning';
|
||||
|
||||
export interface ToastAction {
|
||||
/** Button label — should be short (≤ 20 chars). */
|
||||
label: string;
|
||||
/** Invoked when the button is clicked; the toast auto-dismisses after. */
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export interface Toast {
|
||||
id: number;
|
||||
message: string;
|
||||
kind: ToastKind;
|
||||
/** Optional inline action (e.g. "Go to Files" on a wrong-drop-zone toast). */
|
||||
action?: ToastAction;
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
@@ -74,10 +83,21 @@ class UiStore {
|
||||
/**
|
||||
* Raise a toast and record a notification. `at` is stamped from the clock at
|
||||
* call time; pass `record: false` for purely transient messages.
|
||||
*
|
||||
* The optional `opts.action` renders an inline button in the toast (e.g.
|
||||
* "Go to Files" on a wrong-drop-zone warning); the callback fires on
|
||||
* click and the toast auto-dismisses right after so a caller doesn't have
|
||||
* to manage the id.
|
||||
*/
|
||||
notify(message: string, kind: ToastKind = 'info', timeoutMs = 4000, record = true): number {
|
||||
notify(
|
||||
message: string,
|
||||
kind: ToastKind = 'info',
|
||||
timeoutMs = 4000,
|
||||
record = true,
|
||||
opts: { action?: ToastAction } = {}
|
||||
): number {
|
||||
const id = ++this.#seq;
|
||||
this.toasts = [...this.toasts, { id, message, kind }];
|
||||
this.toasts = [...this.toasts, { id, message, kind, action: opts.action }];
|
||||
if (record) {
|
||||
this.notifications = [
|
||||
{ id, message, kind, at: Date.now(), read: false },
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "الموقع",
|
||||
"wrong_drop_zone_msg": "الرفع يعمل فقط في قسم الملفات — افتح قسم الملفات وأفلت العناصر هناك."
|
||||
"wrong_drop_zone_msg": "الرفع يعمل فقط في قسم الملفات — افتح قسم الملفات وأفلت العناصر هناك.",
|
||||
"wrong_drop_zone_action": "انتقل إلى الملفات"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "Speicherort",
|
||||
"wrong_drop_zone_msg": "Uploads funktionieren nur in Dateien — öffne den Bereich Dateien und lege die Elemente dort ab."
|
||||
"wrong_drop_zone_msg": "Uploads funktionieren nur in Dateien — öffne den Bereich Dateien und lege die Elemente dort ab.",
|
||||
"wrong_drop_zone_action": "Zu Dateien wechseln"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1675,6 +1675,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "Location",
|
||||
"wrong_drop_zone_msg": "Uploads only work in Files — open the Files section and drop there."
|
||||
"wrong_drop_zone_msg": "Uploads only work in Files — open the Files section and drop there.",
|
||||
"wrong_drop_zone_action": "Go to Files"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1294,6 +1294,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "Ubicación",
|
||||
"wrong_drop_zone_msg": "Las subidas solo funcionan en Archivos — abre la sección Archivos y suelta ahí los elementos."
|
||||
"wrong_drop_zone_msg": "Las subidas solo funcionan en Archivos — abre la sección Archivos y suelta ahí los elementos.",
|
||||
"wrong_drop_zone_action": "Ir a Archivos"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "مکان",
|
||||
"wrong_drop_zone_msg": "بارگذاری فقط در بخش پروندهها کار میکند — بخش پروندهها را باز کنید و آنجا رها کنید."
|
||||
"wrong_drop_zone_msg": "بارگذاری فقط در بخش پروندهها کار میکند — بخش پروندهها را باز کنید و آنجا رها کنید.",
|
||||
"wrong_drop_zone_action": "برو به پروندهها"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "Emplacement",
|
||||
"wrong_drop_zone_msg": "Les envois ne fonctionnent que dans Fichiers — ouvrez la section Fichiers et déposez-y vos éléments."
|
||||
"wrong_drop_zone_msg": "Les envois ne fonctionnent que dans Fichiers — ouvrez la section Fichiers et déposez-y vos éléments.",
|
||||
"wrong_drop_zone_action": "Aller aux Fichiers"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "स्थान",
|
||||
"wrong_drop_zone_msg": "अपलोड केवल फ़ाइलें अनुभाग में काम करता है — फ़ाइलें अनुभाग खोलें और वहीं छोड़ें।"
|
||||
"wrong_drop_zone_msg": "अपलोड केवल फ़ाइलें अनुभाग में काम करता है — फ़ाइलें अनुभाग खोलें और वहीं छोड़ें।",
|
||||
"wrong_drop_zone_action": "फ़ाइलों पर जाएँ"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "Posizione",
|
||||
"wrong_drop_zone_msg": "I caricamenti funzionano solo in File — apri la sezione File e trascina lì gli elementi."
|
||||
"wrong_drop_zone_msg": "I caricamenti funzionano solo in File — apri la sezione File e trascina lì gli elementi.",
|
||||
"wrong_drop_zone_action": "Vai a File"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "場所",
|
||||
"wrong_drop_zone_msg": "アップロードはファイル セクションでのみ機能します。ファイル セクションを開いてそこにドロップしてください。"
|
||||
"wrong_drop_zone_msg": "アップロードはファイル セクションでのみ機能します。ファイル セクションを開いてそこにドロップしてください。",
|
||||
"wrong_drop_zone_action": "ファイルへ移動"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1671,6 +1671,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "위치",
|
||||
"wrong_drop_zone_msg": "업로드는 파일 섹션에서만 작동합니다. 파일 섹션을 열고 거기에 놓아 주세요."
|
||||
"wrong_drop_zone_msg": "업로드는 파일 섹션에서만 작동합니다. 파일 섹션을 열고 거기에 놓아 주세요.",
|
||||
"wrong_drop_zone_action": "파일로 이동"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "Locatie",
|
||||
"wrong_drop_zone_msg": "Uploaden werkt alleen in Bestanden — open het onderdeel Bestanden en zet ze daar neer."
|
||||
"wrong_drop_zone_msg": "Uploaden werkt alleen in Bestanden — open het onderdeel Bestanden en zet ze daar neer.",
|
||||
"wrong_drop_zone_action": "Naar Bestanden"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "Lokalizacja",
|
||||
"wrong_drop_zone_msg": "Przesyłanie działa tylko w Plikach — otwórz sekcję Pliki i upuść tam pliki."
|
||||
"wrong_drop_zone_msg": "Przesyłanie działa tylko w Plikach — otwórz sekcję Pliki i upuść tam pliki.",
|
||||
"wrong_drop_zone_action": "Przejdź do Plików"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "Localização",
|
||||
"wrong_drop_zone_msg": "Os envios só funcionam em Ficheiros — abra a secção Ficheiros e largue-os aí."
|
||||
"wrong_drop_zone_msg": "Os envios só funcionam em Ficheiros — abra a secção Ficheiros e largue-os aí.",
|
||||
"wrong_drop_zone_action": "Ir para Arquivos"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "Расположение",
|
||||
"wrong_drop_zone_msg": "Загрузки работают только в разделе Файлы — откройте раздел Файлы и перетащите туда."
|
||||
"wrong_drop_zone_msg": "Загрузки работают только в разделе Файлы — откройте раздел Файлы и перетащите туда.",
|
||||
"wrong_drop_zone_action": "Перейти к файлам"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "位置",
|
||||
"wrong_drop_zone_msg": "上傳僅在「檔案」中有效 — 請開啟檔案區並在該處拖放。"
|
||||
"wrong_drop_zone_msg": "上傳僅在「檔案」中有效 — 請開啟檔案區並在該處拖放。",
|
||||
"wrong_drop_zone_action": "前往檔案"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,7 @@
|
||||
},
|
||||
"resource_list": {
|
||||
"location": "位置",
|
||||
"wrong_drop_zone_msg": "上传仅在 “文件” 中生效 — 请打开文件区并在那里拖放。"
|
||||
"wrong_drop_zone_msg": "上传仅在 “文件” 中生效 — 请打开文件区并在那里拖放。",
|
||||
"wrong_drop_zone_action": "前往文件"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user