source: imaps-frontend/src/scripts/main/MapBuilder.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 3 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 19.4 KB
Line 
1import Factory from "../util/Factory.js";
2import Konva from "konva";
3import HttpService from "../net/HttpService.js";
4import {zoomStage} from "../util/zoomStage.js";
5import {addEventHandling} from "../util/addEventHandling.js";
6import MapNode from "../base/MapNode.js";
7import {json} from "react-router-dom";
8import log from "eslint-plugin-react/lib/util/log.js";
9import ShapeRegistry from "../util/ShapeRegistry.js";
10import shapeRegistry from "../util/ShapeRegistry.js";
11import triggerMapSave from "../util/triggerMapSave.js";
12
13export class MapBuilder {
14 constructor(containerId,floorNum,mapName) {
15 this.container = document.getElementById(containerId);
16 this.stage = new Konva.Stage({
17 container: containerId,
18 width: this.container.clientWidth,
19 height: this.container.clientHeight,
20 });
21
22 // TODO AKO DRAGNIT NEKOJ OD POCETOK NA STAGE POZICIIVE KE SA ZEZNAT
23 // TODO placed shape i mouseMoveHandler da ne callback ( da ne vrakjat funkcija)
24 // TODO text na top layer sekogas
25
26 this._floorNum = floorNum;
27 this.mapName = mapName;
28
29 this.gridLayer = new Konva.Layer();
30 this.mainLayer = new Konva.Layer();
31 this.dragLayer = new Konva.Layer();
32 this.infoPinLayer = new Konva.Layer();
33 this.prioLayer = new Konva.Layer();
34 this.textLayer = new Konva.Layer();
35 this.gridLayer.listening(false);
36
37 this.isDrawEventBound = false;
38
39
40 this.othStairs = [];
41
42 this.blockSize = 10;
43 this.efficientDrawingMode = false;
44 this.roomTypes = [];
45
46 this.gridLine = new Konva.Line({
47 points: [],
48 stroke: "grey",
49 strokeWidth: 1,
50 opacity: 0.3,
51 });
52
53 this.gridLine.cache();
54
55 this.mainTransformer = new Konva.Transformer({
56 centeredScaling: false,
57 rotationSnaps: [0, 90, 180, 270],
58 anchorSize: 5,
59 padding: 2,
60 anchorFill: "#f6031f",
61 borderStroke: "black",
62 anchorStroke: "black",
63 cornerRadius: 20,
64 anchorCornerRadius: 10,
65 anchorDragBoundFunc: this.transformerSnapFunc(),
66 });
67
68 this.selectionRectangle = new Konva.Rect({
69 fill: "rgba(56,194,245,0.5)",
70 visible: false,
71 listening: false,
72 zIndex: 100,
73 });
74
75 this.x1 = 0;
76 this.y1 = 0;
77 this.x2 = 0;
78 this.y2 = 0;
79
80 this.selecting = false;
81
82 this.initialize();
83 }
84
85 initialize() {
86 this.drawGrid();
87 this.mainLayer.add(this.mainTransformer);
88 this.mainLayer.add(this.selectionRectangle);
89 this.stage.add(this.gridLayer);
90 this.stage.add(this.dragLayer);
91 this.stage.add(this.mainLayer);
92 this.stage.add(this.infoPinLayer);
93 this.stage.add(this.textLayer);
94 this.setupEventListeners();
95 }
96
97 setupEventListeners() {
98 document.getElementById("shapeOptions").addEventListener("click", this.selectShape.bind(this));
99 // window.addEventListener("keydown", this.handleExitSelection.bind(this));
100 // window.addEventListener("keydown", this.handleDelete.bind(this));
101 // window.addEventListener("keydown", this.rotateShapesBy90Deg.bind(this));
102 // window.addEventListener("keydown", this.toggleEfficientDrawingMode.bind(this));
103 window.addEventListener("resize", this.handleResize.bind(this));
104
105 this.boundEscapeEventListener = this.handleExitSelection.bind(this);
106 this.boundDeleteEventListener = this.handleDelete.bind(this);
107 this.boundRotateShapeEventListener = this.rotateShapesBy90Deg.bind(this)
108 this.boundEfficientDrawingModeEventListener = this.toggleEfficientDrawingMode.bind(this);
109
110 this.attachKeyPressEventListeners();
111
112 this.stage.on("mousedown touchstart", this.handleMouseDown.bind(this));
113 this.stage.on("mousemove touchmove", this.handleMouseMove.bind(this));
114 this.stage.on("mouseup touchend", this.handleMouseUp.bind(this));
115 this.stage.on("click tap", this.handleStageClick.bind(this));
116 this.stage.on("contextmenu", this.placeInfoPin.bind(this));
117 this.stage.on("dragmove", this.dragStage.bind(this));
118 this.stage.on("wheel", this.zoom.bind(this));
119 }
120
121 detachKeyPressEventListeners() {
122 console.log("DETACH")
123 window.removeEventListener("keydown", this.boundEscapeEventListener);
124 window.removeEventListener("keydown", this.boundDeleteEventListener);
125 window.removeEventListener("keydown", this.boundRotateShapeEventListener);
126 window.removeEventListener("keydown", this.boundEfficientDrawingModeEventListener);
127 }
128
129 attachKeyPressEventListeners() {
130 console.log("ATTACH")
131 window.addEventListener("keydown", this.boundEscapeEventListener);
132 window.addEventListener("keydown", this.boundDeleteEventListener);
133 window.addEventListener("keydown", this.boundRotateShapeEventListener);
134 window.addEventListener("keydown", this.boundEfficientDrawingModeEventListener);
135 }
136
137 dragStage(e) {
138 if (!e.evt.shiftKey) return;
139 this.drawGrid();
140 }
141
142 transformerSnapFunc() {
143 return (oldPos, newPos) => {
144 const snapDistance = 8;
145
146 if (this.mainTransformer.getActiveAnchor() === "rotater") {
147 return newPos;
148 }
149
150 const distance = Math.sqrt(Math.pow(newPos.x - oldPos.x, 2) + Math.pow(newPos.y - oldPos.y, 2));
151
152 if (distance > snapDistance) {
153 return newPos;
154 }
155
156 const nextX = Math.round(newPos.x / this.blockSize) * this.blockSize;
157 const diffX = Math.abs(newPos.x - nextX);
158
159 const nextY = Math.round(newPos.y / this.blockSize) * this.blockSize;
160 const diffY = Math.abs(newPos.y - nextY);
161
162 const snapToX = diffX < snapDistance;
163 const snapToY = diffY < snapDistance;
164
165 if (snapToX && !snapToY) {
166 return {
167 x: nextX,
168 y: oldPos.y,
169 };
170 } else if (!snapToX && snapToY) {
171 return {
172 x: oldPos.x,
173 y: nextY,
174 };
175 } else if (snapToX && snapToY) {
176 return {
177 x: nextX,
178 y: nextY,
179 };
180 }
181
182 return newPos;
183 };
184 }
185
186 handleResize() {
187 this.stage.width(this.container.offsetWidth);
188 this.stage.height(this.container.offsetHeight);
189 this.drawGrid();
190 }
191
192 zoom(e) {
193 zoomStage(e, this.stage, true);
194 this.drawGrid();
195 }
196
197 get floorNum(){
198 return this._floorNum;
199 }
200
201 set floorNum(val){
202 this._floorNum = val;
203 }
204
205 drawGrid() {
206 this.gridLayer.destroyChildren();
207
208 let width = this.stage.width();
209 let height = this.stage.height();
210
211 //presmetka od globalen koordinaten sistem vo lokalen na canvasot
212 let transform = this.stage.getAbsoluteTransform().copy().invert();
213 let topLeft = transform.point({
214 x: 0,
215 y: 0,
216 });
217
218 let bottomRight = transform.point({
219 x: width,
220 y: height,
221 });
222
223 let startX = Math.floor(topLeft.x / this.blockSize) * this.blockSize;
224 let startY = Math.floor(topLeft.y / this.blockSize) * this.blockSize;
225
226 let endX = Math.ceil(bottomRight.x / this.blockSize) * this.blockSize;
227 let endY = Math.ceil(bottomRight.y / this.blockSize) * this.blockSize;
228
229 for (let x = startX; x <= endX; x += this.blockSize) {
230 let line = this.gridLine.clone({
231 points: [x + 0.5, topLeft.y - this.blockSize, x + 0.5, bottomRight.y + this.blockSize],
232 });
233
234 line.transformsEnabled("position");
235 line.perfectDrawEnabled(false);
236 line.shadowForStrokeEnabled(false);
237
238 this.gridLayer.add(line);
239 }
240
241 for (let y = startY; y <= endY; y += this.blockSize) {
242 let line = this.gridLine.clone({
243 points: [topLeft.x - this.blockSize, y + 0.5, bottomRight.x + this.blockSize, y + 0.5],
244 });
245
246 line.perfectDrawEnabled(false);
247 line.shadowForStrokeEnabled(false);
248 line.transformsEnabled("position");
249 this.gridLayer.add(line);
250 }
251
252 this.mainLayer.moveToTop();
253 this.infoPinLayer.moveToTop();
254
255 this.gridLayer.batchDraw();
256 }
257
258 placeInfoPin(e) {
259 e.evt.preventDefault();
260 let mousePos = this.stage.getRelativePointerPosition();
261 const attrs = {
262 type: "InfoPin",
263 position: mousePos,
264 blockSize: this.blockSize,
265 layer: this.mainLayer,
266 rotation: 0,
267 scaleX: 1,
268 scaleY: 1,
269 increment: true,
270 floorNum: this.floorNum
271 };
272 let infoPin = Factory.createShape("InfoPin", attrs);
273 addEventHandling(infoPin, this, "dblclick");
274 ShapeRegistry.add(infoPin)
275 this.mainLayer.add(infoPin);
276 infoPin.displayName(this.textLayer);
277 triggerMapSave()
278
279 console.log(infoPin.name());
280 }
281
282 toggleEfficientDrawingMode(e) {
283 if (e.key === "e" || e.key === "E") {
284 this.efficientDrawingMode = !this.efficientDrawingMode;
285 console.log("EFFICIENT DRAWING MODE is: ", this.efficientDrawingMode);
286
287 if (!this.efficientDrawingMode) {
288 this.stopDrawing();
289 }
290 }
291 }
292
293 placeShape() {
294 const mousePos = this.stage.getRelativePointerPosition();
295 const attrs = {
296 position: mousePos,
297 width: this.blockSize,
298 height: this.blockSize,
299 layer: this.mainLayer,
300 rotation: this.hoverObj.rotation(),
301 scaleX: 1,
302 scaleY: 1,
303 increment: true,
304 snap: true,
305 fromLoad: false,
306 blockSize: this.blockSize,
307 floorNum: this.floorNum
308 };
309
310 const placedObj = Factory.createShape(this.hoverObj.type, attrs);
311
312 if (!placedObj) return;
313
314 console.info("ATTRS FNUM",attrs.floorNum)
315
316 this.mainLayer.add(placedObj);
317 ShapeRegistry.add(placedObj);
318 addEventHandling(placedObj, this, "dblclick");
319 this.mainLayer.draw();
320 // site ovie func da se vo edna funkcija vo shape.
321
322 placedObj.displayName(this.textLayer);
323 placedObj.snapToGrid();
324 placedObj.onPlace();
325
326 triggerMapSave();
327
328 if (!this.efficientDrawingMode) {
329 this.stopDrawing();
330 }
331 this.mainTransformer.nodes([])
332 }
333
334 stopDrawing() {
335 if(this.isDrawEventBound){
336 this.mainTransformer.nodes([]);
337 if (this.hoverObj != null) this.hoverObj.remove();
338 this.dragLayer.removeChildren();
339 this.stage.off("mousemove", this.boundMouseMoveHandler);
340 this.stage.off("click", this.boundPlaceShapeHandler);
341 this.isDrawEventBound = false;
342 }
343
344 }
345
346 mouseMoveHandler() {
347 const mousePos = this.stage.getRelativePointerPosition();
348 this.hoverObj.position({x: mousePos.x, y: mousePos.y});
349 this.hoverObj.visible(true);
350 }
351
352 startDrawing(shapeType) {
353 const attrs = {
354 position: {x: 0, y: 0},
355 width: this.blockSize,
356 height: this.blockSize,
357 layer: this.mainLayer,
358 rotation: 0,
359 scaleX: 1,
360 scaleY: 1,
361 increment: false,
362 snap: true,
363 fromLoad: false,
364 blockSize: this.blockSize
365 };
366 this.hoverObj = Factory.createShape(shapeType, attrs);
367
368 console.log("HOVBER OBK:", this.hoverObj)
369
370 this.hoverObj.visible(false);
371 this.dragLayer.add(this.hoverObj);
372 this.dragLayer.moveToTop();
373
374
375 this.boundMouseMoveHandler = this.mouseMoveHandler.bind(this);
376 this.boundPlaceShapeHandler = this.placeShape.bind(this);
377 this.isDrawEventBound = true;
378
379 this.stage.on("mousemove", this.boundMouseMoveHandler);
380 this.stage.on("click", this.boundPlaceShapeHandler);
381 }
382
383 selectShape(e) {
384 if (e.target.tagName === "LI") {
385 this.stopDrawing()
386 const shapeType = e.target.getAttribute("data-info");
387 this.startDrawing(shapeType);
388 this.mainTransformer.nodes([])
389 }
390 }
391
392 rotateShapesBy90Deg(e) {
393 if (e.key === "r" || e.key === "R") {
394 if (this.hoverObj) {
395 this.hoverObj.rotate(90);
396 }
397 this.mainTransformer.nodes().forEach((node) => {
398 node.rotate(90);
399 });
400 }
401 }
402
403 handleDelete(e) {
404 if (e.key === "Delete") {
405 this.mainTransformer.nodes().forEach((node) => {
406 node.remove();
407 node.destroy();
408 ShapeRegistry.delete(node);
409 triggerMapSave();
410 });
411 this.mainTransformer.nodes([]);
412 this.mainLayer.batchDraw();
413 }
414 }
415
416 handleExitSelection(e) {
417 if (e.key === "Escape") {
418 this.mainTransformer.nodes([]);
419 this.stopDrawing();
420 }
421 }
422
423 handleMouseDown(e) {
424 this.stage.draggable(e.evt.shiftKey);
425
426 if (e.target !== this.stage) {
427 return;
428 }
429
430 e.evt.preventDefault();
431 this.x1 = this.stage.getRelativePointerPosition().x;
432 this.y1 = this.stage.getRelativePointerPosition().y;
433 this.x2 = this.stage.getRelativePointerPosition().x;
434 this.y2 = this.stage.getRelativePointerPosition().y;
435
436 this.selectionRectangle.width(0);
437 this.selectionRectangle.height(0);
438 this.selecting = true;
439 }
440
441 handleMouseMove(e) {
442 if (!this.selecting) {
443 return;
444 }
445 e.evt.preventDefault();
446 this.x2 = this.stage.getRelativePointerPosition().x;
447 this.y2 = this.stage.getRelativePointerPosition().y;
448
449 this.selectionRectangle.setAttrs({
450 visible: true,
451 x: Math.min(this.x1, this.x2),
452 y: Math.min(this.y1, this.y2),
453 width: Math.abs(this.x2 - this.x1),
454 height: Math.abs(this.y2 - this.y1),
455 });
456 }
457
458 handleMouseUp(e) {
459 this.selecting = false;
460 this.stage.draggable(false);
461
462 if (!this.selectionRectangle.visible()) {
463 return;
464 }
465
466 e.evt.preventDefault();
467 this.selectionRectangle.visible(false);
468 const shapes = this.stage.find(".mapObj");
469 const box = this.selectionRectangle.getClientRect();
470 const selected = shapes.filter((shape) => Konva.Util.haveIntersection(box, shape.getClientRect()));
471 this.mainTransformer.nodes(selected);
472 console.log(this.mainTransformer.nodes());
473 }
474
475 saveShapeDetails() {
476 ShapeRegistry.saveDetails();
477 console.log("thisflornum",this.floorNum)
478 return {
479 shapes: ShapeRegistry.getShapes(this.floorNum),
480 roomTypes: JSON.stringify(this.roomTypes),
481 mapName: this.mapName,
482 floorNum: this.floorNum
483 }
484 }
485
486
487
488 handleStageClick(e) {
489 if (this.selectionRectangle.visible()) {
490 console.log("STAGECLICK1")
491 return;
492 }
493
494 if (e.target === this.stage) {
495 console.log("STAGECLICK2")
496 this.mainTransformer.nodes([]);
497 return;
498 }
499
500 if (!e.target.hasName("mapObj")) {
501 console.log("STAGECLICK3")
502 return;
503 }
504
505 const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey;
506 const isSelected = this.mainTransformer.nodes().indexOf(e.target) >= 0;
507
508
509 if (!metaPressed && !isSelected) {
510 this.mainTransformer.nodes([e.target]);
511 console.log("Sel 1");
512 } else if (metaPressed && isSelected) {
513 const nodes = this.mainTransformer.nodes().slice();
514 nodes.splice(nodes.indexOf(e.target), 1);
515 this.mainTransformer.nodes(nodes);
516 } else if (metaPressed && !isSelected) {
517 const nodes = this.mainTransformer.nodes().concat([e.target]);
518 this.mainTransformer.nodes(nodes);
519 }
520 }
521
522 addRoomType(type) {
523 this.roomTypes.push(type);
524 }
525
526 removeRoomType(targetType) {
527 this.roomTypes = this.roomTypes.filter(type => type !== targetType);
528 }
529
530
531 drawConnection(node1Name, node2Name) {
532
533 ShapeRegistry.drawConnection(node1Name,node2Name);
534 }
535
536 removeConnection(from, to) {
537 ShapeRegistry.removeConnection(from,to);
538 }
539
540 updateRoomNames() {
541 this.textLayer.removeChildren();
542 ShapeRegistry.getShapes(this.floorNum).forEach((shape) => {
543 shape.displayName(this.textLayer);
544 });
545
546 }
547
548
549 clearMap() {
550 this.mainLayer.removeChildren();
551 this.hoverObj = null;
552 }
553
554
555 // ova klasa i map display da nasledbat od glavna klasa
556
557 loadNewFloor(floor) {
558
559 this._floorNum = floor?.num;
560 let data = floor?.mapData;
561
562 if (data == null || data === "") return;
563
564 this.deserializeMap(data);
565 shapeRegistry.getShapes(this.floorNum).forEach((shape) => {
566 this.mainLayer.add(shape);
567 });
568
569
570 }
571
572 //nov
573 deserializeMap(data) {
574 console.log("DESERIALIZING: ", data);
575 ShapeRegistry.clear(this.floorNum);
576
577 if (data != null) {
578 const dsrData = JSON.parse(data);
579 //load shapes
580 dsrData.forEach((shape) => {
581 const attrs = {
582 position: {x: shape.attrs.x, y: shape.attrs.y},
583 width: shape.attrs.width,
584 height: shape.attrs.height,
585 layer: this.mainLayer,
586 blockSize: this.blockSize,
587 rotation: shape.attrs.rotation,
588 scaleX: shape.attrs.scaleX,
589 scaleY: shape.attrs.scaleY,
590 increment: false,
591 snap: true,
592 fromLoad: true,
593 floorNum: this.floorNum
594 };
595
596 const loadedShape = Factory.createShape(shape.className, attrs);
597 loadedShape.loadInfo(shape.attrs);
598 ShapeRegistry.add(loadedShape);
599 // na destroy trebit events da sa trgnat
600 addEventHandling(loadedShape, this, "dblclick");
601 });
602 let nodes = ShapeRegistry.getShapes(this.floorNum).filter((shape) => shape.className === "InfoPin" || shape.className === "Entrance" || shape.className === "Stairs");
603 nodes.forEach((pin) => {
604 let connectedPins = pin.info.selectedPins;
605 if (connectedPins) {
606 connectedPins.forEach((slPin) => {
607 console.log("CONN node1: " + pin + "conn node2: " + slPin)
608 this.drawConnection(pin.info.name, slPin);
609 });
610 }
611 });
612 }
613
614 this.mainTransformer.nodes([]);
615 this.mainLayer.add(this.mainTransformer);
616 this.mainLayer.add(this.selectionRectangle);
617
618 ShapeRegistry.getShapes(this.floorNum).forEach((shape) => {
619 shape.displayName(this.textLayer);
620 if(shape.className === "Entrance"){
621 shape.setHighlight();
622 }
623 });
624 }
625
626}
Note: See TracBrowser for help on using the repository browser.