fd9e4a9802
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>
47 lines
1.5 KiB
TypeScript
47 lines
1.5 KiB
TypeScript
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;
|
|
roomId: string;
|
|
hashPath?: string; // overrides window.location.hash navigation when set
|
|
onClick?: () => void; // custom click handler; skips hash navigation when set
|
|
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
|
|
export const toastQueueAtom = atom<ToastNotif[], [ToastNotif | null], void>(
|
|
(get) => get(baseAtom),
|
|
(get, set, notif) => {
|
|
if (notif === null) return; // no-op guard
|
|
set(baseAtom, [...get(baseAtom), notif]);
|
|
},
|
|
);
|
|
|
|
export const dismissToastAtom = atom<null, [string], void>(null, (get, set, id) =>
|
|
set(
|
|
baseAtom,
|
|
get(baseAtom).filter((t) => t.id !== id),
|
|
),
|
|
);
|