diff --git a/editor/css/editor.css b/editor/css/editor.css index 91170d51b3f8d1660650baf6a097a82aeb1ab79f..4fe7246cec32694e3179f86c97f1eed531d71730 100644 --- a/editor/css/editor.css +++ b/editor/css/editor.css @@ -62,3 +62,12 @@ div#ks-editor img.preview-image { div#ks-editor #toolbar-settings, div#ks-editor #toolbar-save { float: right; } + +div#ks-editor #boxSelect { + position: absolute; + z-index: 300000; + border-style: dotted; + border-color: #3e74cc; + background-color: rgba(255, 255, 255, 0.5); + pointer-events: none; +} diff --git a/editor/editor.php b/editor/editor.php index 2fc3a38c523dd43cfe9aaf965f032e1929147fc4..b893ec251a9625da12134954d1b39fa702d48adc 100644 --- a/editor/editor.php +++ b/editor/editor.php @@ -1,7 +1,7 @@ <div id="ks-editor"> <!--The id "ks-editor" indicates, that the javascript associated with this should automatically be executed--> <h1>Interface</h1> - <div id="2d-graph"></div> + <div id="box-select"><div id="2d-graph"></div></div> <section id="toolbar"></section> <section id="tool-menu"> <div id="collect-menu" class="hidden"> diff --git a/editor/js/editor.js b/editor/js/editor.js index 2f82e1f524217b184ec642ffe076380ac786e410..e3f7a2aa379b2690d842820c2c112bdaf8b51571 100644 --- a/editor/js/editor.js +++ b/editor/js/editor.js @@ -8,7 +8,7 @@ import { setSpace, SPACE } from "../../config"; export var state = undefined; export var graph = undefined; -var graphObj; +export var renderer; window.onload = function () { // Only execute, if corresponding dom is present @@ -43,7 +43,7 @@ export function loadSpace(spaceId) { function extractPositions(event) { return { - graph: graphObj.screen2GraphCoords(event.layerX, event.layerY), + graph: renderer.screen2GraphCoords(event.layerX, event.layerY), window: { x: event.clientX, y: event.clientY }, }; } @@ -52,7 +52,7 @@ function load() { const graphContainer = document.getElementById("2d-graph"); const width = graphContainer.offsetWidth; - graphObj = ForceGraph()(graphContainer) + renderer = ForceGraph()(graphContainer) .height(600) .width(width) .graphData(graph.data) @@ -76,9 +76,9 @@ function load() { .onLinkClick((link) => state.onLinkClick(link)); graph.onChangeCallbacks.push((data) => { - graphObj.cooldownTicks(0); - graphObj.graphData(data); + renderer.cooldownTicks(0); + renderer.graphData(data); }); - graph.setRenderer(graphObj); + graph.setRenderer(renderer); } diff --git a/editor/js/state.js b/editor/js/state.js index 1672b394d06a8a6b6c04fdc8a8160658b3a95719..73edbcdb96f21e827c52e8c79eeb832e470ce210 100644 --- a/editor/js/state.js +++ b/editor/js/state.js @@ -2,6 +2,7 @@ import Tool from "./tools/tool"; import UndoTool from "./tools/undotool"; import RedoTool from "./tools/redotool"; import SelectTool from "./tools/selecttool"; +import CollectTool from "./tools/collecttool"; import DeleteTool from "./tools/deletetool"; import AddNodeTool from "./tools/addnodetool"; import ConnectTool from "./tools/connecttool"; @@ -15,6 +16,7 @@ export const TOOLS = { undo: new UndoTool("undo"), redo: new RedoTool("redo"), select: new SelectTool("select"), + collect: new CollectTool("collect"), delete: new DeleteTool("delete"), addnode: new AddNodeTool("addnode"), connect: new ConnectTool("connect"), @@ -26,7 +28,7 @@ export const CONTEXT = { node: "node", link: "link", mixed: "mixed", - nothing: "nothing" + nothing: "nothing", }; export class State extends Tool { @@ -73,6 +75,12 @@ export class State extends Tool { this.selectedItems.add(item); } + addSelectedItems(items) { + Object.values(items).forEach((item) => { + this.selectedItems.add(item); + }); + } + removeSelectedItem(item) { this.selectedItems.delete(item); } @@ -91,11 +99,11 @@ export class State extends Tool { } linkColor(link) { - return 'red'; + return "red"; } nodeColor(node) { - return 'black'; + return "black"; } onKeyDown(key) { @@ -144,7 +152,7 @@ export class State extends Tool { // Draw image if (node[Graph.NODE_IMAGE] !== undefined) { var path = node[Graph.NODE_IMAGE]; - + if (!path.includes("/")) { path = Graph.IMAGE_SRC + path; } @@ -163,18 +171,24 @@ export class State extends Tool { // Draw label if (this.labelVisible) { const label = node[Graph.NODE_LABEL]; - const fontSize = 11/globalScale; + const fontSize = 11 / globalScale; ctx.font = `${fontSize}px Sans-Serif`; const textWidth = ctx.measureText(label).width; - const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2); // some padding - + const bckgDimensions = [textWidth, fontSize].map( + (n) => n + fontSize * 0.2 + ); // some padding + 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'; + 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"; ctx.fillText(label, node.x, node.y + nodeHeightOffset); } @@ -230,7 +244,12 @@ export class State extends Tool { } // Draw gradient link - var gradient = ctx.createLinearGradient(link.source.x, link.source.y, link.target.x, link.target.y); + 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", graph.getNodeColor(link.target)); @@ -247,7 +266,7 @@ export class State extends Tool { // if (link === lastLink) { // ctx.stroke(); // } - + return undefined; } diff --git a/editor/js/tools/collecttool.js b/editor/js/tools/collecttool.js index ffd3f71db598a33f7613e741fb93dfd8f0947daf..9fde474ff691107f4b45b5c0cc959d9593f17551 100644 --- a/editor/js/tools/collecttool.js +++ b/editor/js/tools/collecttool.js @@ -1,11 +1,128 @@ import Tool from "./tool"; -import { state } from "../editor"; +import { graph, state, renderer } from "../editor"; import { CONTEXT } from "../state"; import { CollectMenu, COLLECTION_KEY } from "./menus/collectmenu"; +import * as Graph from "../graph"; export default class CollectTool extends Tool { constructor(key) { super("Collect", "collect", key, new CollectMenu()); + this.setupBoxSelect(); + } + + setupBoxSelect() { + document.addEventListener("load", () => { + // Source: https://github.com/vasturiano/force-graph/issues/151#issuecomment-735850938 + + // forceGraph element is the element provided to the Force Graph Library + document + .getElementById("ks-editor 2d-graph") + .addEventListener("pointerdown", (e) => { + if (e.shiftKey) { + e.preventDefault(); + this.boxSelect = document.createElement("div"); + this.boxSelect.id = "boxSelect"; + this.boxSelect.style.left = e.offsetX.toString() + "px"; + this.boxSelect.style.top = e.offsetY.toString() + "px"; + this.boxSelectStart = { + x: e.offsetX, + y: e.offsetY, + }; + // app element is the element just above the forceGraph element. + document + .getElementById("ks-editor box-select") + .appendChild(this.boxSelect); + } + }); + + document + .getElementById("ks-editor 2d-graph") + .addEventListener("pointermove", (e) => { + if (e.shiftKey && this.boxSelect) { + e.preventDefault(); + if (e.offsetX < this.boxSelectStart.x) { + this.boxSelect.style.left = + e.offsetX.toString() + "px"; + this.boxSelect.style.width = + (this.boxSelectStart.x - e.offsetX).toString() + + "px"; + } else { + this.boxSelect.style.left = + this.boxSelectStart.x.toString() + "px"; + this.boxSelect.style.width = + (e.offsetX - this.boxSelectStart.x).toString() + + "px"; + } + if (e.offsetY < this.boxSelectStart.y) { + this.boxSelect.style.top = + e.offsetY.toString() + "px"; + this.boxSelect.style.height = + (this.boxSelectStart.y - e.offsetY).toString() + + "px"; + } else { + this.boxSelect.style.top = + this.boxSelectStart.y.toString() + "px"; + this.boxSelect.style.height = + (e.offsetY - this.boxSelectStart.y).toString() + + "px"; + } + } else if (this.boxSelect) { + this.boxSelect.remove(); + } + }); + + document + .getElementById("ks-editor 2d-graph") + .addEventListener("pointerup", (e) => { + if (e.shiftKey && this.boxSelect) { + e.preventDefault(); + let left, bottom, top, right; + if (e.offsetX < this.boxSelectStart.x) { + left = e.offsetX; + right = this.boxSelectStart.x; + } else { + left = this.boxSelectStart.x; + right = e.offsetX; + } + if (e.offsetY < this.boxSelectStart.y) { + top = e.offsetY; + bottom = this.boxSelectStart.y; + } else { + top = this.boxSelectStart.y; + bottom = e.offsetY; + } + runBoxSelect(left, bottom, top, right); + this.boxSelect.remove(); + } else if (this.boxSelect) { + this.boxSelect.remove(); + } + }); + + const runBoxSelect = (left, bottom, top, right) => { + const tl = renderer.screen2GraphCoords(left, top); + const br = renderer.screen2GraphCoords(right, bottom); + const hitNodes = []; + graph.data[Graph.GRAPH_NODES].forEach((node) => { + if ( + tl.x < node.x && + node.x < br.x && + br.y > node.y && + node.y > tl.y + ) { + hitNodes.push(node); + } + }); + // run code to select your nodes here + // return selectGraphObjects(hitNodes); + console.log(hitNodes); + + if (state.itemsContext !== CONTEXT.node) { + state.clearSelectedItems(); + state.itemsContext = CONTEXT.node; + } + state.addSelectedItems(hitNodes); + }; + }); } onNodeClick(node) {