Skip to content
Snippets Groups Projects
state.js 8.48 KiB
Newer Older
  • Learn to ignore specific revisions
  • import Tool from "./tools/tool";
    import UndoTool from "./tools/undotool";
    import RedoTool from "./tools/redotool";
    import SelectTool from "./tools/selecttool";
    import DeleteTool from "./tools/deletetool";
    import AddNodeTool from "./tools/addnodetool";
    import ConnectTool from "./tools/connecttool";
    
    import SettingsTool from "./tools/settingstool";
    
    import SaveTool from "./tools/savetool";
    
    import Toolbar from "./toolbar";
    
    import * as Graph from "./graph";
    
    import { DRAG_THRESHOLD_2D } from "../../config";
    
    import { Editor } from "./components/editor";
    
    
    export const TOOLS = {
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        undo: new UndoTool("undo"),
        redo: new RedoTool("redo"),
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        select: new SelectTool("select"),
        delete: new DeleteTool("delete"),
    
        addnode: new AddNodeTool("addnode"),
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        connect: new ConnectTool("connect"),
    
        settings: new SettingsTool("settings"),
    
        save: new SaveTool("save"),
    
    Maximilian Giller's avatar
    Maximilian Giller committed
    };
    
    
    export const CONTEXT = {
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        node: "node",
        link: "link",
        mixed: "mixed",
    
    Maximilian Giller's avatar
    Maximilian Giller committed
    };
    
    
    export class State extends Tool {
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        constructor() {
            super("State");
    
    
            this.display = new Toolbar(TOOLS);
    
    Maximilian Giller's avatar
    Maximilian Giller committed
    
            this.tool = undefined;
            this.setTool(TOOLS.select);
    
            // Shared variables
            this.selectedItem = undefined;
            this.selectedItems = new Set();
    
            this.itemsContext = CONTEXT.nothing;
    
    Maximilian Giller's avatar
    Maximilian Giller committed
            this.labelVisible = true;
    
    Maximilian Giller's avatar
    Maximilian Giller committed
    
            this.keyStates = {};
        }
    
        setTool(tool) {
    
            if (this.tool === tool) {
    
    Maximilian Giller's avatar
    Maximilian Giller committed
                return;
            }
    
    
    Maximilian Giller's avatar
    Maximilian Giller committed
            if (this.tool !== undefined) {
    
                this.tool.deactivateTool(tool);
    
    Maximilian Giller's avatar
    Maximilian Giller committed
            this.previousTool = this.tool;
            this.tool = tool;
            this.display.setSelectedTool(tool);
    
    Maximilian Giller's avatar
    Maximilian Giller committed
            if (this.tool !== undefined) {
    
                this.tool.activateTool();
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        }
    
        setSelectedItem(item) {
            this.selectedItem = item;
        }
    
        addSelectedItem(item) {
            this.selectedItems.add(item);
        }
    
    
        addSelectedItems(items) {
            Object.values(items).forEach((item) => {
                this.selectedItems.add(item);
            });
        }
    
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        removeSelectedItem(item) {
            this.selectedItems.delete(item);
        }
    
        clearSelectedItems() {
            this.selectedItems.clear();
    
            this.itemsContext = CONTEXT.nothing;
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        }
    
        onNodeClick(node) {
            this.tool.onNodeClick(node);
        }
    
    
        onNodeDragEnd(node, translate) {
            // Handle as click event, if drag distance under a certain threshold
    
            var distanceDragged = Math.sqrt(
                Math.pow(translate.x, 2) + Math.pow(translate.y, 2)
            );
    
            if (distanceDragged < DRAG_THRESHOLD_2D) {
                this.onNodeClick(node);
                return;
            } else {
                this.tool.onNodeDragEnd(node, translate);
            }
        }
    
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        onLinkClick(link) {
            this.tool.onLinkClick(link);
        }
    
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        onKeyDown(key) {
            var id = this.getKeyId(key);
            var previous = this.keyStates[id];
    
            this.keyStates[id] = true;
    
            if (previous !== true) {
                this.tool.onKeyDown(key);
            }
        }
    
        onKeyUp(key) {
            var id = this.getKeyId(key);
            var previous = this.keyStates[id];
    
            this.keyStates[id] = false;
    
            if (previous !== false) {
                this.tool.onKeyUp(key);
            }
        }
    
        getKeyId(key) {
            return key.keyCode;
        }
    
    
        nodeCanvasObject(node, ctx, globalScale) {
    
    Maximilian Giller's avatar
    Maximilian Giller committed
            var toolValue = this.tool.nodeCanvasObject(node, ctx);
    
            if (toolValue !== undefined) {
                return toolValue;
            }
    
            // TODO: Clean up function
    
            // add ring just for highlighted nodes
            if (this.selectedItem === node || this.selectedItems.has(node)) {
                ctx.beginPath();
    
                ctx.arc(node.x, node.y, 4 * 0.6, 0, 2 * Math.PI, false);
                ctx.fillStyle = this.selectedItem === node ? "red" : "white";
    
    Maximilian Giller's avatar
    Maximilian Giller committed
                ctx.fill();
            }
    
            // Draw image
    
            if (node[Graph.NODE_IMAGE] !== undefined) {
    
                var path = node[Graph.NODE_IMAGE];
    
                if (!path.includes("/")) {
                    path = Graph.IMAGE_SRC + path;
                }
    
    Maximilian Giller's avatar
    Maximilian Giller committed
                var img = new Image();
                img.src = path;
    
                ctx.drawImage(
                    img,
    
                    node.x - Graph.IMAGE_SIZE / 2,
                    node.y - Graph.IMAGE_SIZE / 2,
                    Graph.IMAGE_SIZE,
                    Graph.IMAGE_SIZE
    
    Maximilian Giller's avatar
    Maximilian Giller committed
            if (this.labelVisible) {
                const label = node[Graph.NODE_LABEL];
    
                const fontSize = 11 / globalScale;
    
    Maximilian Giller's avatar
    Maximilian Giller committed
                ctx.font = `${fontSize}px Sans-Serif`;
                const textWidth = ctx.measureText(label).width;
    
                const bckgDimensions = [textWidth, fontSize].map(
                    (n) => n + fontSize * 0.2
                ); // some padding
    
    
    Maximilian Giller's avatar
    Maximilian Giller committed
                const nodeHeightOffset = Graph.IMAGE_SIZE / 3 + bckgDimensions[1];
    
                ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
                ctx.fillRect(
                    node.x - bckgDimensions[0] / 2,
                    node.y - bckgDimensions[1] / 2 + nodeHeightOffset,
                    ...bckgDimensions
                );
    
                ctx.textAlign = "center";
                ctx.textBaseline = "middle";
                ctx.fillStyle = "white";
    
    Maximilian Giller's avatar
    Maximilian Giller committed
                ctx.fillText(label, node.x, node.y + nodeHeightOffset);
            }
    
    Maximilian Giller's avatar
    Maximilian Giller committed
            // TODO: Render label as always visible
        }
    
        nodePointerAreaPaint(node, color, ctx) {
            var toolValue = this.tool.nodePointerAreaPaint(node, color, ctx);
    
            if (toolValue !== undefined) {
                return toolValue;
            }
    
            ctx.fillStyle = color;
            ctx.fillRect(
    
                node.x - Graph.IMAGE_SIZE / 2,
                node.y - Graph.IMAGE_SIZE / 2,
                Graph.IMAGE_SIZE,
                Graph.IMAGE_SIZE
    
    Maximilian Giller's avatar
    Maximilian Giller committed
            ); // draw square as pointer trap
        }
    
        nodeCanvasObjectMode(node) {
            var toolValue = this.tool.nodeCanvasObjectMode(node);
    
            if (toolValue !== undefined) {
                return toolValue;
            }
    
            return "after";
        }
    
    
        linkCanvasObjectMode(link) {
            var toolValue = this.tool.linkCanvasObjectMode(link);
    
            if (toolValue !== undefined) {
                return toolValue;
            }
    
    
        }
    
        linkCanvasObject(link, ctx, globalScale) {
            var toolValue = this.tool.linkCanvasObject(link, ctx);
    
            if (toolValue !== undefined) {
                return toolValue;
            }
    
    
            // Links already initialized?
            if (link.source.x === undefined) {
                return undefined;
            }
    
            // Draw gradient link
    
            var gradient = ctx.createLinearGradient(
                link.source.x,
                link.source.y,
                link.target.x,
                link.target.y
            );
    
            // Have reversed colors
            // Color at source node referencing the target node and vice versa
    
            gradient.addColorStop("0", Editor.globalGraph.getNodeColor(link.target));
            gradient.addColorStop("1", Editor.globalGraph.getNodeColor(link.source));
    
    
            ctx.beginPath();
            ctx.moveTo(link.source.x, link.source.y);
            ctx.lineTo(link.target.x, link.target.y);
            ctx.strokeStyle = gradient;
            ctx.stroke();
    
            // Only render strokes on last link
            // var lastLink = graph.data[Graph.GRAPH_LINKS][graph.data[Graph.GRAPH_LINKS].length - 1];
            // if (link === lastLink) {
            //     ctx.stroke();
            // }
    
            return undefined;
        }
    
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        linkWidth(link) {
            var toolValue = this.tool.linkWidth(link);
    
            if (toolValue !== undefined) {
                return toolValue;
            }
    
    
            return this.isLinkHighlighted(link) ? 2 : 1;
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        }
    
        linkDirectionalParticles() {
            var toolValue = this.tool.linkDirectionalParticles();
    
            if (toolValue !== undefined) {
                return toolValue;
            }
    
            return 4;
        }
    
        linkDirectionalParticleWidth(link) {
            var toolValue = this.tool.linkDirectionalParticleWidth(link);
    
            if (toolValue !== undefined) {
                return toolValue;
            }
    
    
            return this.isLinkHighlighted(link) ? Graph.LINK_PARTICLE_COUNT : 0;
    
        onBackgroundClick(event, positions) {
            this.tool.onBackgroundClick(event, positions);
        }
    
    
    Maximilian Giller's avatar
    Maximilian Giller committed
        redraw() {
            this.display.setSelectedTool(this.tool);
        }
    
        isLinkHighlighted(link) {
            return (
                this.selectedItem === link ||
    
                Editor.globalGraph.isLinkOnNode(link, this.selectedItem)
    
    Maximilian Giller's avatar
    Maximilian Giller committed
            );
        }
    
    Maximilian Giller's avatar
    Maximilian Giller committed
    
        setLabelVisibility(visibility) {
            this.labelVisible = visibility;
        }
    
    Maximilian Giller's avatar
    Maximilian Giller committed
    }