feat(download): show a toast + button check when a file is saved
The desktop (Tauri) app has no native download UI, so FileSaver.saveAs saved files silently — no visual or audio confirmation. Users re-clicked because nothing said it worked (one report: 5 copies of the same file). Add a small useSaveFile() hook that saves AND raises a 'Downloaded <filename>' toast, and route every download call site through it (file attachments, image viewer, PDF viewer, plus the recovery-key / key-backup exports). The file-message download button also shows a green check on success. Toast system extended with an optional iconSrc so system toasts render an icon instead of an avatar/initials, and an empty roomName is no longer rendered. Tests: createDownloadToast covered; 701/701 pass; typecheck + build clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createStore } from 'jotai';
|
||||
import { toastQueueAtom, dismissToastAtom, ToastNotif } from './toast';
|
||||
import { toastQueueAtom, dismissToastAtom, ToastNotif, createDownloadToast } from './toast';
|
||||
|
||||
// The queue lives in an unexported baseAtom; we drive the two write-only setters
|
||||
// (toastQueueAtom append + null no-op guard, dismissToastAtom remove-by-id)
|
||||
@@ -85,3 +85,15 @@ test('dismissToastAtom for an unknown id is a no-op', () => {
|
||||
['a'],
|
||||
);
|
||||
});
|
||||
|
||||
test('createDownloadToast: filename in body, no room navigation, unique ids', () => {
|
||||
const a = createDownloadToast('photo.jpg');
|
||||
assert.equal(a.displayName, 'Downloaded');
|
||||
assert.equal(a.body, 'photo.jpg');
|
||||
// roomId empty + an onClick present → clicking dismisses without navigating to a room.
|
||||
assert.equal(a.roomId, '');
|
||||
assert.equal(a.roomName, '');
|
||||
assert.equal(typeof a.onClick, 'function');
|
||||
const b = createDownloadToast('photo.jpg');
|
||||
assert.notEqual(a.id, b.id);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { atom } from 'jotai';
|
||||
import type { IconSrc } from 'folds';
|
||||
|
||||
export type ToastNotif = {
|
||||
id: string;
|
||||
avatarUrl?: string;
|
||||
iconSrc?: IconSrc; // folds Icon src for a "system" toast (shown instead of an avatar/initials)
|
||||
displayName: string;
|
||||
body: string;
|
||||
roomName: string;
|
||||
@@ -12,6 +14,19 @@ export type ToastNotif = {
|
||||
sticky?: boolean; // when true, does not auto-dismiss — use for action toasts that require a click
|
||||
};
|
||||
|
||||
// Build a "download complete" system toast. Kept folds-free here (the icon src is
|
||||
// passed in) so this stays a pure, testable builder. roomId is empty + onClick is
|
||||
// set so a click only dismisses (never navigates to a room).
|
||||
export const createDownloadToast = (filename: string, iconSrc?: IconSrc): ToastNotif => ({
|
||||
id: `download-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
displayName: 'Downloaded',
|
||||
body: filename,
|
||||
roomName: '',
|
||||
roomId: '',
|
||||
iconSrc,
|
||||
onClick: () => undefined,
|
||||
});
|
||||
|
||||
const baseAtom = atom<ToastNotif[]>([]);
|
||||
|
||||
// Write-only setter used in ClientNonUIFeatures
|
||||
|
||||
Reference in New Issue
Block a user