Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import { failureMessage } from "$lib/api";
|
||||
import { isAlertEnabled, type AlertKind } from "$lib/alert-prefs";
|
||||
import { toast, type ToastAction } from "$lib/components/ui/toast-state";
|
||||
|
||||
/** Brief confirmation — readable but not sticky. */
|
||||
const SUCCESS_DURATION_MS = 4000;
|
||||
/** Errors need more time to read. */
|
||||
const ERROR_DURATION_MS = 7000;
|
||||
/** Link actions need dwell time to click. */
|
||||
const ACTION_DURATION_MS = 10000;
|
||||
|
||||
type NotifyOptions = {
|
||||
description?: string;
|
||||
action?: ToastAction;
|
||||
actions?: ToastAction[];
|
||||
duration?: number;
|
||||
/**
|
||||
* Operator alert category (P0-9). When set, toast is gated by local alert prefs.
|
||||
* Omit for always-on UI feedback (forms, undo, validation).
|
||||
*/
|
||||
alert?: AlertKind;
|
||||
};
|
||||
|
||||
export type NotifyApiErrorOptions = NotifyOptions & {
|
||||
/** When set, used as toast title; failure message becomes the description. */
|
||||
title?: string;
|
||||
};
|
||||
|
||||
function hasLinkActions(opts: NotifyOptions): boolean {
|
||||
return Boolean(opts.action || opts.actions?.length);
|
||||
}
|
||||
|
||||
function resolveDuration(opts: NotifyOptions, fallbackMs: number): number {
|
||||
if (opts.duration !== undefined) return opts.duration;
|
||||
if (hasLinkActions(opts)) return ACTION_DURATION_MS;
|
||||
return fallbackMs;
|
||||
}
|
||||
|
||||
function shouldShowToast(opts: NotifyOptions): boolean {
|
||||
if (!opts.alert) return true;
|
||||
// Never mute toasts that carry onClick actions (e.g. Undo cancel).
|
||||
const actions = opts.actions?.length ? opts.actions : opts.action ? [opts.action] : [];
|
||||
if (actions.some((a) => typeof a.onClick === "function")) return true;
|
||||
return isAlertEnabled(opts.alert);
|
||||
}
|
||||
|
||||
/** Show a toast (and keep Alert strings in sync when callers also set local state). */
|
||||
export function notifySuccess(title: string, descriptionOrOpts?: string | NotifyOptions) {
|
||||
const opts =
|
||||
typeof descriptionOrOpts === "string"
|
||||
? { description: descriptionOrOpts }
|
||||
: (descriptionOrOpts ?? {});
|
||||
if (!shouldShowToast(opts)) return;
|
||||
toast({
|
||||
title,
|
||||
description: opts.description,
|
||||
variant: "default",
|
||||
action: opts.action,
|
||||
actions: opts.actions,
|
||||
duration: resolveDuration(opts, SUCCESS_DURATION_MS)
|
||||
});
|
||||
}
|
||||
|
||||
export function notifyError(title: string, descriptionOrOpts?: string | NotifyOptions) {
|
||||
const opts =
|
||||
typeof descriptionOrOpts === "string"
|
||||
? { description: descriptionOrOpts }
|
||||
: (descriptionOrOpts ?? {});
|
||||
if (!shouldShowToast(opts)) return;
|
||||
toast({
|
||||
title,
|
||||
description: opts.description,
|
||||
variant: "destructive",
|
||||
action: opts.action,
|
||||
actions: opts.actions,
|
||||
duration: resolveDuration(opts, ERROR_DURATION_MS)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard path for `api()` failures → toast (and optional page banner).
|
||||
* Returns the message so callers can assign `error = notifyApiError(...)`.
|
||||
* Message is always returned even when the toast is muted by alert prefs.
|
||||
*/
|
||||
export function notifyApiError(
|
||||
err: unknown,
|
||||
fallback: string,
|
||||
opts?: NotifyApiErrorOptions
|
||||
): string {
|
||||
const message = failureMessage(err, fallback);
|
||||
const { title, description, action, actions, duration, alert } = opts ?? {};
|
||||
const toastOpts: NotifyOptions = { description, action, actions, duration, alert };
|
||||
if (title !== undefined) {
|
||||
notifyError(title, { ...toastOpts, description: description ?? message });
|
||||
} else {
|
||||
notifyError(message, toastOpts);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
Reference in New Issue
Block a user