Index: imaps-frontend/src/scripts/util/Factory.js
===================================================================
--- imaps-frontend/src/scripts/util/Factory.js	(revision d56544918790c0c3bc50f2f0a98c99f3d2988ccf)
+++ imaps-frontend/src/scripts/util/Factory.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
@@ -6,24 +6,35 @@
 import RenderedEntrance from "../rendered_shapes/RenderedEntrance";
 import RenderedWall from "../rendered_shapes/RenderedWall.js";
+import Stairs from "../shapes/Stairs.js";
+import RenderedStairs from "../rendered_shapes/RenderedStairs.js";
+import {updateShapeCount, getShapeCount} from "./ShapeCount.js";
 export default class Factory {
+  // BUG KO KE NAPRES REFRESH SA RESETVAT COUNTS!
 
-  static infoPinCount = 0;
-  static wallCount = 0;
-  static entranceCount = 0;
-  static roomCount = 0;
+  static createShape(shapeType,attrs) {
+    console.log(attrs,"attrs in factory")
+    console.log("position in factory: " + attrs.position.x)
 
-  static createShape(shapeType, position, blockSize, layer, rotation,scaleX = 1, scaleY = 1, increment = false) {
+    let scaleX = (attrs.scaleX ? parseFloat(attrs.scaleX) : 1);
+    let scaleY =  (attrs.scaleY ? parseFloat(attrs.scaleY) : 1);
+
+    attrs.scaleX = scaleX;
+    attrs.scaleY = scaleY;
+
     switch (shapeType) {
       case "Entrance":
-        if(increment) this.entranceCount++;
-        return new Entrance(position, blockSize, layer, rotation,true,this.entranceCount,scaleX,scaleY);
+        if(attrs.increment) updateShapeCount("entrance")
+        return new Entrance(attrs,getShapeCount("entrance"));
       case "Room":
-        if(increment) this.roomCount++;
-        return new Room(position, blockSize, layer, rotation,true, this.roomCount,scaleX,scaleY);
-      case "Wall":
-        return new Wall(position, blockSize, layer, rotation,true,scaleX,scaleY);
-      case "InfoPin":
-        if(increment) this.infoPinCount++;
-        return new InfoPin(position, blockSize, layer, false,this.infoPinCount);
+        if(attrs.increment) updateShapeCount("room");
+        return new Room(attrs,getShapeCount("room"));
+       case "Wall":
+         return new Wall(attrs);
+       case "InfoPin":
+        if(attrs.increment) updateShapeCount("infoPin")
+          return new InfoPin(attrs,getShapeCount("infoPin"));
+      case "Stairs":
+        if(attrs.increment) updateShapeCount("stairs")
+        return new Stairs(attrs,getShapeCount("stairs"))
       default:
         throw new Error("Invalid shape type: " + shapeType);
@@ -41,4 +52,6 @@
       case "Wall":
         return new RenderedWall(attrs,scaleX,scaleY);
+      case "Stairs":
+        return new RenderedStairs(attrs,scaleX,scaleY)
       default:
         throw new Error("Invalid shape type." + shapeType);
Index: imaps-frontend/src/scripts/util/ShapeCount.js
===================================================================
--- imaps-frontend/src/scripts/util/ShapeCount.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
+++ imaps-frontend/src/scripts/util/ShapeCount.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
@@ -0,0 +1,18 @@
+const shapeCount = {
+    infoPin: 0,
+    wall: 0,
+    entrance: 0,
+    room: 0,
+    stairs: 0
+};
+
+const updateShapeCount = (key) => {
+    shapeCount[key]++;
+}
+const getShapeCount = (key) => {
+    return shapeCount[key];
+}
+
+export {updateShapeCount, getShapeCount};
+
+// todo za da sa incrementvit i posle restart na browwser, trebit vo baza nova tabela za broenje
Index: imaps-frontend/src/scripts/util/ShapeQuery.js
===================================================================
--- imaps-frontend/src/scripts/util/ShapeQuery.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
+++ imaps-frontend/src/scripts/util/ShapeQuery.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
@@ -0,0 +1,19 @@
+import MapNode from "../base/MapNode.js";
+import ShapeRegistry from "./ShapeRegistry.js";
+
+export default class ShapeQuery{
+    static findAllByType(...types) {
+        return ShapeRegistry.getShapes().filter(shape => types.includes(shape.className));
+    }
+
+    static findNodeByName(shapes, name){
+        return shapes.filter(shape => shape instanceof MapNode && shape.info.name === name)[0];
+    }
+    static findAllByTypeAndFloor(floor,...types){
+        return ShapeRegistry.getShapes().filter(shape => types.includes(shape.className) && shape.floorNum === floor);
+    }
+
+    static getAllNodes(floor = null){
+        return ShapeRegistry.getShapes(floor).filter(shape => shape instanceof MapNode);
+    }
+}
Index: imaps-frontend/src/scripts/util/ShapeRegistry.js
===================================================================
--- imaps-frontend/src/scripts/util/ShapeRegistry.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
+++ imaps-frontend/src/scripts/util/ShapeRegistry.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
@@ -0,0 +1,165 @@
+import MapNode from "../base/MapNode.js";
+import ShapeQuery from "./ShapeQuery.js";
+// TODO abstraktna klasa ova da stanit i za da napram za view registry, za da mozis search na floors globalno
+
+class ShapeRegistry {
+    constructor() {
+        if (!ShapeRegistry.instance) {
+            this.store = {
+                floors: {} // key brojka 0: shappes arr
+            };
+            ShapeRegistry.instance = this;
+        }
+
+        return ShapeRegistry.instance;
+    }
+
+    /**
+     * @description Get all the shapes from a floor if floorNumber is specified.
+     * @description If floorNumber is not specified, returns all the shapes from every floor.
+     * @param floorNumber
+     * @returns {[{}]} [shapes]
+     */
+    getShapes(floorNumber = null) {
+        if (!this.store.floors[floorNumber]) {
+            this.store.floors[floorNumber] = [];
+        }
+
+        if (floorNumber != null) {
+            return this.store.floors[floorNumber];
+        } else {
+            return Object.values(this.store.floors).flat();
+        }
+
+
+    }
+
+    /**
+     * Adds a shape to a floor that corresponds the shape's internally stored floor number.
+     * @param shape
+     */
+    add(shape) {
+        console.log("floor: " + shape.floorNum)
+        if (!this.store.floors[shape.floorNum]) {
+            this.store.floors[shape.floorNum] = [];
+        }
+        this.store.floors[shape.floorNum].push(shape);
+    }
+
+    /**
+     * Deletes a shape from the floor corresponds the shape's internally stored floor number
+     * @param shape
+     */
+    delete(shape) {
+        const floorShapes = this.store.floors[shape.floorNum];
+        if (floorShapes) {
+            const index = floorShapes.indexOf(shape);
+            if (index !== -1) {
+                floorShapes.splice(index, 1);
+            }
+        }
+    }
+
+    /**
+     * @param floorNumber Clears all shapes from the specified floor
+     */
+    clear(floorNumber = null) {
+
+        if(floorNumber != null){
+            const floor = this.store.floors[floorNumber];
+            if (Array.isArray(floor)) {
+                floor.forEach(shape => shape.destroy());
+                this.store.floors[floorNumber] = [];
+            }
+        } else {
+           Object.keys(this.store.floors).forEach(floorNumber => {
+               const floor = this.store.floors[floorNumber];
+               if (Array.isArray(floor)) {
+                   floor.forEach(shape => shape.destroy());
+                   this.store.floors[floorNumber] = [];
+               }
+           })
+        }
+
+
+
+    }
+
+    // ova mozit optimizacija da ne pret na site spratoj update celo vreme, samo ko ke povrzvis stairs da pret na site
+    updateConnections() {
+        Object.values(this.store.floors).forEach(floorShapes => {
+            floorShapes.forEach(shape => {
+                if (shape.className === "InfoPin" || shape.className === "Entrance" || shape.className === "Stairs") {
+                    shape.info.selectedPins.forEach((connectedShapeName) => {
+                        const connectedShape = floorShapes.find(s => s.info.name === connectedShapeName);
+                        if (
+                            connectedShape &&
+                            (connectedShape.className === "InfoPin" || connectedShape.className === "Entrance" || connectedShape.className === "Stairs")
+                        ) {
+                            if (!connectedShape.info.selectedPins.includes(shape.info.name)) {
+                                connectedShape.info.selectedPins.push(shape.info.name);
+                            }
+                        }
+                    });
+                }
+            });
+        });
+    }
+
+    drawConnection(node1Name, node2Name) {
+        const node1 = Object.values(this.store.floors).flat()
+            .find(shape => shape instanceof MapNode && shape.info.name === node1Name);
+        const node2 = Object.values(this.store.floors).flat()
+            .find(shape => shape instanceof MapNode && shape.info.name === node2Name);
+
+        console.log("NODE1: " + JSON.stringify(node1))
+        console.log("NODE2: " + JSON.stringify(node2));
+
+        if (node1 && node2) {
+            node1.connect(node2);
+            console.log("N1",node1.info.selectedPins,"N2",node2.info.selectedPins)
+        } else {
+            console.error("Cant find node1 or node 2.","Node1: " + node1,"Node2: " + node2);
+        }
+    }
+
+
+    removeConnection(from, to) {
+        let shapes = Object.values(this.store.floors).flat();
+
+        let node1 = ShapeQuery.findNodeByName(shapes, from);
+        let node2 = ShapeQuery.findNodeByName(shapes, to);
+
+        if(node1.floorNum === node2.floorNum)
+            node1.removeConnectionLine(node2);
+
+        //node1.removeConnection(node2);
+
+        Object.values(this.store.floors).flat().filter(s => s.info.name === from || s.info.name === to)
+            .forEach(s => {
+                if(s.info.name  === from){
+                    s.info.selectedPins = s.info.selectedPins.filter(pin =>  pin !== to);
+                } else {
+                    s.info.selectedPins = s.info.selectedPins.filter(pin =>  pin !== from);
+                }
+
+            });
+        console.log("Remove");
+    }
+
+    saveDetails() {
+        Object.values(this.store.floors).flat().forEach(shape => {
+            shape.saveShapeDetails();
+            console.log(shape.info);
+        });
+    }
+}
+
+/**
+ * A singleton instance that holds all the shapes for all the floors in the map builder.
+ * Every operation of adding, removing or updating drawn shapes should reference this instance.
+ */
+const instance = new ShapeRegistry();
+Object.freeze(instance);
+
+export default instance;
Index: imaps-frontend/src/scripts/util/addEventHandling.js
===================================================================
--- imaps-frontend/src/scripts/util/addEventHandling.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
+++ imaps-frontend/src/scripts/util/addEventHandling.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
@@ -0,0 +1,13 @@
+export const addEventHandling = (shape, map, action) => {
+    shape.on(action, () => {
+        const eventName = shape.eventName;
+        if (eventName) {
+            const data = {
+                room: shape,
+                map: map,
+            };
+            const event = new CustomEvent(eventName, { detail: data });
+            window.dispatchEvent(event);
+        }
+    });
+}
Index: imaps-frontend/src/scripts/util/parseMapData.js
===================================================================
--- imaps-frontend/src/scripts/util/parseMapData.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
+++ imaps-frontend/src/scripts/util/parseMapData.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
@@ -0,0 +1,28 @@
+import Factory from "./Factory.js";
+import shapeRegistry from "./ShapeRegistry.js";
+
+export default function parseMapData(data, predicate, rendered) {
+    if (data == null || data === "") return [];
+    const dsrData = JSON.parse(data);
+
+    console.log("VLEZE use")
+
+    let result = [];
+
+    dsrData.filter(predicate)
+        .forEach(s => {
+            console.log(s.attrs, "Sattrs pls")
+            s.attrs.position = {x: s.attrs.x, y: s.attrs.y}
+            const shape = rendered ? Factory.createRenderedShape(s.className, s.attrs) : Factory.createShape(s.className, s.attrs);
+
+            if (!rendered) {
+                shape.loadInfo(s.attrs);
+                shapeRegistry.add(shape)
+            }
+
+            result.push(shape)
+
+        })
+
+    return result;
+}
Index: imaps-frontend/src/scripts/util/queryShapes.js
===================================================================
--- imaps-frontend/src/scripts/util/queryShapes.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
+++ imaps-frontend/src/scripts/util/queryShapes.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
@@ -0,0 +1,8 @@
+export const queryShapeInfo = (map,type) => {
+    return map.shapes.filter(shape => shape.className === type).map(shape => shape.info);
+}
+
+export const queryRenderedShapes = (map,type) => {
+
+}
+
Index: imaps-frontend/src/scripts/util/triggerMapSave.js
===================================================================
--- imaps-frontend/src/scripts/util/triggerMapSave.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
+++ imaps-frontend/src/scripts/util/triggerMapSave.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
@@ -0,0 +1,21 @@
+let canSave = true;
+const mapSaveEvent = new CustomEvent("mapsave",{});
+const setCanSave = (value) => {
+    canSave = value;
+}
+/**
+ * Triggers a save event for the floor you are currently on with a debounce time of 3s;
+ * @example triggerMapSave() - will save the floor once, and after 3s again.
+ */
+const triggerMapSave = () => {
+    if(!canSave) return;
+
+    window.dispatchEvent(mapSaveEvent)
+    setCanSave(false);
+    setTimeout(() => {
+        setCanSave(true);
+        window.dispatchEvent(mapSaveEvent)
+    },2000)
+}
+
+export default triggerMapSave;
Index: imaps-frontend/src/scripts/util/triggerNavigate.js
===================================================================
--- imaps-frontend/src/scripts/util/triggerNavigate.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
+++ imaps-frontend/src/scripts/util/triggerNavigate.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
@@ -0,0 +1,14 @@
+const triggerNavigate = (nodes,idx,changeFloorTo,shape) => {
+    const event = new CustomEvent("navigate",{
+        detail: {
+            nodes: nodes,
+            offset: idx,
+            changeFloorTo: changeFloorTo,
+            shape:shape
+        }
+    })
+
+    window.dispatchEvent(event);
+}
+
+export default triggerNavigate;
Index: imaps-frontend/src/scripts/util/verifyToken.js
===================================================================
--- imaps-frontend/src/scripts/util/verifyToken.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
+++ imaps-frontend/src/scripts/util/verifyToken.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
@@ -0,0 +1,23 @@
+import HttpService from "../net/HttpService.js";
+import config from "../net/netconfig.js";
+
+export const verifyToken = async (setIsAuthenticated,setUsername,setLoading) => {
+    const token = localStorage.getItem("token");
+    const httpService = new HttpService();
+
+    try {
+        const response = await httpService.get(`${config.auth.verify}?token=${token}`);
+        if (response.username) {
+            setIsAuthenticated(true);
+            setUsername(response.username)
+            console.log("/verify resp: ",response.username);
+        } else {
+            setIsAuthenticated(false);
+            setLoading(false);
+        }
+    } catch (error) {
+        setIsAuthenticated(false);
+    } finally {
+        setLoading(false);
+    }
+}
Index: imaps-frontend/src/scripts/util/zoomStage.js
===================================================================
--- imaps-frontend/src/scripts/util/zoomStage.js	(revision d56544918790c0c3bc50f2f0a98c99f3d2988ccf)
+++ imaps-frontend/src/scripts/util/zoomStage.js	(revision 0c6b92add4cfaedcb91bcd37b45849f142f7d152)
@@ -1,4 +1,4 @@
-export const zoomStage = (e,stage) => {
-    if (!e.evt.shiftKey) return;
+export const zoomStage = (e,stage,shift=false) => {
+    if (shift && !e.evt.shiftKey) return;
 
     e.evt.preventDefault();
