Skip to content
Snippets Groups Projects
Commit effc4671 authored by Maximilian Giller's avatar Maximilian Giller
Browse files

Unod/redo prototype

parent 42598906
No related branches found
No related tags found
No related merge requests found
......@@ -24,8 +24,11 @@
const PLUGIN_PATH = "%WWW%";
</script>
<script src="%WWW%editor/js/manageddata.js"></script>
<script src="%WWW%editor/js/graph.js"></script>
<script src="%WWW%editor/js/tools/tool.js"></script>
<script src="%WWW%editor/js/tools/undotool.js"></script>
<script src="%WWW%editor/js/tools/redotool.js"></script>
<script src="%WWW%editor/js/tools/selecttool.js"></script>
<script src="%WWW%editor/js/tools/collecttool.js"></script>
<script src="%WWW%editor/js/tools/deletetool.js"></script>
......
editor/images/tools/redo.png

3.04 KiB

editor/images/tools/undo.png

3.07 KiB

var state;
var graph;
var graphObj;
window.onload = function () {
......@@ -8,9 +9,7 @@ window.onload = function () {
})
.then((graphConfig) => {
state = new State();
graph.data = graphConfig;
graph.addIdentifiers();
graph = new Graph(graphConfig);
load();
// Deactivate physics after a short delay
......@@ -64,7 +63,7 @@ function load() {
.nodeCanvasObject((node, ctx) => state.nodeCanvasObject(node, ctx))
.onLinkClick((link) => state.onLinkClick(link));
graph.externUpdate.push(() => {
graphObj.graphData(graph.data);
graph.onChangeCallbacks.push((data) => {
graphObj.graphData(data);
});
}
......@@ -22,54 +22,77 @@ const JSON_CONFIG = PLUGIN_PATH + "datasets/aud1.json";
const STOP_PHYSICS_DELAY = 5000; // ms
const graph = {
data: undefined,
externUpdate: [], // Register callbacks in this list
class Graph extends ManagedData {
constructor(data) {
super(Graph.addIdentifiers(data));
update() {
graph.externUpdate.forEach((fn) => fn());
},
this.onChangeCallbacks = [];
}
triggerOnChange() {
this.onChangeCallbacks.forEach((fn) => fn(this.data));
}
onRedo() {
this.triggerOnChange();
}
onUndo() {
this.triggerOnChange();
}
formatData(data) {
return this.getCleanData(data, positions = true);
}
deleteNode(nodeId) {
// Delete node from nodes
graph.data[GRAPH_NODES] = graph.data[GRAPH_NODES].filter(
this.data[GRAPH_NODES] = this.data[GRAPH_NODES].filter(
(n) => n[NODE_ID] !== nodeId
);
// Delete links with node
graph.data[GRAPH_LINKS] = graph.data[GRAPH_LINKS].filter(
this.data[GRAPH_LINKS] = this.data[GRAPH_LINKS].filter(
(l) =>
l[LINK_SOURCE][NODE_ID] !== nodeId &&
l[LINK_TARGET][NODE_ID] !== nodeId
);
},
this.storeCurrentData("Deleted node with id [" + nodeId + "]");
}
stopPhysics() {
graph.data[GRAPH_NODES].forEach((n) => {
this.data[GRAPH_NODES].forEach((n) => {
n.fx = n.x;
n.fy = n.y;
});
},
}
addIdentifiers() {
graph.data[GRAPH_NODES].forEach((n) => {
static addIdentifiers(data) {
data[GRAPH_NODES].forEach((n) => {
n.node = true;
n.link = false;
});
graph.data[GRAPH_LINKS].forEach((l) => {
data[GRAPH_LINKS].forEach((l) => {
l.node = false;
l.link = true;
});
},
return data;
}
deleteLink(sourceId, targetId) {
// Only keep links, of one of the nodes is different
graph.data[GRAPH_LINKS] = graph.data[GRAPH_LINKS].filter(
this.data[GRAPH_LINKS] = this.data[GRAPH_LINKS].filter(
(l) =>
l[LINK_SOURCE][NODE_ID] !== sourceId ||
l[LINK_TARGET][NODE_ID] !== targetId
);
},
this.storeCurrentData(
"Deleted link connecting [" + sourceId + "] with [" + targetId + "]"
);
}
isLinkOnNode(link, node) {
if (link === undefined || node === undefined) {
......@@ -84,10 +107,10 @@ const graph = {
link[LINK_SOURCE][NODE_ID] === node[NODE_ID] ||
link[LINK_TARGET][NODE_ID] === node[NODE_ID]
);
},
}
existsLink(sourceId, targetId) {
const links = graph.data[GRAPH_LINKS];
const links = this.data[GRAPH_LINKS];
for (var i = 0; i < links.length; i++) {
var link = links[i];
......@@ -100,13 +123,13 @@ const graph = {
}
return false;
},
}
connectNodes(sourceId, targetIds) {
targetIds.forEach((targetId) => {
if (
graph.existsLink(sourceId, targetId) ||
graph.existsLink(targetId, sourceId)
this.existsLink(sourceId, targetId) ||
this.existsLink(targetId, sourceId)
) {
return;
}
......@@ -116,36 +139,53 @@ const graph = {
link[LINK_SOURCE] = sourceId;
link[LINK_TARGET] = targetId;
graph.data[GRAPH_LINKS].push(link);
this.data[GRAPH_LINKS].push(link);
});
},
getCleanData() {
this.storeCurrentData(
"Created link connecting [" +
sourceId +
"] with [" +
targetIds.join() +
"]"
);
}
getCleanData(data = undefined, positions = false) {
if (data === undefined) {
data = this.data;
}
var cleanData = {};
cleanData[GRAPH_LINKS] = [];
cleanData[GRAPH_NODES] = [];
graph.data[GRAPH_LINKS].forEach((link) =>
cleanData[GRAPH_LINKS].push(graph.getCleanLink(link))
data[GRAPH_LINKS].forEach((link) =>
cleanData[GRAPH_LINKS].push(this.getCleanLink(link))
);
graph.data[GRAPH_NODES].forEach((node) =>
cleanData[GRAPH_NODES].push(graph.getCleanNode(node))
data[GRAPH_NODES].forEach((node) =>
cleanData[GRAPH_NODES].push(this.getCleanNode(node, positions))
);
console.log(cleanData);
return cleanData;
},
}
getCleanNode(node) {
getCleanNode(node, positions) {
var cleanNode = {};
NODE_PARAMS.forEach((param) => {
cleanNode[param] = node[param];
});
if (positions) {
cleanNode.fx = node.fx;
cleanNode.fy = node.fy;
}
return cleanNode;
},
}
getCleanLink(link) {
var cleanLink = {};
......@@ -161,27 +201,28 @@ const graph = {
});
return cleanLink;
},
}
existsNodeId(nodeId) {
var nodes = graph.data[GRAPH_NODES];
var nodes = this.data[GRAPH_NODES];
for (var i = 0; i < nodes.length; i++) {
if (nodes[i][NODE_ID] === nodeId) {
return true;
}
}
return false;
},
}
getUnusedNodeId() {
var id;
do {
id = graph.getRandomString();
} while (graph.existsNodeId(id));
id = this.getRandomString();
} while (this.existsNodeId(id));
return id;
},
}
getRandomString(length = 8) {
// Move to global helpers
// Based on: https://stackoverflow.com/a/1349426/7376120
var characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
......@@ -194,7 +235,7 @@ const graph = {
);
}
return result;
},
}
addLink(sourceId, targetId, linkDetails = {}) {
// Copy params
......@@ -204,15 +245,14 @@ const graph = {
if (
sourceId === undefined ||
targetId === undefined ||
graph.existsNodeId(sourceId) === false ||
graph.existsNodeId(targetId) === false
this.existsNodeId(sourceId) === false ||
this.existsNodeId(targetId) === false
) {
return;
}
// Make sure the link is unique
if (graph.existsLink(sourceId, targetId)) {
if (this.existsLink(sourceId, targetId)) {
return;
}
......@@ -224,11 +264,19 @@ const graph = {
newLink.node = false;
// Add node
graph.data[GRAPH_LINKS].push(newLink);
graph.update();
this.data[GRAPH_LINKS].push(newLink);
this.triggerOnChange();
this.storeCurrentData(
"Added custom link connecting [" +
sourceId +
"] with [" +
targetId +
"]"
);
return newLink;
},
}
addNode(nodeDetails) {
// Copy params
......@@ -236,8 +284,8 @@ const graph = {
// Make sure the ID is set and unique
if (newNode[NODE_ID] === undefined) {
newNode[NODE_ID] = graph.getUnusedNodeId();
} else if (graph.existsNodeId(newNode[NODE_ID])) {
newNode[NODE_ID] = this.getUnusedNodeId();
} else if (this.existsNodeId(newNode[NODE_ID])) {
return;
}
......@@ -246,9 +294,13 @@ const graph = {
newNode.link = false;
// Add node
graph.data[GRAPH_NODES].push(newNode);
graph.update();
this.data[GRAPH_NODES].push(newNode);
this.triggerOnChange();
this.storeCurrentData(
"Added custom node with id [" + newNode[NODE_ID] + "]"
);
return newNode;
},
};
}
}
class ManagedData {
constructor(data) {
this.data = data;
this.history = [];
this.historyPosition = 0;
this.storeCurrentData("Initial state");
}
onUndo() {}
onRedo() {}
undo() {
if (this.historyPosition + 1 >= this.history.length) {
return false;
}
this.historyPosition += 1;
this.data = JSON.parse(this.history[this.historyPosition].data);
this.onUndo();
return true;
}
redo() {
if (this.historyPosition <= 0) {
return false;
}
this.historyPosition -= 1;
this.data = JSON.parse(this.history[this.historyPosition].data);
this.onRedo();
return true;
}
formatData(data) {
return data;
}
storeCurrentData(description) {
var formattedData = this.formatData(this.data);
this.history.unshift({
description: description,
data: JSON.stringify(formattedData), // Creating a deep copy
});
// Forget about the currently stored potential future
this.history.splice(0, this.historyPosition);
this.historyPosition = 0;
}
}
const TOOLS = {
undo: new UndoTool("undo"),
redo: new RedoTool("redo"),
select: new SelectTool("select"),
collect: new CollectTool("collect"),
delete: new DeleteTool("delete"),
......
class RedoTool extends Tool {
constructor(key) {
super("Redo", "redo", key);
}
onToolActivate() {
graph.redo();
state.setTool(state.previousTool)
}
}
\ No newline at end of file
class UndoTool extends Tool {
constructor(key) {
super("Undo", "undo", key);
}
onToolActivate() {
graph.undo();
state.setTool(state.previousTool)
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment