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

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

Update repo after prototype presentation

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