Skip to content
Snippets Groups Projects
editor.tsx 14.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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";
    
    
    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)
    
                .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));
    
            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="box-select-layer">
                        <div id="2d-graph"></div>
                    </div>
    
                    <ToolPool state={this.state.state} />
    
                    <section id="tool-menu">
                        <div id="delete-menu" className="hidden">
                            <p>
                                Drag and drop while pressing SHIFT to delete all the
                                nodes that are being selected.
                            </p>
                        </div>
                        <div id="collect-menu" className="hidden">
                            <h3>Collected items</h3>
                            <button id="clear-collection">Clear</button>
                            <ul id="selected-items"></ul>
                        </div>
                        <div id="select-menu" className="">
                            <p id="nothing-selected">Nothing selected</p>
                            <div id="node-selected" className="hidden">
                                <label htmlFor="node-name" hidden>
                                    Name
                                </label>
                                <br />
                                <input
                                    type="text"
                                    id="node-name"
                                    name="node-name"
                                    placeholder="Enter name"
                                    className="bottom-space"
                                ></input>
                                <br />
                                <label htmlFor="node-description">
                                    Description
                                </label>
                                <br />
                                <textarea
                                    id="node-description"
                                    name="node-description"
                                    className="bottom-space"
                                ></textarea>
                                <br />
                                <label htmlFor="node-image">Node Image</label>
                                <br />
                                <img
                                    id="node-image-preview"
                                    className="preview-image"
                                    src=""
                                />
                                <br />
                                <input
                                    type="text"
                                    id="node-image"
                                    name="node-image"
                                    placeholder="Enter file name or URL"
                                    className="bottom-space"
                                />
                                <br />
                                <label htmlFor="node-detail-image">
                                    Info Image
                                </label>
                                <br />
                                <img
                                    id="node-detail-image-preview"
                                    className="preview-image"
                                    src=""
                                />
                                <br />
                                <input
                                    type="text"
                                    id="node-detail-image"
                                    name="node-detail-image"
                                    placeholder="Enter file name or URL"
                                    className="bottom-space"
                                />
                                <br />
                                <label htmlFor="node-type">Type</label>
                                <br />
                                <select
                                    id="node-type"
                                    name="node-type"
                                    className="bottom-space"
                                >
                                    <option value="Vorlesung">Vorlesung</option>
                                    <option value="Algorithmus">Algorithmus</option>
                                    <option value="Definition">Definition</option>
                                    <option value="Beispiel">Beispiel</option>
                                    <option value="Übung">Übung</option>
                                    <option value="Kapitel">Kapitel</option>
                                </select>
                                <br />
                                <label htmlFor="node-video">Video</label>
                                <br />
                                <input
                                    type="text"
                                    placeholder="Video URL"
                                    id="node-video"
                                    name="node-video"
                                ></input>
                                <br />
                                <label htmlFor="node-references">
                                    References
                                </label>{" "}
                                <small>One URL per line</small>
                                <br />
                                <textarea
                                    id="node-references"
                                    name="node-references"
                                    className="bottom-space"
                                ></textarea>
                            </div>
                            <div id="link-selected" className="hidden">
                                <h3 id="link-name"></h3>
                            </div>
                        </div>
                        <div id="settings-menu" className="hidden">
                            <label htmlFor="label-toggle" className="bottom-space">
                                <input
                                    type="checkbox"
                                    checked
                                    id="label-toggle"
                                    name="label-toggle"
                                ></input>
                                Show labels in graph
                            </label>
                            <br />
                            <br />
    
                            <h3>Space</h3>
                            <label htmlFor="space-id-select">Currently open</label>
                            <br />
                            <select
                                id="space-id-select"
                                name="space-id-select"
                                className="bottom-space"
                            ></select>
    
                            <br />
                            <br />
    
                            <h3>Physics Simulation</h3>
    
                            <button
                                id="reanimate-button"
                                name="reanimate-button"
                                className="bottom-space"
                            >
                                Re-simulate
                            </button>
    
                            <br />
    
                            <label htmlFor="stop-physics-delay">
                                Amount of time [in seconds] after which the physics
                                simulation is stopped
                            </label>
                            <br />
                            <input
                                type="number"
                                onKeyPress={(event) =>
                                    (event.charCode != 8 && event.charCode == 0) ||
                                    (event.charCode >= 48 && event.charCode <= 57)
                                }
                                value="5"
                                id="stop-physics-delay"
                                name="stop-physics-delay"
                                className="small-width"
                            ></input>
                            <br />
                            <br />
    
                            <h3>Import Space</h3>
                            <label htmlFor="import-space-area">Space JSON</label>
                            <br />
                            <textarea
                                id="import-space-area"
                                name="import-space-area"
                                className="bottom-space"
                            ></textarea>
                            <br />
                            <label htmlFor="import-space-name-text">
                                Space Name
                            </label>
                            <br />
                            <input
                                type="text"
                                id="import-space-name-text"
                                name="import-space-name-text"
                                className="bottom-space"
                            ></input>
                            <br />
                            <button
                                id="import-space-btn"
                                name="import-space-btn"
                                className="bottom-space"
                            >
                                Import
                            </button>
    
                            <br />
                            <br />
                        </div>
                    </section>
                </div>
            );
        }
    }