feat(workbench): expand SCADA monitoring and investigation presentation
This commit is contained in:
@@ -1,46 +1,30 @@
|
||||
import type { ComponentProps } from "react";
|
||||
import { toast, Toaster } from "sonner";
|
||||
import { toast } from "sonner";
|
||||
import { cn } from "@/shared/ui/cn";
|
||||
import { MAP_ICON_CELL_RADIUS_CLASS_NAME } from "./map-control-styles";
|
||||
import { MapNoticeCloseIcon, MapNoticeContent } from "./notice-presentation";
|
||||
import type { MapNoticeOptions, MapNoticePosition } from "./notice-types";
|
||||
|
||||
type SonnerPosition = NonNullable<ComponentProps<typeof Toaster>["position"]>;
|
||||
|
||||
const DEFAULT_POSITION: MapNoticePosition = "top-center";
|
||||
|
||||
const positionClassNames: Record<MapNoticePosition, string> = {
|
||||
"top-center": "left-1/2! right-auto! top-[84px] [translate:-50%_0]",
|
||||
"top-right": "top-[84px] right-[72px]",
|
||||
"bottom-left": "bottom-12 left-4",
|
||||
"bottom-right": "bottom-12 right-4"
|
||||
};
|
||||
|
||||
const sonnerPositions: Record<MapNoticePosition, SonnerPosition> = {
|
||||
"top-center": "top-center",
|
||||
"top-right": "top-right",
|
||||
"bottom-left": "bottom-left",
|
||||
"bottom-right": "bottom-right"
|
||||
};
|
||||
const pendingNotices = new Map<string | number, () => void>();
|
||||
let nextNoticeId = 0;
|
||||
|
||||
export function showMapNotice(options: MapNoticeOptions) {
|
||||
const id = options.id ?? `map-notice:${++nextNoticeId}`;
|
||||
const tone = options.tone ?? "info";
|
||||
const position = options.position ?? DEFAULT_POSITION;
|
||||
const duration = options.duration ?? (tone === "info" || tone === "success" ? 3200 : Infinity);
|
||||
const dismissible = options.dismissible ?? tone !== "loading";
|
||||
const hasAction = Boolean(options.actionLabel && options.onAction);
|
||||
// Actionable notices must remain available even when a caller supplies a timeout.
|
||||
const duration = hasAction
|
||||
? Infinity
|
||||
: options.duration ?? (tone === "info" || tone === "success" ? 3200 : Infinity);
|
||||
const dismissible = hasAction || (options.dismissible ?? tone !== "loading");
|
||||
|
||||
return toast.custom(
|
||||
const publish = () => toast.custom(
|
||||
() => <MapNoticeContent {...options} tone={tone} />,
|
||||
{
|
||||
id: options.id,
|
||||
duration,
|
||||
position: sonnerPositions[position],
|
||||
dismissible,
|
||||
id, duration, position, dismissible,
|
||||
cancel: dismissible
|
||||
? {
|
||||
label: <MapNoticeCloseIcon />,
|
||||
onClick: (event) => event.stopPropagation()
|
||||
}
|
||||
? { label: <MapNoticeCloseIcon />, onClick: (event) => event.stopPropagation() }
|
||||
: undefined,
|
||||
classNames: {
|
||||
cancelButton: cn(
|
||||
@@ -48,11 +32,22 @@ export function showMapNotice(options: MapNoticeOptions) {
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME
|
||||
)
|
||||
},
|
||||
className: cn("fixed! z-[60]! w-[min(420px,calc(100vw-24px))]!", positionClassNames[position])
|
||||
className: "w-full"
|
||||
}
|
||||
);
|
||||
// Publish after React finishes effects: Sonner renews its subscription during
|
||||
// those effects and can otherwise miss an alert while map sources update.
|
||||
pendingNotices.set(id, publish);
|
||||
queueMicrotask(() => {
|
||||
if (pendingNotices.get(id) !== publish) return;
|
||||
pendingNotices.delete(id);
|
||||
publish();
|
||||
});
|
||||
return id;
|
||||
}
|
||||
|
||||
export function dismissMapNotice(id?: string | number) {
|
||||
if (id === undefined) pendingNotices.clear();
|
||||
else pendingNotices.delete(id);
|
||||
toast.dismiss(id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user