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 * as Config from "./config";
import { createRoot } from "react-dom/client";
import React from "react";
const container = document.getElementById("knowledge-space-editor");
const root = createRoot(container);
root.render(<Editor spaceId={Config.SPACE} />);
root.render(<Editor />);
import { SerializableItem } from "./serializableitem";
interface SavePoint<DataType> {
export interface Checkpoint<DataType> {
id: number;
description: string;
data: DataType;
......@@ -11,7 +11,7 @@ export class History<HistoryDataType> {
private current: number;
private data: SerializableItem<unknown, HistoryDataType>;
public checkpoints: SavePoint<HistoryDataType>[];
public checkpoints: Checkpoint<HistoryDataType>[];
private next: number;
constructor(
......@@ -22,7 +22,7 @@ export class History<HistoryDataType> {
this.data = data;
this.maxCheckpoints = maxCheckpoints;
this.checkpoints = [];
this.current = 0;
this.current = -1;
this.next = 0;
this.createCheckpoint(initialMessage);
}
......@@ -36,9 +36,12 @@ export class History<HistoryDataType> {
};
// 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.current++;
}
public get currentCheckpoint() {
......@@ -49,7 +52,7 @@ export class History<HistoryDataType> {
return this.checkpoints.map((savepoint) => savepoint.description);
}
public resetToCheckpoint(id: number): SavePoint<HistoryDataType> {
public resetToCheckpoint(id: number): Checkpoint<HistoryDataType> {
const index = this.checkpoints.findIndex(
(checkpoint) => checkpoint.id == id
);
......@@ -60,14 +63,14 @@ export class History<HistoryDataType> {
}
}
public undo(): SavePoint<HistoryDataType> {
public undo(): Checkpoint<HistoryDataType> {
if (this.hasUndoCheckpoints()) {
this.current--;
}
return this.checkpoints[this.current];
}
public redo(): SavePoint<HistoryDataType> {
public redo(): Checkpoint<HistoryDataType> {
if (this.hasRedoCheckpoints()) {
this.current++;
}
......
import React, { ChangeEvent } from "react";
import { saveGraphJson } from "../../common/datasets";
import React from "react";
import "./historynavigator.css";
import { History } from "../../common/history";
import { Checkpoint, History } from "../../common/history";
type propTypes = {
history: History<any>;
spaceId: string;
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();
}
}
interface HistoryNavigatorProps<HistoryDataType> {
history: History<HistoryDataType>;
onCheckpointLoad: { (checkpoint: Checkpoint<HistoryDataType>): void };
}
render(): React.ReactNode {
return (
<div id="history-navigator">
<button onClick={this.handleUndo}>Undo</button>
<button onClick={this.handleRedo}>Redo</button>
<select
onChange={this.handleSelectSavepoint}
value={
this.props.history
? this.props.history.currentCheckpoint.id
: 0
}
>
{this.props.history
? this.props.history.checkpoints.map((savepoint) => {
return (
<option
key={savepoint.id}
value={savepoint.id}
>
{savepoint.description}
</option>
);
})
: ""}
</select>
<button
onClick={this.handleSave}
// disabled={ TODO: Reimplement
// this.props.history
// ? !this.props.history.hasUnsavedChanges()
// : true
// }
>
Save
</button>
</div>
);
}
function HistoryNavigator<HistoryDataType>({
history,
onCheckpointLoad,
}: HistoryNavigatorProps<HistoryDataType>) {
return (
<div id="history-navigator">
<button onClick={() => onCheckpointLoad(history.undo())}>
Undo
</button>
<button onClick={() => onCheckpointLoad(history.redo())}>
Redo
</button>
<select
onChange={(e) =>
onCheckpointLoad(
history.resetToCheckpoint(Number(e.target.value))
)
}
value={history.currentCheckpoint.id}
>
{history.checkpoints.map((checkpoint) => {
return (
<option key={checkpoint.id} value={checkpoint.id}>
{checkpoint.description}
</option>
);
})}
</select>
</div>
);
}
export default HistoryNavigator;
......@@ -28,6 +28,7 @@ export class NodeTypesEditor extends React.Component<propTypes> {
return (
<div id="node-types-editor">
<h3>Node types</h3>
<ul>
{this.props.graph.objectGroups.map((type) => (
<NodeTypeEntry
......
import React, { ChangeEvent } from "react";
import { ReactNode } from "react";
import { listAllSpaces } from "../../common/datasets";
import React from "react";
import "./spaceselect.css";
type propTypes = {
interface SpaceSelectProps {
onLoadSpace: (spaceId: string) => boolean;
};
type stateTypes = {
spaces: 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 + "]");
}
}
spaceId: string;
}
render(): ReactNode {
return (
<div id="spaceselect">
<select onChange={this.handleSelectChange}>
{this.state.spaces.map((spaceName: string) => (
<option key={spaceName} value={spaceName}>
{spaceName}
</option>
))}
</select>
</div>
);
}
function SpaceSelect({ onLoadSpace, spaces, spaceId }: SpaceSelectProps) {
return (
<div id="spaceselect">
<select
onChange={(event) => onLoadSpace(event.target.value)}
defaultValue={spaceId}
>
{spaces.map((spaceName: string) => (
<option key={spaceName} value={spaceName}>
{spaceName}
</option>
))}
</select>
</div>
);
}
export default SpaceSelect;
import React from "react";
import { DynamicGraph } from "./graph";
import { loadGraphJson } from "../common/datasets";
import { listAllSpaces, loadGraphJson } from "../common/datasets";
import { NodeDetails } from "./components/nodedetails";
import { SpaceSelect } from "./components/spaceselect";
import SpaceSelect from "./components/spaceselect";
import "./editor.css";
import * as Helpers from "../common/helpers";
import { Node } from "../common/graph/node";
......@@ -15,10 +15,10 @@ import { NodeType } from "../common/graph/nodetype";
import { GraphRenderer2D } from "./renderer";
import Instructions from "./components/instructions";
import Settings from "./components/settings";
import HistoryNavigator from "./components/historynavigator";
import * as Config from "../config";
type propTypes = {
spaceId: string;
};
type propTypes = {};
type stateTypes = {
/**
* Graph structure holding the basic information.
......@@ -49,6 +49,10 @@ type stateTypes = {
* Collection of all currently selected nodes. Can also be undefined or empty.
*/
selectedNodes: Node[];
spaces: string[];
spaceId: string;
};
/**
......@@ -78,6 +82,8 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
this.graphContainer = React.createRef();
listAllSpaces().then((spaces) => this.setState({ spaces: spaces }));
// Set as new state
this.state = {
graph: undefined,
......@@ -86,6 +92,8 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
graphWidth: 1000,
selectedNodes: [],
keys: {},
spaces: [],
spaceId: Config.SPACE,
};
}
......@@ -105,9 +113,9 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
* Tries to load initial graph after webpage finished loading.
*/
componentDidMount() {
if (this.props.spaceId !== undefined) {
if (this.state.spaceId !== undefined) {
// 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> {
this.selectNodes(nodesWithType);
}
private onNodeDataChange() {}
render(): React.ReactNode {
return (
<div id="ks-editor">
<h1>Interface</h1>
<SpaceSelect onLoadSpace={this.loadSpace} />
<SpaceSelect
onLoadSpace={this.loadSpace}
spaces={this.state.spaces}
spaceId={this.state.spaceId}
/>
<SpaceManager />
<div id="content">
{this.state.graph && (
{this.state.graph && (
<div id="content">
<div
id="force-graph-renderer"
ref={this.graphContainer}
......@@ -241,26 +255,25 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
/>
</SelectLayer>
</div>
)}
{this.state.graph && (
<div id="sidepanel">
{/*<HistoryNavigator*/}
{/* spaceId="space"*/}
{/* history={this.state.graph.history}*/}
{/* onChange={this.onGraphDataChange}*/}
{/*/>*/}
<HistoryNavigator
history={this.state.graph.history}
onCheckpointLoad={(checkpoint) => {
const graph = new DynamicGraph();
this.setState({
graph: graph.fromSerializedObject(
checkpoint.data
),
});
}}
/>
<hr />
<NodeDetails
selectedNodes={this.state.selectedNodes}
allTypes={
this.state.graph
? this.state.graph.objectGroups
: []
}
allTypes={this.state.graph.objectGroups}
onChange={this.forceUpdate}
/>
<hr />
<h3>Node types</h3>
<NodeTypesEditor
onChange={this.forceUpdate}
graph={this.state.graph}
......@@ -270,7 +283,9 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
<Settings
labelVisibility={this.state.visibleLabels}
onLabelVisibilityChange={(visible) =>
this.setState({ visibleLabels: visible })
this.setState({
visibleLabels: visible,
})
}
connectOnDrag={this.state.connectOnDrag}
onConnectOnDragChange={(connectOnDrag) =>
......@@ -285,8 +300,8 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
connectOnDragEnabled={this.state.connectOnDrag}
/>
</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