source: imaps-frontend/src/scripts/main/MapBuilder.js@ 0c6b92a

main
Last change on this file since 0c6b92a was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

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