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

Reworked history navigation and space selection.

parent 88ba7e21
No related branches found
No related tags found
No related merge requests found
import { Editor } from "./editor/editor"; import { Editor } from "./editor/editor";
import * as Config from "./config";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import React from "react"; import React from "react";
const container = document.getElementById("knowledge-space-editor"); const container = document.getElementById("knowledge-space-editor");
const root = createRoot(container); const root = createRoot(container);
root.render(<Editor spaceId={Config.SPACE} />); root.render(<Editor />);
import { SerializableItem } from "./serializableitem"; import { SerializableItem } from "./serializableitem";
interface SavePoint<DataType> { export interface Checkpoint<DataType> {
id: number; id: number;
description: string; description: string;
data: DataType; data: DataType;
...@@ -11,7 +11,7 @@ export class History<HistoryDataType> { ...@@ -11,7 +11,7 @@ export class History<HistoryDataType> {
private current: number; private current: number;
private data: SerializableItem<unknown, HistoryDataType>; private data: SerializableItem<unknown, HistoryDataType>;
public checkpoints: SavePoint<HistoryDataType>[]; public checkpoints: Checkpoint<HistoryDataType>[];
private next: number; private next: number;
constructor( constructor(
...@@ -22,7 +22,7 @@ export class History<HistoryDataType> { ...@@ -22,7 +22,7 @@ export class History<HistoryDataType> {
this.data = data; this.data = data;
this.maxCheckpoints = maxCheckpoints; this.maxCheckpoints = maxCheckpoints;
this.checkpoints = []; this.checkpoints = [];
this.current = 0; this.current = -1;
this.next = 0; this.next = 0;
this.createCheckpoint(initialMessage); this.createCheckpoint(initialMessage);
} }
...@@ -36,9 +36,12 @@ export class History<HistoryDataType> { ...@@ -36,9 +36,12 @@ export class History<HistoryDataType> {
}; };
// Remove potential history which is not relevant anymore (maybe caused by undo ops) // Remove potential history which is not relevant anymore (maybe caused by undo ops)
this.checkpoints.length = ++this.current; if (this.current < this.checkpoints.length - 1 && this.current > 0) {
this.checkpoints.length = this.current + 1;
}
this.checkpoints.push(checkpoint); this.checkpoints.push(checkpoint);
this.current++;
} }
public get currentCheckpoint() { public get currentCheckpoint() {
...@@ -49,7 +52,7 @@ export class History<HistoryDataType> { ...@@ -49,7 +52,7 @@ export class History<HistoryDataType> {
return this.checkpoints.map((savepoint) => savepoint.description); return this.checkpoints.map((savepoint) => savepoint.description);
} }
public resetToCheckpoint(id: number): SavePoint<HistoryDataType> { public resetToCheckpoint(id: number): Checkpoint<HistoryDataType> {
const index = this.checkpoints.findIndex( const index = this.checkpoints.findIndex(
(checkpoint) => checkpoint.id == id (checkpoint) => checkpoint.id == id
); );
...@@ -60,14 +63,14 @@ export class History<HistoryDataType> { ...@@ -60,14 +63,14 @@ export class History<HistoryDataType> {
} }
} }
public undo(): SavePoint<HistoryDataType> { public undo(): Checkpoint<HistoryDataType> {
if (this.hasUndoCheckpoints()) { if (this.hasUndoCheckpoints()) {
this.current--; this.current--;
} }
return this.checkpoints[this.current]; return this.checkpoints[this.current];
} }
public redo(): SavePoint<HistoryDataType> { public redo(): Checkpoint<HistoryDataType> {
if (this.hasRedoCheckpoints()) { if (this.hasRedoCheckpoints()) {
this.current++; this.current++;
} }
......
import React, { ChangeEvent } from "react"; import React from "react";
import { saveGraphJson } from "../../common/datasets";
import "./historynavigator.css"; import "./historynavigator.css";
import { History } from "../../common/history"; import { Checkpoint, History } from "../../common/history";
type propTypes = { interface HistoryNavigatorProps<HistoryDataType> {
history: History<any>; history: History<HistoryDataType>;
spaceId: string; onCheckpointLoad: { (checkpoint: Checkpoint<HistoryDataType>): void };
onChange: { (): void }; }
};
export class HistoryNavigator extends React.Component<propTypes> {
constructor(props: propTypes) {
super(props);
this.handleUndo = this.handleUndo.bind(this);
this.handleRedo = this.handleRedo.bind(this);
this.handleSave = this.handleSave.bind(this);
this.handleSelectSavepoint = this.handleSelectSavepoint.bind(this);
}
/**
* Undo a step in the history object.
*/
handleUndo() {
this.props.history.undo();
}
/**
* Redo a step in the hisory object.
*/
handleRedo() {
this.props.history.redo();
}
/**
* Saves current data of history object.
*/
handleSave() {
this.props.history.createCheckpoint("Saved graph.");
saveGraphJson(
this.props.spaceId,
this.props.history.currentCheckpoint.data
);
// TODO: Save successful?
// this.props.historyObject.markChangesAsSaved(); TODO: Reimplement
alert(
"Saved! (Though not for sure, currently not checking for success of failure)"
);
this.forceUpdate();
}
/**
* Loads selected savepoint into history object.
*/
handleSelectSavepoint(e: ChangeEvent<HTMLSelectElement>) {
if (!this.props.history.resetToCheckpoint(Number(e.target.value))) {
alert("Something went wrong, could not load savepoint.");
} else {
this.props.onChange();
}
}
render(): React.ReactNode { function HistoryNavigator<HistoryDataType>({
return ( history,
<div id="history-navigator"> onCheckpointLoad,
<button onClick={this.handleUndo}>Undo</button> }: HistoryNavigatorProps<HistoryDataType>) {
<button onClick={this.handleRedo}>Redo</button> return (
<select <div id="history-navigator">
onChange={this.handleSelectSavepoint} <button onClick={() => onCheckpointLoad(history.undo())}>
value={ Undo
this.props.history </button>
? this.props.history.currentCheckpoint.id <button onClick={() => onCheckpointLoad(history.redo())}>
: 0 Redo
} </button>
> <select
{this.props.history onChange={(e) =>
? this.props.history.checkpoints.map((savepoint) => { onCheckpointLoad(
return ( history.resetToCheckpoint(Number(e.target.value))
<option )
key={savepoint.id} }
value={savepoint.id} value={history.currentCheckpoint.id}
> >
{savepoint.description} {history.checkpoints.map((checkpoint) => {
</option> return (
); <option key={checkpoint.id} value={checkpoint.id}>
}) {checkpoint.description}
: ""} </option>
</select> );
<button })}
onClick={this.handleSave} </select>
// disabled={ TODO: Reimplement </div>
// this.props.history );
// ? !this.props.history.hasUnsavedChanges()
// : true
// }
>
Save
</button>
</div>
);
}
} }
export default HistoryNavigator;
...@@ -28,6 +28,7 @@ export class NodeTypesEditor extends React.Component<propTypes> { ...@@ -28,6 +28,7 @@ export class NodeTypesEditor extends React.Component<propTypes> {
return ( return (
<div id="node-types-editor"> <div id="node-types-editor">
<h3>Node types</h3>
<ul> <ul>
{this.props.graph.objectGroups.map((type) => ( {this.props.graph.objectGroups.map((type) => (
<NodeTypeEntry <NodeTypeEntry
......
import React, { ChangeEvent } from "react"; import React from "react";
import { ReactNode } from "react";
import { listAllSpaces } from "../../common/datasets";
import "./spaceselect.css"; import "./spaceselect.css";
type propTypes = { interface SpaceSelectProps {
onLoadSpace: (spaceId: string) => boolean; onLoadSpace: (spaceId: string) => boolean;
};
type stateTypes = {
spaces: string[]; spaces: string[];
}; spaceId: string;
}
export class SpaceSelect extends React.Component<propTypes, stateTypes> {
constructor(props: propTypes) {
super(props);
this.state = {
spaces: [],
};
this.handleSelectChange = this.handleSelectChange.bind(this);
this.updateSpaceList = this.updateSpaceList.bind(this);
listAllSpaces().then(this.updateSpaceList);
}
/**
* Render list of spaces as available options.
* @param spaceList Array containing all available space names.
*/
updateSpaceList(spaceList: string[]) {
this.setState({
spaces: spaceList,
});
}
/**
* Triggered when another option (space) is selected. Calls given function to load specified space.
* @param e Select-event that references the select element and allows to access the new value (space).
*/
handleSelectChange(e: ChangeEvent<HTMLSelectElement>) {
const success = this.props.onLoadSpace(e.target.value);
if (!success) {
alert("Failed to load space with id [" + e.target.value + "]");
}
}
render(): ReactNode { function SpaceSelect({ onLoadSpace, spaces, spaceId }: SpaceSelectProps) {
return ( return (
<div id="spaceselect"> <div id="spaceselect">
<select onChange={this.handleSelectChange}> <select
{this.state.spaces.map((spaceName: string) => ( onChange={(event) => onLoadSpace(event.target.value)}
<option key={spaceName} value={spaceName}> defaultValue={spaceId}
{spaceName} >
</option> {spaces.map((spaceName: string) => (
))} <option key={spaceName} value={spaceName}>
</select> {spaceName}
</div> </option>
); ))}
} </select>
</div>
);
} }
export default SpaceSelect;
import React from "react"; import React from "react";
import { DynamicGraph } from "./graph"; import { DynamicGraph } from "./graph";
import { loadGraphJson } from "../common/datasets"; import { listAllSpaces, loadGraphJson } from "../common/datasets";
import { NodeDetails } from "./components/nodedetails"; import { NodeDetails } from "./components/nodedetails";
import { SpaceSelect } from "./components/spaceselect"; import SpaceSelect from "./components/spaceselect";
import "./editor.css"; import "./editor.css";
import * as Helpers from "../common/helpers"; import * as Helpers from "../common/helpers";
import { Node } from "../common/graph/node"; import { Node } from "../common/graph/node";
...@@ -15,10 +15,10 @@ import { NodeType } from "../common/graph/nodetype"; ...@@ -15,10 +15,10 @@ import { NodeType } from "../common/graph/nodetype";
import { GraphRenderer2D } from "./renderer"; import { GraphRenderer2D } from "./renderer";
import Instructions from "./components/instructions"; import Instructions from "./components/instructions";
import Settings from "./components/settings"; import Settings from "./components/settings";
import HistoryNavigator from "./components/historynavigator";
import * as Config from "../config";
type propTypes = { type propTypes = {};
spaceId: string;
};
type stateTypes = { type stateTypes = {
/** /**
* Graph structure holding the basic information. * Graph structure holding the basic information.
...@@ -49,6 +49,10 @@ type stateTypes = { ...@@ -49,6 +49,10 @@ type stateTypes = {
* Collection of all currently selected nodes. Can also be undefined or empty. * Collection of all currently selected nodes. Can also be undefined or empty.
*/ */
selectedNodes: Node[]; selectedNodes: Node[];
spaces: string[];
spaceId: string;
}; };
/** /**
...@@ -78,6 +82,8 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -78,6 +82,8 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
this.graphContainer = React.createRef(); this.graphContainer = React.createRef();
listAllSpaces().then((spaces) => this.setState({ spaces: spaces }));
// Set as new state // Set as new state
this.state = { this.state = {
graph: undefined, graph: undefined,
...@@ -86,6 +92,8 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -86,6 +92,8 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
graphWidth: 1000, graphWidth: 1000,
selectedNodes: [], selectedNodes: [],
keys: {}, keys: {},
spaces: [],
spaceId: Config.SPACE,
}; };
} }
...@@ -105,9 +113,9 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -105,9 +113,9 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
* Tries to load initial graph after webpage finished loading. * Tries to load initial graph after webpage finished loading.
*/ */
componentDidMount() { componentDidMount() {
if (this.props.spaceId !== undefined) { if (this.state.spaceId !== undefined) {
// Load initial space // Load initial space
this.loadSpace(this.props.spaceId); this.loadSpace(this.state.spaceId);
} }
} }
...@@ -205,14 +213,20 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -205,14 +213,20 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
this.selectNodes(nodesWithType); this.selectNodes(nodesWithType);
} }
private onNodeDataChange() {}
render(): React.ReactNode { render(): React.ReactNode {
return ( return (
<div id="ks-editor"> <div id="ks-editor">
<h1>Interface</h1> <h1>Interface</h1>
<SpaceSelect onLoadSpace={this.loadSpace} /> <SpaceSelect
onLoadSpace={this.loadSpace}
spaces={this.state.spaces}
spaceId={this.state.spaceId}
/>
<SpaceManager /> <SpaceManager />
<div id="content"> {this.state.graph && (
{this.state.graph && ( <div id="content">
<div <div
id="force-graph-renderer" id="force-graph-renderer"
ref={this.graphContainer} ref={this.graphContainer}
...@@ -241,26 +255,25 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -241,26 +255,25 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
/> />
</SelectLayer> </SelectLayer>
</div> </div>
)}
{this.state.graph && (
<div id="sidepanel"> <div id="sidepanel">
{/*<HistoryNavigator*/} <HistoryNavigator
{/* spaceId="space"*/} history={this.state.graph.history}
{/* history={this.state.graph.history}*/} onCheckpointLoad={(checkpoint) => {
{/* onChange={this.onGraphDataChange}*/} const graph = new DynamicGraph();
{/*/>*/} this.setState({
graph: graph.fromSerializedObject(
checkpoint.data
),
});
}}
/>
<hr /> <hr />
<NodeDetails <NodeDetails
selectedNodes={this.state.selectedNodes} selectedNodes={this.state.selectedNodes}
allTypes={ allTypes={this.state.graph.objectGroups}
this.state.graph
? this.state.graph.objectGroups
: []
}
onChange={this.forceUpdate} onChange={this.forceUpdate}
/> />
<hr /> <hr />
<h3>Node types</h3>
<NodeTypesEditor <NodeTypesEditor
onChange={this.forceUpdate} onChange={this.forceUpdate}
graph={this.state.graph} graph={this.state.graph}
...@@ -270,7 +283,9 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -270,7 +283,9 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
<Settings <Settings
labelVisibility={this.state.visibleLabels} labelVisibility={this.state.visibleLabels}
onLabelVisibilityChange={(visible) => onLabelVisibilityChange={(visible) =>
this.setState({ visibleLabels: visible }) this.setState({
visibleLabels: visible,
})
} }
connectOnDrag={this.state.connectOnDrag} connectOnDrag={this.state.connectOnDrag}
onConnectOnDragChange={(connectOnDrag) => onConnectOnDragChange={(connectOnDrag) =>
...@@ -285,8 +300,8 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -285,8 +300,8 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
connectOnDragEnabled={this.state.connectOnDrag} connectOnDragEnabled={this.state.connectOnDrag}
/> />
</div> </div>
)} </div>
</div> )}
</div> </div>
); );
} }
......
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