import React from "react"; import PropTypes, { InferType } from "prop-types"; import { State } from "../state"; import * as Interactions from "../interactions"; import { Graph } from "../structures/graph/graph"; import ForceGraph from "force-graph"; import { loadGraphJson } from "../../../datasets"; import { ToolPool } from "./toolpool"; import { ToolDetails } from "./tooldetails"; import { GraphRenderer } from "./graphrenderer"; import { SpaceSelect } from "./spaceselect"; import "./editor.css"; export class Editor extends React.PureComponent< InferType<typeof Editor.propTypes>, InferType<typeof Editor.stateTypes> > { static propTypes = {}; static stateTypes = { state: State, graph: Graph, renderer: PropTypes.any, }; // TODO: Not a long term solution! public static globalState: State; public static globalGraph: Graph; public static globalRenderer: any; constructor(props: InferType<typeof Editor.propTypes>) { super(props); // 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 ..."); console.log(data); console.log(Graph.parse(data)); // Create global objects // TODO: is not loading properly console.log("Parsing ..."); 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; // } // Create renderer // 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); // }); // Set as new state console.log("Updating state ..."); this.setState({ state: newState, graph: newGraph, }); // Subscribe to global key-press events document.onkeydown = newState.onKeyDown; document.onkeyup = newState.onKeyUp; console.log("Finished loading!"); return true; } /** * 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 }, }; } 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> <div id="content"> <div id="sidepanel"> <SpaceSelect /> <hr /> <ToolPool state={this.state.state} /> <hr /> <ToolDetails /> </div> {this.state.graph ? ( <GraphRenderer graphData={this.state.graph.data} /> ) : undefined} </div> </div> ); } }