onion_graph.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. // The React state and reducer of a D3Graph component showing an onion graph state.
  2. import {PrimitiveValue} from "onion/types";
  3. import {INodeState, IValueState, GraphStateListener} from "onion/graph_state";
  4. import {D3GraphData, D3NodeData, D3LinkData} from "../d3graph";
  5. export type D3OnionNodeData = D3NodeData<INodeState|IValueState>;
  6. export type D3OnionLinkData = D3LinkData<null>;
  7. export type D3OnionGraphData = D3GraphData<INodeState|IValueState,null>;
  8. interface AddNode {type:'addNode', ns: INodeState, x: number, y: number}
  9. interface RemoveNode {type:'removeNode', id: PrimitiveValue}
  10. interface AddValue {type:'addValue', vs: IValueState, x: number, y: number}
  11. interface RemoveValue {type:'removeValue', value: PrimitiveValue}
  12. interface AddLinkToNode {type:'addLinkToNode', sourceId: PrimitiveValue, label: string, targetId: PrimitiveValue}
  13. interface AddLinkToValue {type:'addLinkToValue', sourceId: PrimitiveValue, label: string, targetValue: PrimitiveValue}
  14. interface RemoveLink {type:'removeLink', sourceId: PrimitiveValue, label: string}
  15. export type D3OnionGraphAction =
  16. Readonly<AddNode>
  17. | Readonly<RemoveNode>
  18. | Readonly<AddValue>
  19. | Readonly<RemoveValue>
  20. | Readonly<AddLinkToNode>
  21. | Readonly<AddLinkToValue>
  22. | Readonly<RemoveLink>;
  23. export function onionGraphReducer(prevState: D3OnionGraphData, action: D3OnionGraphAction): D3OnionGraphData {
  24. switch (action.type) {
  25. case 'addNode': {
  26. return {
  27. nodes: [...prevState.nodes, {
  28. id: nodeNodeId(action.ns.creation.id.value),
  29. label: JSON.stringify(action.ns.creation.id.value),
  30. x: action.x,
  31. y: action.y,
  32. color: "darkturquoise",
  33. obj: action.ns,
  34. bold: false,
  35. }],
  36. links: prevState.links,
  37. };
  38. }
  39. case 'removeNode': {
  40. return {
  41. nodes: prevState.nodes.filter(n => !n.obj.isNode(action.id)),
  42. links: prevState.links,
  43. };
  44. }
  45. case 'addValue': {
  46. return {
  47. nodes: [...prevState.nodes, {
  48. id: valueNodeId(action.vs.value),
  49. label: JSON.stringify(action.vs.value),
  50. x: action.x,
  51. y: action.y,
  52. color: "darkorange",
  53. obj: action.vs,
  54. bold: false,
  55. }],
  56. links: prevState.links,
  57. };
  58. }
  59. case 'removeValue': {
  60. return {
  61. nodes: prevState.nodes.filter(n => !n.obj.isValue(action.value)),
  62. links: prevState.links,
  63. };
  64. }
  65. case 'addLinkToNode': {
  66. return {
  67. nodes: prevState.nodes,
  68. links: [...prevState.links, {
  69. source: prevState.nodes.find(n => n.obj.isNode(action.sourceId)), // AR: here is the problem!
  70. target: prevState.nodes.find(n => n.obj.isNode(action.targetId)),
  71. label: action.label,
  72. color: 'black',
  73. obj: null,
  74. }],
  75. };
  76. }
  77. case 'addLinkToValue': {
  78. return {
  79. nodes: prevState.nodes,
  80. links: [...prevState.links, {
  81. source: prevState.nodes.find(n => n.obj.isNode(action.sourceId)),
  82. target: prevState.nodes.find(n => n.obj.isValue(action.targetValue)),
  83. label: action.label,
  84. color: 'black',
  85. obj: null,
  86. }],
  87. };
  88. }
  89. case 'removeLink': {
  90. return {
  91. nodes: prevState.nodes,
  92. links: prevState.links.filter(l => l.source.obj.creation.id.value !== action.sourceId || l.label !== action.label),
  93. };
  94. }
  95. }
  96. }
  97. function nodeNodeId(nodeId: PrimitiveValue) {
  98. return "N"+JSON.stringify(nodeId);
  99. }
  100. function valueNodeId(value: PrimitiveValue) {
  101. return "V"+JSON.stringify(value);
  102. }
  103. // Responds to changes to a GraphState object by updating the React state of a d3Graph component.
  104. export class D3GraphUpdater implements GraphStateListener {
  105. readonly setGraph: (cb: (prevGraph: D3OnionGraphData) => D3OnionGraphData) => void;
  106. // SVG coordinates for newly created nodes
  107. // This information cannot be part of our NodeCreation deltas, but it must come from somewhere...
  108. x: number;
  109. y: number;
  110. constructor(setGraph: (cb: (prevGraph: D3OnionGraphData) => D3OnionGraphData) => void, x, y) {
  111. this.setGraph = setGraph;
  112. this.x = x;
  113. this.y = y;
  114. }
  115. createNode(ns: INodeState) {
  116. this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'addNode', ns, x: this.x, y: this.y}));
  117. }
  118. createValue(vs: IValueState) {
  119. this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'addValue', vs, x: this.x, y: this.y}));
  120. }
  121. deleteNode(id: PrimitiveValue) {
  122. this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'removeNode', id}));
  123. }
  124. deleteValue(value: PrimitiveValue) {
  125. this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'removeValue', value}));
  126. }
  127. createLinkToNode(sourceId: PrimitiveValue, label: string, targetId: PrimitiveValue) {
  128. this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'addLinkToNode', sourceId, label, targetId}));
  129. }
  130. createLinkToValue(sourceId: PrimitiveValue, label: string, targetValue: PrimitiveValue) {
  131. this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'addLinkToValue', sourceId, label, targetValue}));
  132. }
  133. deleteLink(sourceId: PrimitiveValue, label: string) {
  134. this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'removeLink', sourceId, label}));
  135. }
  136. }