/* contains all logic for the VisProgEditor warning system * * Missing but desirable features: * - Warning filtering: * - if there is no completely connected chain of startNode-[PhaseNodes]-EndNode * then hide any startNode, phaseNode, or endNode specific warnings */ import useFlowStore from "../VisProgStores.tsx"; import type {FlowState} from "../VisProgTypes.tsx"; // --| Type definitions |-- export type WarningId = NodeId | "GLOBAL_WARNINGS"; export type NodeId = string; export type WarningType = | 'MISSING_INPUT' | 'MISSING_OUTPUT' | 'PLAN_IS_UNDEFINED' | 'INCOMPLETE_PROGRAM' | string export type WarningSeverity = | 'INFO' // Acceptable, but important to be aware of | 'WARNING' // Acceptable, but probably undesirable behavior | 'ERROR' // Prevents running program, should be fixed before running program is allowed export type WarningScope = { id: string; handleId?: string; } export type EditorWarning = { scope: WarningScope; type: WarningType; severity: WarningSeverity; description: string; }; /** * a scoped WarningKey, * the handleId scoping is only needed for handle specific errors * * "`WarningType`:`handleId`" */ export type WarningKey = string; // for warnings that can occur on a per-handle basis /** * a composite key used in the severityIndex * * "`WarningId`|`WarningKey`" */ export type CompositeWarningKey = string; export type WarningRegistry = Map>; export type SeverityIndex = Map>; type ZustandSet = (partial: Partial | ((state: FlowState) => Partial)) => void; type ZustandGet = () => FlowState; export type EditorWarningRegistry = { editorWarningRegistry: WarningRegistry; severityIndex: SeverityIndex; getWarnings: () => EditorWarning[]; getWarningsBySeverity: (warningSeverity: WarningSeverity) => EditorWarning[]; /** * checks if there are no warnings of breaking severity * @returns {boolean} */ isProgramValid: () => boolean; /** * registers a warning to the warningRegistry and the SeverityIndex * @param {EditorWarning} warning */ registerWarning: (warning: EditorWarning) => void; /** * unregisters a warning from the warningRegistry and the SeverityIndex * @param {EditorWarning} warning */ unregisterWarning: (id: WarningId, warningKey: WarningKey) => void /** * unregisters warnings from the warningRegistry and the SeverityIndex * @param {EditorWarning} warning */ unregisterWarningsForId: (id: WarningId) => void; } // --| implemented logic |-- export const globalWarning = "GLOBAL_WARNINGS"; export function editorWarningRegistry(get: ZustandGet, set: ZustandSet) : EditorWarningRegistry { return { editorWarningRegistry: new Map>(), severityIndex: new Map([ ['INFO', new Set()], ['WARNING', new Set()], ['ERROR', new Set()], ]), getWarningsBySeverity: (warningSeverity) => { const wRegistry = get().editorWarningRegistry; const sIndex = get().severityIndex; const warningKeys = sIndex.get(warningSeverity); const warnings: EditorWarning[] = []; warningKeys?.forEach( (compositeKey) => { const [id, warningKey] = compositeKey.split('|'); const warning = wRegistry.get(id)?.get(warningKey); if (warning) { warnings.push(warning); } } ) return warnings; }, isProgramValid: () => { const sIndex = get().severityIndex; return (sIndex.get("ERROR")!.size === 0); }, getWarnings: () => Array.from(get().editorWarningRegistry.values()) .flatMap(innerMap => Array.from(innerMap.values())), registerWarning: (warning) => { const { scope: {id, handleId}, type, severity } = warning; const warningKey = handleId ? `${type}:${handleId}` : type; const compositeKey = `${id}|${warningKey}`; const wRegistry = structuredClone(get().editorWarningRegistry); const sIndex = structuredClone(get().severityIndex); console.log("register") // add to warning registry if (!wRegistry.has(id)) { wRegistry.set(id, new Map()); } wRegistry.get(id)!.set(warningKey, warning); // add to severityIndex if (!sIndex.get(severity)!.has(compositeKey)) { sIndex.get(severity)!.add(compositeKey); } set({ editorWarningRegistry: wRegistry, severityIndex: sIndex }) }, unregisterWarning: (id, warningKey) => { const wRegistry = structuredClone(get().editorWarningRegistry); const sIndex = structuredClone(get().severityIndex); console.log("unregister") // verify if the warning was created already const warning = wRegistry.get(id)?.get(warningKey); if (!warning) return; // remove from warning registry wRegistry.get(id)!.delete(warningKey); // remove from severityIndex sIndex.get(warning.severity)!.delete(`${id}|${warningKey}`); set({ editorWarningRegistry: wRegistry, severityIndex: sIndex }) }, unregisterWarningsForId: (id) => { const wRegistry = structuredClone(get().editorWarningRegistry); const sIndex = structuredClone(get().severityIndex); const nodeWarnings = wRegistry.get(id); // remove from severity index if (nodeWarnings) { nodeWarnings.forEach((warning, warningKey) => { sIndex.get(warning.severity)?.delete(`${id}|${warningKey}`); }); } // remove from warning registry wRegistry.delete(id); set({ editorWarningRegistry: wRegistry, severityIndex: sIndex }) }, }} // returns a summary of the warningRegistry export function warningSummary() { const {severityIndex, isProgramValid} = useFlowStore.getState(); return { info: severityIndex.get('INFO')!.size, warning: severityIndex.get('WARNING')!.size, error: severityIndex.get('ERROR')!.size, isValid: isProgramValid(), }; }