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 { // Create global objects // TODO: is not loading properly Editor.globalState = new State(); Editor.globalGraph = Graph.parse(data); // Is valid and parsed successfully? if (Editor.globalGraph == undefined) { Editor.globalState = this.state.state; Editor.globalGraph = this.state.graph; return false; } // Create renderer const renderTarget = document.getElementById("2d-graph"); const renderWidth = renderTarget.offsetWidth; Editor.globalRenderer = ForceGraph()(renderTarget); // Subscribe to interactions 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 this.setState({ state: Editor.globalState, graph: Editor.globalGraph, renderer: Editor.globalRenderer, }); // Subscribe to global key-press events document.onkeydown = this.state.state.onKeyDown; document.onkeyup = this.state.state.onKeyUp; 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> <GraphRenderer /> </div> </div> ); } }