Skip to content
Snippets Groups Projects
Commit d3e71f46 authored by Matthias Konitzny's avatar Matthias Konitzny :fire:
Browse files

Reimplemented history. Still a little bit WIP.

parent accfbe31
No related branches found
No related tags found
No related merge requests found
Pipeline #56975 passed
......@@ -27,6 +27,12 @@ export class History<HistoryDataType> {
this.createCheckpoint(initialMessage);
}
public copyCheckpointsFromHistory(other: History<HistoryDataType>) {
this.checkpoints = other.checkpoints;
this.current = other.current;
this.next = other.next;
}
public createCheckpoint(description: string) {
const checkpointData = this.data.toHistorySerializableObject();
const checkpoint = {
......@@ -40,10 +46,27 @@ export class History<HistoryDataType> {
this.checkpoints.length = this.current + 1;
}
// Remove old checkpoints if history is full
if (this.checkpoints.length == this.maxCheckpoints) {
this.checkpoints.shift();
this.current--;
}
this.checkpoints.push(checkpoint);
this.current++;
}
/**
* Clears the history and creates a new base checkpoint
* @param description Description for the new base checkpoint
*/
public clearHistory(description = "") {
this.checkpoints = [];
this.current = -1;
this.createCheckpoint(description);
}
public get currentCheckpoint() {
return this.checkpoints[this.current];
}
......@@ -57,7 +80,7 @@ export class History<HistoryDataType> {
(checkpoint) => checkpoint.id == id
);
if (index > 0) {
if (index >= 0) {
this.current = index;
return this.checkpoints[index];
}
......@@ -82,6 +105,6 @@ export class History<HistoryDataType> {
}
public hasRedoCheckpoints(): boolean {
return this.current < this.checkpoints.length;
return this.current + 1 < this.checkpoints.length;
}
}
import React from "react";
import "./historynavigator.css";
import { Checkpoint, History } from "../../common/history";
import { History } from "../../common/history";
interface HistoryNavigatorProps<HistoryDataType> {
history: History<HistoryDataType>;
onCheckpointLoad: { (checkpoint: Checkpoint<HistoryDataType>): void };
onCheckpointRequest: (id: number) => void;
onRedo: () => void;
onUndo: () => void;
}
function HistoryNavigator<HistoryDataType>({
history,
onCheckpointLoad,
onCheckpointRequest,
onUndo,
onRedo,
}: HistoryNavigatorProps<HistoryDataType>) {
return (
<div className={"history-navigator"}>
<button onClick={() => onCheckpointLoad(history.undo())}>
<button onClick={onUndo} disabled={!history.hasUndoCheckpoints()}>
Undo
</button>
<button onClick={() => onCheckpointLoad(history.redo())}>
<button onClick={onRedo} disabled={!history.hasRedoCheckpoints()}>
Redo
</button>
<select
onChange={(e) =>
onCheckpointLoad(
history.resetToCheckpoint(Number(e.target.value))
)
}
onChange={(e) => onCheckpointRequest(Number(e.target.value))}
value={history.currentCheckpoint.id}
>
{history.checkpoints.map((checkpoint) => {
......
......@@ -7,15 +7,15 @@ import NodeDetails from "./nodedetails";
import { NodeTypesEditor } from "./nodetypeseditor";
import Settings from "./settings";
import Instructions from "./instructions";
import { Checkpoint } from "../../common/history";
import { Node } from "../../common/graph/node";
import { NodeType } from "../../common/graph/nodetype";
import { SimGraphData } from "../../common/graph/graph";
import { EditorSettings, NodeDataChangeRequest } from "../editor";
interface SidepanelProps {
graph: DynamicGraph;
onCheckpointLoad: { (checkpoint: Checkpoint<SimGraphData>): void };
onCheckpointRequest: (id: number) => void;
onRedo: () => void;
onUndo: () => void;
onNodeTypeSelect: (type: NodeType) => void;
onSettingsChange: (settings: EditorSettings) => void;
onNodeDataChange: { (requests: NodeDataChangeRequest[]): void };
......@@ -26,7 +26,9 @@ interface SidepanelProps {
function Sidepanel({
graph,
onCheckpointLoad,
onCheckpointRequest,
onUndo,
onRedo,
onNodeTypeSelect,
onSettingsChange,
onNodeDataChange,
......@@ -39,7 +41,9 @@ function Sidepanel({
<div className={"editor-sidepanel-topbar"}>
<HistoryNavigator
history={graph.history}
onCheckpointLoad={onCheckpointLoad}
onCheckpointRequest={onCheckpointRequest}
onUndo={onUndo}
onRedo={onRedo}
/>
<button onClick={onSave}>Save</button>
</div>
......
......@@ -12,12 +12,13 @@ import { Node, NodeProperties } from "../common/graph/node";
import { SpaceManager } from "./components/spacemanager";
import SelectLayer from "./components/selectlayer";
import { Coordinate2D, GraphData } from "../common/graph/graph";
import { Coordinate2D, GraphData, SimGraphData } from "../common/graph/graph";
import { NodeType } from "../common/graph/nodetype";
import { GraphRenderer2D } from "./renderer";
import * as Config from "../config";
import Sidepanel from "./components/sidepanel";
import { Link } from "../common/graph/link";
import { Checkpoint } from "../common/history";
export interface NodeDataChangeRequest extends NodeProperties {
id: number;
......@@ -86,6 +87,9 @@ export class Editor extends React.PureComponent<any, stateTypes> {
this.handleNodeDeletion = this.handleNodeDeletion.bind(this);
this.handleLinkCreation = this.handleLinkCreation.bind(this);
this.handleLinkDeletion = this.handleLinkDeletion.bind(this);
this.handleCheckpointRequest = this.handleCheckpointRequest.bind(this);
this.handleUndo = this.handleUndo.bind(this);
this.handleRedo = this.handleRedo.bind(this);
document.addEventListener("keydown", (e) => {
this.keyPressed(e.key);
......@@ -238,6 +242,8 @@ export class Editor extends React.PureComponent<any, stateTypes> {
Object.assign(node, request);
}
graph.createCheckpoint(`Modified ${nodeData.length} node(s) data.`);
// Push shallow copy to state
this.setState({ graph: graph });
}
......@@ -245,6 +251,7 @@ export class Editor extends React.PureComponent<any, stateTypes> {
private handleNodeCreation(position?: Coordinate2D): Node {
const graph = Object.assign(new DynamicGraph(), this.state.graph);
const node = graph.createNode(undefined, position.x, position.y, 0, 0);
graph.createCheckpoint("Created new node.");
this.setState({
graph: graph,
......@@ -258,6 +265,7 @@ export class Editor extends React.PureComponent<any, stateTypes> {
const selectedNodes = this.state.selectedNodes.filter(
(node) => !ids.includes(node.id)
);
graph.createCheckpoint(`Deleted ${ids.length} nodes.`);
this.setState({ graph: graph, selectedNodes: selectedNodes });
}
......@@ -265,6 +273,12 @@ export class Editor extends React.PureComponent<any, stateTypes> {
private handleLinkCreation(source: number, target: number): Link {
const graph = Object.assign(new DynamicGraph(), this.state.graph);
const link = graph.createLink(source, target);
graph.createCheckpoint(
`Created link between ${graph.node(source).name} and ${
graph.node(target).name
}.`
);
this.setState({ graph: graph });
return link;
......@@ -273,7 +287,44 @@ export class Editor extends React.PureComponent<any, stateTypes> {
private handleLinkDeletion(ids: number[]) {
const graph = Object.assign(new DynamicGraph(), this.state.graph);
ids.forEach((id) => graph.deleteLink(id));
graph.createCheckpoint(`Deleted ${ids.length} link(s).`);
this.setState({ graph: graph });
}
private loadGraphFromCheckpoint(checkpoint: Checkpoint<SimGraphData>) {
const graph = new DynamicGraph();
graph.fromSerializedObject(checkpoint.data);
graph.history.copyCheckpointsFromHistory(this.state.graph.history);
const selectedNodes = this.state.selectedNodes
.map((node) => graph.node(node.id))
.filter((node) => node != undefined);
this.setState({ graph: graph, selectedNodes: selectedNodes });
}
private handleCheckpointRequest(id: number) {
const history = this.state.graph.history;
const checkpoint = history.resetToCheckpoint(id);
this.loadGraphFromCheckpoint(checkpoint);
}
private handleUndo() {
const history = this.state.graph.history;
const checkpoint = history.undo();
this.loadGraphFromCheckpoint(checkpoint);
}
private handleRedo() {
const history = this.state.graph.history;
const checkpoint = history.redo();
this.loadGraphFromCheckpoint(checkpoint);
}
private clearHistory(description = "") {
const graph = Object.assign(new DynamicGraph(), this.state.graph);
graph.history.clearHistory(description);
this.setState({ graph: graph });
}
......@@ -310,6 +361,11 @@ export class Editor extends React.PureComponent<any, stateTypes> {
onNodeDeletion={this.handleNodeDeletion}
onLinkCreation={this.handleLinkCreation}
onLinkDeletion={this.handleLinkDeletion}
onEngineStop={() =>
this.clearHistory(
`Loaded graph ${this.state.spaceId}.`
)
}
selectedNodes={this.state.selectedNodes}
settings={this.state.settings}
/>
......@@ -318,14 +374,9 @@ export class Editor extends React.PureComponent<any, stateTypes> {
<Sidepanel
graph={this.state.graph}
onCheckpointLoad={(checkpoint) => {
const graph = new DynamicGraph();
this.setState({
graph: graph.fromSerializedObject(
checkpoint.data
),
});
}}
onCheckpointRequest={this.handleCheckpointRequest}
onUndo={this.handleUndo}
onRedo={this.handleRedo}
onNodeTypeSelect={this.handleNodeTypeSelect}
onSettingsChange={(settings) =>
this.setState({ settings: settings })
......
......@@ -20,6 +20,14 @@ export class DynamicGraph extends Common.Graph {
}
}
/**
* Convenience-wrapper for history.createCheckpoint()
* @param description Checkpoint description
*/
public createCheckpoint(description = "") {
this.history.createCheckpoint(description);
}
public fromSerializedObject(data: GraphData | SimGraphData): DynamicGraph {
super.fromSerializedObject(data);
this.history = new History<SimGraphData>(
......
......@@ -33,6 +33,7 @@ export class GraphRenderer2D extends React.PureComponent<
onNodeDeletion: PropTypes.func,
onLinkCreation: PropTypes.func,
onLinkDeletion: PropTypes.func,
onEngineStop: PropTypes.func,
/**
* Collection of all currently selected nodes. Can also be undefined or empty.
*/
......@@ -176,7 +177,7 @@ export class GraphRenderer2D extends React.PureComponent<
if (this.warmupTicks <= 0) {
return;
}
this.props.onEngineStop();
this.warmupTicks = 0; // Only warm up once, so stop warming up after the first freeze
}
......
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