feat: The Big One UI #47
@@ -1,10 +1,18 @@
|
||||
import type {Edge, Node} from "@xyflow/react";
|
||||
import type {StateCreator, StoreApi } from 'zustand/vanilla';
|
||||
import type {
|
||||
SeverityIndex,
|
||||
WarningRegistry
|
||||
} from "./components/EditorWarnings.tsx";
|
||||
import type {FlowState} from "./VisProgTypes.tsx";
|
||||
|
||||
export type FlowSnapshot = {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
warnings: {
|
||||
warningRegistry: WarningRegistry;
|
||||
severityIndex: SeverityIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +49,11 @@ export const UndoRedo = (
|
||||
*/
|
||||
const getSnapshot = (state : BaseFlowState) : FlowSnapshot => (structuredClone({
|
||||
nodes: state.nodes,
|
||||
edges: state.edges
|
||||
edges: state.edges,
|
||||
warnings: {
|
||||
warningRegistry: state.editorWarningRegistry,
|
||||
severityIndex: state.severityIndex,
|
||||
}
|
||||
}));
|
||||
|
||||
const initialState = config(set, get, api);
|
||||
@@ -78,6 +90,8 @@ export const UndoRedo = (
|
||||
set({
|
||||
nodes: snapshot.nodes,
|
||||
edges: snapshot.edges,
|
||||
editorWarningRegistry: snapshot.warnings.warningRegistry,
|
||||
severityIndex: snapshot.warnings.severityIndex,
|
||||
});
|
||||
|
||||
state.future.push(currentSnapshot); // push current to redo
|
||||
@@ -97,6 +111,8 @@ export const UndoRedo = (
|
||||
set({
|
||||
nodes: snapshot.nodes,
|
||||
edges: snapshot.edges,
|
||||
editorWarningRegistry: snapshot.warnings.warningRegistry,
|
||||
severityIndex: snapshot.warnings.severityIndex,
|
||||
});
|
||||
|
||||
state.past.push(currentSnapshot); // push current to undo
|
||||
|
||||
@@ -19,6 +19,7 @@ export type WarningType =
|
||||
| 'MISSING_OUTPUT'
|
||||
| 'PLAN_IS_UNDEFINED'
|
||||
| 'INCOMPLETE_PROGRAM'
|
||||
| 'NOT_CONNECTED_TO_PROGRAM'
|
||||
| string
|
||||
|
||||
export type WarningSeverity =
|
||||
@@ -87,7 +88,7 @@ export type EditorWarningRegistry = {
|
||||
|
||||
/**
|
||||
* unregisters warnings from the warningRegistry and the SeverityIndex
|
||||
* @param {EditorWarning} warning
|
||||
* @param {WarningId} warning
|
||||
*/
|
||||
unregisterWarningsForId: (id: WarningId) => void;
|
||||
}
|
||||
@@ -187,13 +188,15 @@ export function editorWarningRegistry(get: ZustandGet, set: ZustandSet) : Editor
|
||||
|
||||
// remove from severity index
|
||||
if (nodeWarnings) {
|
||||
nodeWarnings.forEach((warning, warningKey) => {
|
||||
nodeWarnings.forEach((warning) => {
|
||||
const warningKey = `${warning.type}:${warning.scope.handleId}`;
|
||||
sIndex.get(warning.severity)?.delete(`${id}|${warningKey}`);
|
||||
});
|
||||
}
|
||||
|
||||
// remove from warning registry
|
||||
wRegistry.delete(id);
|
||||
console.log(wRegistry.get(id));
|
||||
|
||||
set({
|
||||
editorWarningRegistry: wRegistry,
|
||||
|
||||
@@ -19,9 +19,6 @@ export function WarningsSidebar() {
|
||||
? warnings
|
||||
: warnings.filter(w => w.severity === severityFilter);
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<aside className={styles.warningsSidebar}>
|
||||
<WarningsHeader
|
||||
@@ -83,7 +80,7 @@ function WarningsList(props: { warnings: EditorWarning[] }) {
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div style={{overflowY: 'auto'}}>
|
||||
<div className={"warningGroup"}>
|
||||
<div className={styles.warningGroupHeader}>global:</div>
|
||||
<div className={styles.warningsList}>
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
type Node, useNodeConnections
|
||||
} from '@xyflow/react';
|
||||
import {useEffect} from "react";
|
||||
import type {EditorWarning} from "../components/EditorWarnings.tsx";
|
||||
import {type EditorWarning} from "../components/EditorWarnings.tsx";
|
||||
import { Toolbar } from '../components/NodeComponents';
|
||||
import styles from '../../VisProg.module.css';
|
||||
import {SingleConnectionHandle, MultiConnectionHandle} from "../components/RuleBasedHandle.tsx";
|
||||
@@ -39,16 +39,29 @@ export type PhaseNode = Node<PhaseNodeData>
|
||||
*/
|
||||
export default function PhaseNode(props: NodeProps<PhaseNode>) {
|
||||
const data = props.data;
|
||||
const {updateNodeData} = useFlowStore();
|
||||
const {updateNodeData, registerWarning, unregisterWarning} = useFlowStore();
|
||||
const updateLabel = (value: string) => updateNodeData(props.id, {...data, label: value});
|
||||
const label_input_id = `phase_${props.id}_label_input`;
|
||||
|
||||
const {registerWarning, unregisterWarning} = useFlowStore.getState();
|
||||
const connections = useNodeConnections({
|
||||
id: props.id,
|
||||
handleType: "target",
|
||||
handleId: 'data'
|
||||
})
|
||||
|
||||
const phaseOutCons = useNodeConnections({
|
||||
id: props.id,
|
||||
handleType: "source",
|
||||
handleId: 'source',
|
||||
})
|
||||
|
||||
const phaseInCons = useNodeConnections({
|
||||
id: props.id,
|
||||
handleType: "target",
|
||||
handleId: 'target',
|
||||
})
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const noConnectionWarning : EditorWarning = {
|
||||
@@ -61,10 +74,64 @@ export default function PhaseNode(props: NodeProps<PhaseNode>) {
|
||||
description: "the phaseNode has no incoming goals, norms, and/or triggers"
|
||||
}
|
||||
|
||||
if (connections.length === 0) { registerWarning(noConnectionWarning); }
|
||||
else { unregisterWarning(props.id, `${noConnectionWarning.type}:data`); }
|
||||
if (connections.length === 0) { registerWarning(noConnectionWarning); return; }
|
||||
unregisterWarning(props.id, `${noConnectionWarning.type}:data`);
|
||||
}, [connections.length, props.id, registerWarning, unregisterWarning]);
|
||||
|
||||
useEffect(() => {
|
||||
const notConnectedInfo : EditorWarning = {
|
||||
scope: {
|
||||
id: props.id,
|
||||
handleId: undefined,
|
||||
},
|
||||
type: 'NOT_CONNECTED_TO_PROGRAM',
|
||||
severity: "INFO",
|
||||
description: "The PhaseNode is not connected to other nodes"
|
||||
};
|
||||
const noIncomingPhaseWarning : EditorWarning = {
|
||||
scope: {
|
||||
id: props.id,
|
||||
handleId: 'target'
|
||||
},
|
||||
type: 'MISSING_INPUT',
|
||||
severity: "WARNING",
|
||||
description: "the phaseNode has no incoming connection from a phase or the startNode"
|
||||
}
|
||||
const noOutgoingPhaseWarning : EditorWarning = {
|
||||
scope: {
|
||||
id: props.id,
|
||||
handleId: 'source'
|
||||
},
|
||||
type: 'MISSING_OUTPUT',
|
||||
severity: "WARNING",
|
||||
description: "the phaseNode has no outgoing connection to a phase or the endNode"
|
||||
}
|
||||
|
||||
// register relevant warning and unregister others
|
||||
if (phaseInCons.length === 0 && phaseOutCons.length === 0) {
|
||||
registerWarning(notConnectedInfo);
|
||||
unregisterWarning(props.id, `${noOutgoingPhaseWarning.type}:${noOutgoingPhaseWarning.scope.handleId}`);
|
||||
unregisterWarning(props.id, `${noIncomingPhaseWarning.type}:${noIncomingPhaseWarning.scope.handleId}`);
|
||||
return;
|
||||
}
|
||||
if (phaseOutCons.length === 0) {
|
||||
registerWarning(noOutgoingPhaseWarning);
|
||||
unregisterWarning(props.id, `${noIncomingPhaseWarning.type}:${noIncomingPhaseWarning.scope.handleId}`);
|
||||
unregisterWarning(notConnectedInfo.scope.id, notConnectedInfo.type);
|
||||
return;
|
||||
}
|
||||
if (phaseInCons.length === 0) {
|
||||
registerWarning(noIncomingPhaseWarning);
|
||||
unregisterWarning(props.id, `${noOutgoingPhaseWarning.type}:${noOutgoingPhaseWarning.scope.handleId}`);
|
||||
unregisterWarning(notConnectedInfo.scope.id, notConnectedInfo.type);
|
||||
return;
|
||||
}
|
||||
// unregister all warnings if none should be present
|
||||
unregisterWarning(notConnectedInfo.scope.id, notConnectedInfo.type);
|
||||
unregisterWarning(props.id, `${noOutgoingPhaseWarning.type}:${noOutgoingPhaseWarning.scope.handleId}`);
|
||||
unregisterWarning(props.id, `${noIncomingPhaseWarning.type}:${noIncomingPhaseWarning.scope.handleId}`);
|
||||
}, [phaseInCons.length, phaseOutCons.length, props.id, registerWarning, unregisterWarning]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbar nodeId={props.id} allowDelete={true}/>
|
||||
|
||||
@@ -34,10 +34,17 @@ describe("UndoRedo Middleware", () => {
|
||||
type: 'default',
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'A'}
|
||||
},
|
||||
}
|
||||
],
|
||||
edges: []
|
||||
edges: [],
|
||||
warnings: {
|
||||
warningRegistry: new Map(),
|
||||
severityIndex: new Map()
|
||||
}
|
||||
}],
|
||||
ruleRegistry: new Map(),
|
||||
editorWarningRegistry: new Map(),
|
||||
severityIndex: new Map()
|
||||
});
|
||||
|
||||
act(() => {
|
||||
@@ -53,7 +60,11 @@ describe("UndoRedo Middleware", () => {
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'A'}
|
||||
}],
|
||||
edges: []
|
||||
edges: [],
|
||||
warnings: {
|
||||
warningRegistry: {},
|
||||
severityIndex: {}
|
||||
}
|
||||
});
|
||||
expect(state.future).toEqual([]);
|
||||
});
|
||||
@@ -80,7 +91,9 @@ describe("UndoRedo Middleware", () => {
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'A'}
|
||||
}],
|
||||
edges: []
|
||||
edges: [],
|
||||
editorWarningRegistry: new Map(),
|
||||
severityIndex: new Map()
|
||||
});
|
||||
|
||||
act(() => {
|
||||
@@ -114,7 +127,11 @@ describe("UndoRedo Middleware", () => {
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'B'}
|
||||
}],
|
||||
edges: []
|
||||
edges: [],
|
||||
warnings: {
|
||||
warningRegistry: {},
|
||||
severityIndex: {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -140,7 +157,9 @@ describe("UndoRedo Middleware", () => {
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'A'}
|
||||
}],
|
||||
edges: []
|
||||
edges: [],
|
||||
editorWarningRegistry: new Map(),
|
||||
severityIndex: new Map()
|
||||
});
|
||||
|
||||
act(() => {
|
||||
@@ -176,7 +195,11 @@ describe("UndoRedo Middleware", () => {
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'A'}
|
||||
}],
|
||||
edges: []
|
||||
edges: [],
|
||||
warnings: {
|
||||
warningRegistry: {},
|
||||
severityIndex: {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -199,7 +222,9 @@ describe("UndoRedo Middleware", () => {
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'A'}
|
||||
}],
|
||||
edges: []
|
||||
edges: [],
|
||||
editorWarningRegistry: new Map(),
|
||||
severityIndex: new Map()
|
||||
});
|
||||
|
||||
act(() => { store.getState().beginBatchAction(); });
|
||||
|
||||
Reference in New Issue
Block a user