Skip to content
Snippets Groups Projects
editor.tsx 7.38 KiB
Newer Older
import React from "react";
import { State } from "../state";
import * as Interactions from "../interactions";
import { Graph, GraphData } from "../structures/graph/graph";
import { loadGraphJson } from "../../../datasets";
import { ToolPool } from "./toolpool";
import { ToolDetails } from "./tooldetails";
import { SpaceSelect } from "./spaceselect";
import "./editor.css";
import ReactForceGraph2d from "react-force-graph-2d";
import { Node } from "../structures/graph/node";
import { HistoryNavigator } from "./historynavigator";
type propTypes = any;
type stateTypes = {
    state: State;
    graph: Graph;
    renderer: any;
};

export class Editor extends React.PureComponent<propTypes, stateTypes> {
    // TODO: Not a long term solution!
    public static globalState: State;
    public static globalGraph: Graph;
    public static globalRenderer: any;

    constructor(props: propTypes) {
        this.loadGraph = this.loadGraph.bind(this);
        this.loadSpace = this.loadSpace.bind(this);
        this.handleNodeClick = this.handleNodeClick.bind(this);
        this.onHistoryChange = this.onHistoryChange.bind(this);
        // Set as new state
        this.state = {
            state: undefined,
            graph: undefined,
            renderer: undefined,
        };

        Interactions.initInteractions();

        // Load initial space
        this.loadSpace("space");
    }

    /**
     * Loads a space from the database to the editor.
     * @param spaceId Id of space to load.
     * @returns Promise with boolean value that is true, if successful.
     */
    public loadSpace(spaceId: string): any {
        return loadGraphJson(spaceId).then(this.loadGraph);
    }

    /**
     * Loads another graph based on the data supplied. Note: Naming currently suggests that this only loads a GRAPH, not a SPACE. Needs further work and implementation to see if that makes sense or not.
     * @param data Serialized graph data.
     * @returns True, if successful.
     */
    public loadGraph(data: any): boolean {
        console.log("Starting to load new graph ...");
        console.log(data);
        // Create global objects
        const newState = new State();
        const newGraph = Graph.parse(data);
        Editor.globalState = newState;
        Editor.globalGraph = newGraph;

        // Is valid and parsed successfully?
        // TODO: Check doesn't work for some reason, always returns true, even tho it should be considered defined
        // if (Editor.globalGraph == undefined) {
        //     Editor.globalState = this.state.state;
        //     Editor.globalGraph = this.state.graph;
        //     return false;
        // }
        // console.log("Creating renderer ...");
        // const renderTarget = document.getElementById("2d-graph");
        // const renderWidth = renderTarget.offsetWidth;
        // Editor.globalRenderer = ForceGraph()(renderTarget);

        // // Subscribe to interactions
        // console.log("Subscribing to events ...");
        // Editor.globalRenderer
        //     .height(600)
        //     .width(renderWidth)
        //     .graphData(Editor.globalGraph.data)
        //     .nodeLabel("label")
        //     .linkColor((link: any) => Editor.globalState.linkColor(link))
        //     .nodeColor((node: any) => Editor.globalState.nodeColor(node))
        //     .onNodeClick((node: any) => Editor.globalState.onNodeClick(node))
        //     .onNodeDragEnd((node: any, translate: any) =>
        //         Editor.globalState.onNodeDragEnd(node, translate)
        //     )
        //     .autoPauseRedraw(false) // keep redrawing after engine has stopped
        //     .linkWidth((link: any) => Editor.globalState.linkWidth(link))
        //     .linkDirectionalParticles(
        //         Editor.globalState.linkDirectionalParticles()
        //     )
        //     .linkDirectionalParticleWidth((link: any) =>
        //         Editor.globalState.linkDirectionalParticleWidth(link)
        //     )
        //     .onBackgroundClick((event: any) =>
        //         Editor.globalState.onBackgroundClick(
        //             event,
        //             this.extractPositions(event)
        //         )
        //     )
        //     .nodeCanvasObjectMode((node: any) =>
        //         Editor.globalState.nodeCanvasObjectMode(node)
        //     )
        //     .nodeCanvasObject((node: any, ctx: any, globalScale: any) =>
        //         Editor.globalState.nodeCanvasObject(node, ctx, globalScale)
        //     )
        //     .linkCanvasObjectMode((link: any) =>
        //         Editor.globalState.linkCanvasObjectMode(link)
        //     )
        //     .linkCanvasObject((link: any, ctx: any, globalScale: any) =>
        //         Editor.globalState.linkCanvasObject(link, ctx, globalScale)
        //     )
        //     .onLinkClick((link: any) => Editor.globalState.onLinkClick(link));

        // // Connect update event
        // Editor.globalGraph.onChangeCallbacks.push((data) => {
        //     Editor.globalRenderer.graphData(data);
        // });
            state: newState,
            graph: newGraph,
        this.state.graph.onChangeCallbacks.push(this.onHistoryChange);

        // Subscribe to global key-press events
        document.onkeydown = newState.onKeyDown;
        document.onkeyup = newState.onKeyUp;
    /**
     * Propagates the changed state of the graph.
     */
    private onHistoryChange() {
        this.forceUpdate();
    }

    /**
     * Calculates the corresponding coordinates for a click event for easier further processing.
     * @param event The corresponding click event.
     * @returns Coordinates in graph and coordinates in browser window.
     */
    private extractPositions(event: any): {
        graph: { x: number; y: number };
        window: { x: number; y: number };
    } {
        return {
            graph: this.state.renderer.screen2GraphCoords(
                event.layerX,
                event.layerY
            ),
            window: { x: event.clientX, y: event.clientY },
        };
    }
    private handleNodeClick(node: Node) {
        node.delete();
        this.forceUpdate();
    render(): React.ReactNode {
        // The id "ks-editor" indicates, that the javascript associated with this should automatically be executed
        return (
            <div id="ks-editor">
                <h1>Interface</h1>
                <SpaceSelect onLoadSpace={this.loadSpace} />
                <div id="content">
                    <div id="sidepanel">
                        <HistoryNavigator
                            spaceId="space"
                            historyObject={this.state.graph}
                        <hr />
                        <ToolPool state={this.state.state} />
                        <hr />
                        <ToolDetails />
                    </div>
                    {this.state.graph ? (
                        <ReactForceGraph2d
                            graphData={this.state.graph.data}
                            onNodeClick={this.handleNodeClick}
                            autoPauseRedraw={false}
                        />
                    ) : undefined}