- The SPA loads the backend::lang bundle before /auth/me, sets the document language from meta.locale and renders plural messages with Intl.PluralRules; interpolate mirrors phrasebook for :name/:Name/:NAME - Create and record routes; mapWinterUrl maps recordUrl and redirects onto the controller's list, create and record routes only - List rows open their record; FormView loads the form schema and the record, shows context-allowed fields in the span grid, saves values keyed by field name and toasts the resolved saved message - A 422 puts each message under its field (aria-invalid, aria-describedby), shows the plural banner, focuses the first invalid field in schema order and clears a field's error on change - The D-05 registry maps text, textarea, number and dropdown; any other type renders the unsupported-field box with the type in DM Mono - The admin OpenAPI document declares the write request bodies (AdminRecord, AdminIDsRequest) and the list filter query as a deepObject, so the typed client can send them - New backend::lang form.load_failed key; boardwalk/dist rebuilt
53 lines
1.3 KiB
TypeScript
53 lines
1.3 KiB
TypeScript
// Global toast queue (design: State Management). Toasts outlive route
|
|
// changes because AppShell mounts the renderer, so "Zapisz i zamknij" can
|
|
// show its toast on the list it returns to.
|
|
import { readonly, ref } from 'vue'
|
|
|
|
export type ToastTone = 'success' | 'danger'
|
|
|
|
export interface ToastItem {
|
|
id: number
|
|
text: string
|
|
tone: ToastTone
|
|
}
|
|
|
|
/** Auto-dismiss delay in milliseconds (design: about 4 s). */
|
|
export const TOAST_TIMEOUT = 4000
|
|
|
|
const items = ref<ToastItem[]>([])
|
|
const timers = new Map<number, ReturnType<typeof setTimeout>>()
|
|
let nextId = 1
|
|
|
|
export const toasts = readonly(items)
|
|
|
|
export function showToast(text: string, tone: ToastTone = 'success'): number {
|
|
const id = nextId++
|
|
items.value = [...items.value, { id, text, tone }]
|
|
timers.set(
|
|
id,
|
|
setTimeout(() => dismissToast(id), TOAST_TIMEOUT),
|
|
)
|
|
return id
|
|
}
|
|
|
|
export function dismissToast(id: number): void {
|
|
const timer = timers.get(id)
|
|
if (timer !== undefined) {
|
|
clearTimeout(timer)
|
|
timers.delete(id)
|
|
}
|
|
items.value = items.value.filter((item) => item.id !== id)
|
|
}
|
|
|
|
export function clearToasts(): void {
|
|
for (const timer of timers.values()) {
|
|
clearTimeout(timer)
|
|
}
|
|
timers.clear()
|
|
items.value = []
|
|
}
|
|
|
|
export function useToasts() {
|
|
return { toasts, showToast, dismissToast, clearToasts }
|
|
}
|