diff --git a/src/canvas/edge.test.ts b/src/canvas/edge.test.ts
new file mode 100644
index 0000000..cbe5670
--- /dev/null
+++ b/src/canvas/edge.test.ts
@@ -0,0 +1,16 @@
+import Edge from "./edge";
+import edge from "./edge";
+
+describe("id", () => {
+test("should create an id from an edge", () => {
+ const edge = { sourceId: "1", targetId: "2" };
+ expect(Edge.id(edge)).toEqual("1-->2");
+ });
+});
+
+describe("parseId", () => {
+ test("should return an edge from an id", () => {
+ const id = "1-->2";
+ expect(Edge.parseId(id)).toEqual({ sourceId: "1", targetId: "2" });
+ });
+});
diff --git a/src/canvas/edge.ts b/src/canvas/edge.ts
index 201413a..e77b827 100644
--- a/src/canvas/edge.ts
+++ b/src/canvas/edge.ts
@@ -3,6 +3,13 @@ interface Edge {
targetId: string;
}
+const ArrowSymbol = "-->";
+
export default {
- id: (edge: Edge): string => `${edge.sourceId}-->${edge.targetId}`,
+ id: (edge: Edge): string => `${edge.sourceId}${ArrowSymbol}${edge.targetId}`,
+
+ parseId: (edgeId: string): Edge => {
+ const [sourceId, targetId] = edgeId.split(ArrowSymbol);
+ return { sourceId, targetId };
+ },
};
diff --git a/src/interface/control.ts b/src/interface/control.ts
index 8959708..01e9bba 100644
--- a/src/interface/control.ts
+++ b/src/interface/control.ts
@@ -3,7 +3,7 @@ import { html } from "htm/react";
import { Store } from "redux";
import { ApplicationState } from "../store";
-import { deleteAtom, evalSelectedAtom } from "../store/defaultReducer";
+import { deleteAtom, evalSelectedAtom, deleteEdge } from "../store/defaultReducer";
import { selectors as canvasSelectors } from "../canvas";
import PlayIcon from "./play_icon";
import TrashIcon from "./trash_icon";
@@ -11,7 +11,7 @@ import CastIcon from "./cast_icon";
import BookIcon from "./book_icon";
import { actions } from "./interfaceReducer";
-const { getSelectedAtomId } = canvasSelectors;
+const { getSelectedAtomId, getSelectedEdgeId } = canvasSelectors;
const { toggleTranscript } = actions;
interface Props {
@@ -20,10 +20,12 @@ interface Props {
export default function Control({ store }: Props) {
const [selectedAtomId, setSelectedAtomId] = useState(null);
+ const [selectedEdgeId, setSelectedEdgeId] = useState(null);
const [connectedToRepl, setConnectedToRepl] = useState(false);
store.subscribe(() => {
setSelectedAtomId(getSelectedAtomId(store.getState()));
+ setSelectedEdgeId(getSelectedEdgeId(store.getState()));
setConnectedToRepl(store.getState().default.connectedToRepl);
});
@@ -36,7 +38,8 @@ export default function Control({ store }: Props) {
const onDeleteClick = (event) => {
event.preventDefault();
event.stopPropagation();
- store.dispatch(deleteAtom(selectedAtomId));
+ if (selectedAtomId) store.dispatch(deleteAtom(selectedAtomId));
+ if (selectedEdgeId) store.dispatch(deleteEdge(selectedEdgeId));
}
const onTranscriptClick = (event) => {
@@ -58,7 +61,7 @@ export default function Control({ store }: Props) {
<${PlayIcon} />
diff --git a/src/keyboard/index.ts b/src/keyboard/index.ts
index 8d2cf4e..33fe955 100644
--- a/src/keyboard/index.ts
+++ b/src/keyboard/index.ts
@@ -2,7 +2,7 @@ import * as Mousetrap from "mousetrap";
import { ApplicationState } from "../store";
import {
deleteAtom as deleteAtomAction, addAtom, connectAtoms, childrenSelector,
- evalSelectedAtom, parentSelector, deepChildrenSelector
+ evalSelectedAtom, parentSelector, deepChildrenSelector, deleteEdge
} from "../store/defaultReducer";
import { buildNodeGeometry } from "../canvas/node_geometry";
import { createAtom } from "../store/atom";
@@ -16,30 +16,44 @@ import { remote } from "electron";
const { dialog } = remote;
const { select, unselect, changeMode } = actions;
const { toggleTranscript } = interfaceActions;
-const { getSelectedAtom, getMode } = selectors;
+const { getSelectedAtom, getMode, getSelectedEdgeId } = selectors;
export default function Keyboard(store: Store) {
let state = store.getState();
let mode = getMode(store.getState());
let selectedAtom = getSelectedAtom(store.getState());
+ let selectedEdgeId = getSelectedEdgeId(store.getState());
store.subscribe(() => {
state = store.getState();
mode = getMode(store.getState());
selectedAtom = getSelectedAtom(store.getState());
+ selectedEdgeId = getSelectedEdgeId(store.getState());
});
const standardAtomOffset = 40;
const evaluateAtom = () => store.dispatch(evalSelectedAtom());
- const deleteAtom = (event) => {
- if (!selectedAtom) return;
-
- event.preventDefault();
+ const deleteSelectedAtom = () => {
const parent = parentSelector(state.default, selectedAtom.id);
store.dispatch(deleteAtomAction(selectedAtom.id));
if (parent) store.dispatch(select(parent.id));
};
+
+ const deleteSelectedEdge = () => store.dispatch(deleteEdge(selectedEdgeId));
+
+ const deleteSelectedElement = (event) => {
+ if (selectedAtom) {
+ event.preventDefault();
+ deleteSelectedAtom();
+ }
+
+ if (selectedEdgeId) {
+ event.preventDefault();
+ deleteSelectedEdge();
+ }
+ };
+
const createChildAtom = (event) => {
if (!selectedAtom) return;
@@ -163,7 +177,7 @@ export default function Keyboard(store: Store) {
};
Mousetrap.bind("command+e", evaluateAtom);
- Mousetrap.bind("command+backspace", deleteAtom);
+ Mousetrap.bind("command+backspace", deleteSelectedElement);
Mousetrap.bind("tab", createChildAtom);
Mousetrap.bind("command+enter", handleCmdEnter);
Mousetrap.bind("esc", handleEsc);
diff --git a/src/store/defaultReducer.ts b/src/store/defaultReducer.ts
index 0847b41..f07b584 100644
--- a/src/store/defaultReducer.ts
+++ b/src/store/defaultReducer.ts
@@ -5,6 +5,7 @@ import { buildNodeGeometry } from "../canvas/node_geometry";
import { Line } from "../canvas/geometry";
import { ApplicationState } from ".";
import { EvalResult } from "../repl";
+import Edge from "../canvas/edge";
export interface DefaultState {
atoms: { [id: string]: Atom };
@@ -43,6 +44,10 @@ export const setAtomValue =
export const evalSelectedAtom =
() => ({ type: "eval-selected-atom" });
+export const deleteEdge = (edgeId: string) => (
+ { type: "delete-edge", payload: Edge.parseId(edgeId) }
+);
+
const initialState: DefaultState = {
atoms: {},
edges: [],
@@ -66,6 +71,12 @@ const reducer = createReducer(initialState, {
return !isSource && !isTarget;
});
},
+ "delete-edge": (state, action) => {
+ const notMatchingEdge = (signature) => ({ sourceId, targetId }) => (
+ !(sourceId === signature.sourceId && targetId === signature.targetId)
+ );
+ state.edges = state.edges.filter(notMatchingEdge(action.payload));
+ },
"add-atom": (state, action) => {
state.atoms[action.payload.id] = action.payload;
},