import Factory from "../util/Factory.js"; import Konva from "konva"; import HttpService from "../net/HttpService.js"; import {zoomStage} from "../util/zoomStage.js"; import {addEventHandling} from "../util/addEventHandling.js"; import MapNode from "../base/MapNode.js"; import {json} from "react-router-dom"; import log from "eslint-plugin-react/lib/util/log.js"; import ShapeRegistry from "../util/ShapeRegistry.js"; import shapeRegistry from "../util/ShapeRegistry.js"; import triggerMapSave from "../util/triggerMapSave.js"; export class MapBuilder { constructor(containerId,floorNum,mapName) { this.container = document.getElementById(containerId); this.stage = new Konva.Stage({ container: containerId, width: this.container.clientWidth, height: this.container.clientHeight, }); // TODO AKO DRAGNIT NEKOJ OD POCETOK NA STAGE POZICIIVE KE SA ZEZNAT // TODO jwt vo cookie // TODO placed shape i mouseMoveHandler da ne callback ( da ne vrakjat funkcija) // TODO text na top layer sekogas this._floorNum = floorNum; this.mapName = mapName; this.gridLayer = new Konva.Layer(); this.mainLayer = new Konva.Layer(); this.dragLayer = new Konva.Layer(); this.infoPinLayer = new Konva.Layer(); this.prioLayer = new Konva.Layer(); this.textLayer = new Konva.Layer(); this.gridLayer.listening(false); this.othStairs = []; this.blockSize = 10; this.efficientDrawingMode = false; this.roomTypes = []; this.gridLine = new Konva.Line({ points: [], stroke: "grey", strokeWidth: 1, opacity: 0.3, }); this.gridLine.cache(); this.mainTransformer = new Konva.Transformer({ centeredScaling: false, rotationSnaps: [0, 90, 180, 270], anchorSize: 5, padding: 2, anchorFill: "#f6031f", borderStroke: "black", anchorStroke: "black", cornerRadius: 20, anchorCornerRadius: 10, anchorDragBoundFunc: this.transformerSnapFunc(), }); this.selectionRectangle = new Konva.Rect({ fill: "rgba(56,194,245,0.5)", visible: false, listening: false, zIndex: 100, }); this.x1 = 0; this.y1 = 0; this.x2 = 0; this.y2 = 0; this.selecting = false; this.initialize(); } initialize() { this.drawGrid(); this.mainLayer.add(this.mainTransformer); this.mainLayer.add(this.selectionRectangle); this.stage.add(this.gridLayer); this.stage.add(this.dragLayer); this.stage.add(this.mainLayer); this.stage.add(this.infoPinLayer); this.stage.add(this.textLayer); this.setupEventListeners(); } setupEventListeners() { document.getElementById("shapeOptions").addEventListener("click", this.selectShape.bind(this)); window.addEventListener("keydown", this.handleExitSelection.bind(this)); window.addEventListener("keydown", this.handleDelete.bind(this)); window.addEventListener("keydown", this.rotateShapesBy90Deg.bind(this)); window.addEventListener("keydown", this.toggleEfficientDrawingMode.bind(this)); window.addEventListener("resize", this.handleResize.bind(this)); this.boundEscapeEventListener = this.handleExitSelection.bind(this); this.boundDeleteEventListener = this.handleDelete.bind(this); this.boundRotateShapeEventListener = this.rotateShapesBy90Deg.bind(this) this.boundEfficientDrawingModeEventListener = this.toggleEfficientDrawingMode.bind(this); //this.attachKeyPressEventListeners(); this.stage.on("mousedown touchstart", this.handleMouseDown.bind(this)); this.stage.on("mousemove touchmove", this.handleMouseMove.bind(this)); this.stage.on("mouseup touchend", this.handleMouseUp.bind(this)); this.stage.on("click tap", this.handleStageClick.bind(this)); this.stage.on("contextmenu", this.placeInfoPin.bind(this)); this.stage.on("dragmove", this.dragStage.bind(this)); this.stage.on("wheel", this.zoom.bind(this)); } detachKeyPressEventListeners() { window.removeEventListener("keydown", this.boundEscapeEventListener); window.removeEventListener("keydown", this.boundDeleteEventListener); window.removeEventListener("keydown", this.boundRotateShapeEventListener); window.removeEventListener("keydown", this.boundEfficientDrawingModeEventListener); } attachKeyPressEventListeners() { window.addEventListener("keydown", this.boundEscapeEventListener); window.addEventListener("keydown", this.boundDeleteEventListener); window.addEventListener("keydown", this.boundRotateShapeEventListener); window.addEventListener("keydown", this.boundEfficientDrawingModeEventListener); } dragStage(e) { if (!e.evt.shiftKey) return; this.drawGrid(); } transformerSnapFunc() { return (oldPos, newPos) => { const snapDistance = 8; if (this.mainTransformer.getActiveAnchor() === "rotater") { return newPos; } const distance = Math.sqrt(Math.pow(newPos.x - oldPos.x, 2) + Math.pow(newPos.y - oldPos.y, 2)); if (distance > snapDistance) { return newPos; } const nextX = Math.round(newPos.x / this.blockSize) * this.blockSize; const diffX = Math.abs(newPos.x - nextX); const nextY = Math.round(newPos.y / this.blockSize) * this.blockSize; const diffY = Math.abs(newPos.y - nextY); const snapToX = diffX < snapDistance; const snapToY = diffY < snapDistance; if (snapToX && !snapToY) { return { x: nextX, y: oldPos.y, }; } else if (!snapToX && snapToY) { return { x: oldPos.x, y: nextY, }; } else if (snapToX && snapToY) { return { x: nextX, y: nextY, }; } return newPos; }; } handleResize() { this.stage.width(this.container.offsetWidth); this.stage.height(this.container.offsetHeight); this.drawGrid(); } zoom(e) { zoomStage(e, this.stage, true); this.drawGrid(); } get floorNum(){ return this._floorNum; } set floorNum(val){ this._floorNum = val; } drawGrid() { this.gridLayer.destroyChildren(); let width = this.stage.width(); let height = this.stage.height(); //presmetka od globalen koordinaten sistem vo lokalen na canvasot let transform = this.stage.getAbsoluteTransform().copy().invert(); let topLeft = transform.point({ x: 0, y: 0, }); let bottomRight = transform.point({ x: width, y: height, }); let startX = Math.floor(topLeft.x / this.blockSize) * this.blockSize; let startY = Math.floor(topLeft.y / this.blockSize) * this.blockSize; let endX = Math.ceil(bottomRight.x / this.blockSize) * this.blockSize; let endY = Math.ceil(bottomRight.y / this.blockSize) * this.blockSize; for (let x = startX; x <= endX; x += this.blockSize) { let line = this.gridLine.clone({ points: [x + 0.5, topLeft.y - this.blockSize, x + 0.5, bottomRight.y + this.blockSize], }); line.transformsEnabled("position"); line.perfectDrawEnabled(false); line.shadowForStrokeEnabled(false); this.gridLayer.add(line); } for (let y = startY; y <= endY; y += this.blockSize) { let line = this.gridLine.clone({ points: [topLeft.x - this.blockSize, y + 0.5, bottomRight.x + this.blockSize, y + 0.5], }); line.perfectDrawEnabled(false); line.shadowForStrokeEnabled(false); line.transformsEnabled("position"); this.gridLayer.add(line); } this.mainLayer.moveToTop(); this.infoPinLayer.moveToTop(); this.gridLayer.batchDraw(); } placeInfoPin(e) { e.evt.preventDefault(); let mousePos = this.stage.getRelativePointerPosition(); const attrs = { type: "InfoPin", position: mousePos, blockSize: this.blockSize, layer: this.mainLayer, rotation: 0, scaleX: 1, scaleY: 1, increment: true, floorNum: this.floorNum }; let infoPin = Factory.createShape("InfoPin", attrs); addEventHandling(infoPin, this, "dblclick"); //this.shapes.push(infoPin); ShapeRegistry.add(infoPin) this.mainLayer.add(infoPin); infoPin.displayName(this.textLayer); triggerMapSave() console.log(infoPin.name()); } toggleEfficientDrawingMode(e) { if (e.key === "e" || e.key === "E") { this.efficientDrawingMode = !this.efficientDrawingMode; console.log("EFFICIENT DRAWING MODE is: ", this.efficientDrawingMode); if (!this.efficientDrawingMode) { this.stopDrawing(); } } } placeShape() { const mousePos = this.stage.getRelativePointerPosition(); const attrs = { position: mousePos, width: this.blockSize, height: this.blockSize, layer: this.mainLayer, rotation: this.hoverObj.rotation(), scaleX: 1, scaleY: 1, increment: true, snap: true, fromLoad: false, blockSize: this.blockSize, floorNum: this.floorNum }; const placedObj = Factory.createShape(this.hoverObj.type, attrs); if (!placedObj) return; console.info("ATTRS FNUM",attrs.floorNum) this.mainLayer.add(placedObj); //this.shapes.push(placedObj); console.log("VO PLACED SHAEPS WALL ZITI SE: " + placedObj.className); ShapeRegistry.add(placedObj); addEventHandling(placedObj, this, "dblclick"); this.mainLayer.draw(); // site ovie func da se vo edna funkcija vo shape. placedObj.displayName(this.textLayer); placedObj.snapToGrid(); triggerMapSave(); if (!this.efficientDrawingMode) { this.stopDrawing(); } } stopDrawing() { this.mainTransformer.nodes([]); if (this.hoverObj != null) this.hoverObj.remove(); this.dragLayer.removeChildren(); this.stage.off("mousemove", this.boundMouseMoveHandler); this.stage.off("click", this.boundPlaceShapeHandler); } mouseMoveHandler() { const mousePos = this.stage.getRelativePointerPosition(); this.hoverObj.position({x: mousePos.x, y: mousePos.y}); this.hoverObj.visible(true); } startDrawing(shapeType) { const attrs = { position: {x: 0, y: 0}, width: this.blockSize, height: this.blockSize, layer: this.mainLayer, rotation: 0, scaleX: 1, scaleY: 1, increment: false, snap: true, fromLoad: false, blockSize: this.blockSize }; this.hoverObj = Factory.createShape(shapeType, attrs); console.log("HOVBER OBK:", this.hoverObj) this.hoverObj.visible(false); this.dragLayer.add(this.hoverObj); this.dragLayer.moveToTop(); this.boundMouseMoveHandler = this.mouseMoveHandler.bind(this); this.boundPlaceShapeHandler = this.placeShape.bind(this); this.stage.on("mousemove", this.boundMouseMoveHandler); this.stage.on("click", this.boundPlaceShapeHandler); } selectShape(e) { if (e.target.tagName === "LI") { const shapeType = e.target.getAttribute("data-info"); this.startDrawing(shapeType); this.mainTransformer.nodes([]); } } rotateShapesBy90Deg(e) { if (e.key === "r" || e.key === "R") { if (this.hoverObj) { this.hoverObj.rotate(90); } this.mainTransformer.nodes().forEach((node) => { node.rotate(90); }); } } handleDelete(e) { if (e.key === "Delete") { this.mainTransformer.nodes().forEach((node) => { node.remove(); node.destroy(); ShapeRegistry.delete(node); triggerMapSave(); }); this.mainTransformer.nodes([]); this.mainLayer.batchDraw(); } } handleExitSelection(e) { if (e.key === "Escape") { this.mainTransformer.nodes([]); this.stopDrawing(); } } handleMouseDown(e) { this.stage.draggable(e.evt.shiftKey); if (e.target !== this.stage) { return; } e.evt.preventDefault(); this.x1 = this.stage.getRelativePointerPosition().x; this.y1 = this.stage.getRelativePointerPosition().y; this.x2 = this.stage.getRelativePointerPosition().x; this.y2 = this.stage.getRelativePointerPosition().y; this.selectionRectangle.width(0); this.selectionRectangle.height(0); this.selecting = true; } handleMouseMove(e) { if (!this.selecting) { return; } e.evt.preventDefault(); this.x2 = this.stage.getRelativePointerPosition().x; this.y2 = this.stage.getRelativePointerPosition().y; this.selectionRectangle.setAttrs({ visible: true, x: Math.min(this.x1, this.x2), y: Math.min(this.y1, this.y2), width: Math.abs(this.x2 - this.x1), height: Math.abs(this.y2 - this.y1), }); } handleMouseUp(e) { this.selecting = false; this.stage.draggable(false); if (!this.selectionRectangle.visible()) { return; } e.evt.preventDefault(); this.selectionRectangle.visible(false); const shapes = this.stage.find(".mapObj"); const box = this.selectionRectangle.getClientRect(); const selected = shapes.filter((shape) => Konva.Util.haveIntersection(box, shape.getClientRect())); this.mainTransformer.nodes(selected); console.log(this.mainTransformer.nodes()); } saveShapeDetails() { // this.shapes.forEach(shape => { // shape.saveShapeDetails(); // console.log(shape.info); // }); ShapeRegistry.saveDetails(); console.log("thisflornum",this.floorNum) return { shapes: ShapeRegistry.getShapes(this.floorNum), roomTypes: JSON.stringify(this.roomTypes), mapName: this.mapName, floorNum: this.floorNum } } getPayload(){ this.saveShapeDetails(); return { shapes: ShapeRegistry.getShapes(this.floorNum), roomTypes: JSON.stringify(this.roomTypes), mapName: this.mapName, floorNum: this.floorNum } } handleStageClick(e) { if (this.selectionRectangle.visible()) { return; } if (e.target === this.stage) { this.mainTransformer.nodes([]); return; } if (!e.target.hasName("mapObj")) { return; } const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey; const isSelected = this.mainTransformer.nodes().indexOf(e.target) >= 0; if (!metaPressed && !isSelected) { this.mainTransformer.nodes([e.target]); console.log("Sel 1"); } else if (metaPressed && isSelected) { const nodes = this.mainTransformer.nodes().slice(); nodes.splice(nodes.indexOf(e.target), 1); this.mainTransformer.nodes(nodes); } else if (metaPressed && !isSelected) { const nodes = this.mainTransformer.nodes().concat([e.target]); this.mainTransformer.nodes(nodes); } } addRoomType(type) { this.roomTypes.push(type); } removeRoomType(targetType) { this.roomTypes = this.roomTypes.filter(type => type !== targetType); } getRoomTypes() { return this.roomTypes; } getRooms() { return this.getShapeInfoByType("Room"); } getPins() { return this.getShapeInfoByType("InfoPin"); } getEntrances() { return this.getShapeInfoByType("Entrance"); } getShapeInfoByType(type) { return ShapeRegistry.getShapes(this.floorNum).filter((shape) => shape.className === type).map((shape) => shape.info); } drawConnection(node1Name, node2Name) { ShapeRegistry.drawConnection(node1Name,node2Name); } getNodeByName(name) { return ShapeRegistry.getShapes(this.floorNum).filter(shape => shape instanceof MapNode && shape.info.name === name)[0]; } removeConnection(from, to) { ShapeRegistry.removeConnection(from,to); } updateRoomNames() { this.textLayer.removeChildren(); ShapeRegistry.getShapes(this.floorNum).forEach((shape) => { shape.displayName(this.textLayer); }); } isMainEntranceSelected() { console.log(this.getEntrances().forEach((en) => console.log(en.isMainEntrance, "asdsad"))); let hasMainEntrance = false; this.getEntrances().forEach((entrance) => { if (entrance.isMainEntrance === true) hasMainEntrance = true; }); return hasMainEntrance; } clearMap() { this.mainLayer.removeChildren(); this.hoverObj = null; } // ova klasa i map display da nasledbat od glavna klasa loadNewFloor(floor) { this._floorNum = floor?.num; let data = floor?.mapData; if (data == null || data === "") return; this.deserializeMap(data); shapeRegistry.getShapes(this.floorNum).forEach((shape) => { this.mainLayer.add(shape); }); } //nov deserializeMap(data) { console.log("DESERIALIZING: ", data); ShapeRegistry.clear(this.floorNum); if (data != null) { const dsrData = JSON.parse(data); //load shapes dsrData.forEach((shape) => { const attrs = { position: {x: shape.attrs.x, y: shape.attrs.y}, width: shape.attrs.width, height: shape.attrs.height, layer: this.mainLayer, blockSize: this.blockSize, rotation: shape.attrs.rotation, scaleX: shape.attrs.scaleX, scaleY: shape.attrs.scaleY, increment: false, snap: true, fromLoad: true, floorNum: this.floorNum }; const loadedShape = Factory.createShape(shape.className, attrs); loadedShape.loadInfo(shape.attrs); ShapeRegistry.add(loadedShape); // na destroy trebit events da sa trgnat addEventHandling(loadedShape, this, "dblclick"); }); let nodes = ShapeRegistry.getShapes(this.floorNum).filter((shape) => shape.className === "InfoPin" || shape.className === "Entrance" || shape.className === "Stairs"); nodes.forEach((pin) => { let connectedPins = pin.info.selectedPins; if (connectedPins) { connectedPins.forEach((slPin) => { console.log("CONN node1: " + pin + "conn node2: " + slPin) this.drawConnection(pin.info.name, slPin); }); } }); } this.mainTransformer.nodes([]); this.mainLayer.add(this.mainTransformer); this.mainLayer.add(this.selectionRectangle); ShapeRegistry.getShapes(this.floorNum).forEach((shape) => shape.displayName(this.textLayer)); } }