Changeset 79a0317 for imaps-frontend/src


Ignore:
Timestamp:
01/21/25 03:08:24 (3 days ago)
Author:
stefan toskovski <stefantoska84@…>
Branches:
main
Parents:
0c6b92a
Message:

F4 Finalna Verzija

Location:
imaps-frontend/src
Files:
25 added
58 edited

Legend:

Unmodified
Added
Removed
  • imaps-frontend/src/App.jsx

    r0c6b92a r79a0317  
    1919import {AppProvider} from "./components/AppContext/AppContext.jsx";
    2020import AdminPage from "./pages/AdminPage/AdminPage.jsx";
     21import {AuthCallback} from "./pages/AuthCallback.jsx";
     22import PrivacyPolicy from "./pages/PrivacyPolicy/PrivacyPolicy.jsx";
     23import TermsOfService from "./pages/TermsOfService/TermsOfService.jsx";
    2124
    2225
     
    3033
    3134                    <Route element={<ProtectedRoute/>}>
    32                         <Route path="/myMaps/:mapName/Draw" element={<Draw/>}/>
     35                        <Route path="/myMaps/Draw/:mapName" element={<Draw/>}/>
    3336                        <Route path="/myMaps" element={<MyMaps/>}/>
    34                         <Route path="/myMaps/:mapName/View" element={<MapView isPrivate={true}/>}/>
     37                        <Route path="/myMaps/View/:mapName" element={<MapView isPrivate={true}/>}/>
    3538                        <Route path="/Admin" element={<AdminPage/>}/>
    3639                    </Route>
    3740                    <Route path="/" element={<IMaps/>}/>
    38                     <Route path="/Maps/:mapName/View" element={<MapView isPrivate={false}/>}/>
     41                    <Route path="/Maps/View/:mapName" element={<MapView isPrivate={false}/>}/>
    3942                    <Route path="/Maps" element={<BrowseMaps/>}/>
     43                    <Route path="/auth-callback" element={<AuthCallback/>}/>
     44                    <Route path={"/privacy-policy"} element={<PrivacyPolicy/>}/>
     45                    <Route path={"/terms-of-service"} element={<TermsOfService/>}/>
    4046
    4147
  • imaps-frontend/src/TODO.txt

    r0c6b92a r79a0317  
    1 PRIORITY HIGH
    2 
    3 - Id fix na shapes vo Draw
    4 
    5 PRIORITY MEDIUM
    6 
    7 - Detach event na draw (keys: e, r)
    8 - Text na shape vo Draw da e top layer
    9 
    10 PRIORITY LOW
    11 
    12 - Admin Page
    13 
    14 
    15 - KO KE SMENIS IME VO MODAL,TREBIT VO GRAFOT DA SA UPDATE TOJ ENTRY // got
    16 - VO room da sa cuvat entrances, za ko ke mrdas room isto entrance da sa mrdnit
    17 
    18 
    19 bug: selectiras eden shape pa drug, pa celo vreme sa klavat posle shape
     1-IDs za shape posebno od iminja
     2- Refactor style na my maps i browse, da ne e tolku prazno od okolu
     3- Admin link da izlegvit za admin users
     4- Google maps url da sa pokazvit vo browse na mapite
  • imaps-frontend/src/components/DrawGuide/DrawGuide.jsx

    r0c6b92a r79a0317  
    1818          <div onClick={toggleModal} className={styles.overlay}></div>
    1919          <div className={styles.modalContent}>
    20             <h2>How to Use the Map Builder</h2>
    21             <p>Welcome to the Map Builder! Here you can create an indoor map, render it and enjoy!:</p>
     20            <h2 className={styles.title}>How to Use the Map Builder</h2>
     21            <p className={styles.paragraph}>Welcome to the Map Builder! Here you can create an indoor map, render it and enjoy!:</p>
    2222            <br></br>
    2323            <ul>
     
    3535            </ul>
    3636            <br></br>
    37             <p>After completing your map, click the render button and go to the View page to see your full featured map!</p>
     37            <p className={styles.paragraph}>After completing your map, click the render button and go to the View page to see your full featured map!</p>
    3838
    3939            <button className={styles.closeModal} onClick={toggleModal}>
  • imaps-frontend/src/components/DrawGuide/DrawGuide.module.css

    r0c6b92a r79a0317  
    1818  backdrop-filter: blur(5px); /* Optional blur effect */
    1919  z-index: 999;
     20
     21}
     22
     23.title{
     24  color: white;
     25}
     26
     27.paragraph{
     28  color: #ababab;
    2029}
    2130
     
    2534  left: 50%;
    2635  transform: translate(-50%, -50%);
    27   background-color: #2c2f33; /* Darker background for modal content */
     36  background-color: #2c2f33;
    2837  color: #ffffff; /* White text for contrast */
    2938  padding: 20px;
     
    3544}
    3645
    37 h2 {
    38   margin-bottom: 15px;
    39   color: #ffffff; /* White text for the heading */
    40 }
     46/*h2 {*/
     47/*  margin-bottom: 15px;*/
     48/*  color: #ffffff; !* White text for the heading *!*/
     49/*}*/
    4150
    4251.btnModal {
  • imaps-frontend/src/components/FilterBar/FilterBar.module.css

    r0c6b92a r79a0317  
    1 /* FilterBar.module.css */
     1/* FilterMaps.module.css */
    22* {
    33  font-family: "Poppins", sans-serif;
     
    1818  padding: 0px 0; /* Adds some space around the buttons */
    1919  width: 100%;
     20  overflow-y: hidden;
    2021}
    2122
    2223.buttonValue {
    23   border: 2px solid #6759ff;
     24  border: 2px solid rgb(37,111,123);
    2425  padding: 0.5em 1.5em;
    2526  border-radius: 3em;
    2627  background-color: transparent;
    27   color: #6759ff;
     28  color: rgb(37,111,123);
    2829  cursor: pointer;
    2930  transition: background-color 0.3s, color 0.3s;
     
    3334.buttonValue:hover,
    3435.buttonValue:focus {
    35   background-color: #6759ff;
     36  background-color: rgb(33, 98, 108);
    3637  color: #ffffff;
    3738}
    3839
    3940.active {
    40   background-color: #6759ff;
     41  background-color: rgb(37,111,123);
    4142  color: #ffffff;
    4243}
     
    4748
    4849.scrollableContainer::-webkit-scrollbar-thumb {
    49   background-color: #6759ff;
     50  background-color: rgb(37,111,123);
    5051  border-radius: 3px;
    5152}
  • imaps-frontend/src/components/LoadingContainer/LoadingContainer.jsx

    r0c6b92a r79a0317  
    11import React from "react";
     2import styles from "./LoadingContainer.module.css"
    23
    34export const LoadingContainer = () => {
     
    56        <div className="loading-container">
    67            <div className="spinner"></div>
    7             <p>Loading, please wait...</p>
    88        </div>
    99    );
  • imaps-frontend/src/components/Logo/Logo.jsx

    r0c6b92a r79a0317  
    11import React from "react";
    22import { useNavigate } from "react-router-dom";
    3 import logoImage from "../../assets/logo_icon.png";
     3import novo_logo from "../../assets/novo_logo_nobg_cropped.png";
    44import styles from "./Logo.module.css";
    55
     
    1616      onClick={handleClick}
    1717    >
    18       <img src={logoImage} alt="Logo" className={styles.logoImage} />
     18      <img src={novo_logo} alt="Logo" className={styles.logoImage} />
    1919    </div>
    2020  );
  • imaps-frontend/src/components/Logo/Logo.module.css

    r0c6b92a r79a0317  
    88
    99.logoImage {
    10   width: 50px; /* Adjust the size as needed */
     10  width: 80px;
    1111  height: auto;
    1212  transition: opacity 0.2s ease;
     13  border-radius: 20px;
    1314}
    1415
     
    3031}
    3132
    32 .logoImage {
    33   width: 50px; /* Adjust size as needed */
    34   height: auto;
    35   transition: opacity 0.2s ease;
    36 }
    37 
    3833.logoImage:hover {
    3934  opacity: 0.8; /* Hover effect */
  • imaps-frontend/src/components/MapInfoModal/MapInfoModal.jsx

    r0c6b92a r79a0317  
    1 import React, {useEffect, useState} from "react";
     1import React, { useEffect, useState } from "react";
    22import styles from "./MapInfoModal.module.css";
    3 import {json, useNavigate} from "react-router-dom";
    43import edit_icon from "../../assets/edit_icon_black.png";
    54import PublishForm from "../PublishForm/PublishForm.jsx";
    65import HttpService from "../../scripts/net/HttpService.js";
    76import config from "../../scripts/net/netconfig.js";
    8 import {useAppContext} from "../AppContext/AppContext.jsx";
    9 
    10 export default function MapInfoModal({isOpen, onClose, map, onDelete, onUpdate, onPublish, published=false}) {
     7import { useAppContext } from "../AppContext/AppContext.jsx";
     8import { useNavigate } from "react-router-dom";
     9
     10export default function MapInfoModal({
     11                                         isOpen,
     12                                         onClose,
     13                                         map,
     14                                         onDelete,
     15                                         onUpdate,
     16                                         onPublish,
     17                                         published = false,
     18                                     }) {
    1119    const [isEditPopupOpen, setEditPopupOpen] = useState(false);
    12     const [editedName, setEditedName] = useState(map?.mapName || "");
    13     const [editedGmapsUrl, setEditedGmapsUrl] = useState(map?.gmaps_url || "");
    14     const [publishFormOpen,setPublishFormOpen] = useState(false)
     20    const [editedName, setEditedName] = useState("");
     21    const [editedGmapsUrl, setEditedGmapsUrl] = useState("");
     22    const [editedType, setEditedType] = useState("");
     23    const [publishFormOpen, setPublishFormOpen] = useState(false);
    1524    const navigate = useNavigate();
    16     const[loadedFormData,setLoadedFormData] = useState(null)
    17 
     25    const [loadedFormData, setLoadedFormData] = useState(null);
     26    const { setLoading } = useAppContext();
     27    const { loading } = useAppContext();
     28    const { username } = useAppContext();
    1829
    1930    useEffect(() => {
    20         console.log("GMAPS: " + JSON.stringify(map))
    21     }, []);
    22 
    23     const {username} = useAppContext();
     31        if (map) {
     32            setEditedName(map.mapName || "");
     33            setEditedGmapsUrl(map.gmaps_url || "");
     34            setEditedType(map.mapType || "");
     35        }
     36    }, [map, isEditPopupOpen]);
    2437
    2538    if (!isOpen || !map) return null;
    2639
    2740    const handleView = () => {
    28         navigate(`/myMaps/${map.mapName}/View`)
     41        navigate(`/myMaps/View/${map.mapName}`);
    2942    };
    3043
    3144    const handleEdit = () => {
    32         navigate(`/myMaps/${map.mapName}/Draw`)
     45        navigate(`/myMaps/Draw/${map.mapName}`);
    3346    };
    3447
    3548    const handleDelete = () => {
    36         if (window.confirm(`Are you sure you want to delete the map "${map.mapName}"?`)) {
     49        if (
     50            window.confirm(`Are you sure you want to delete the map "${map.mapName}"?`)
     51        ) {
    3752            onDelete(map.mapName);
    3853            onClose();
     
    4055    };
    4156
    42 
    4357    const openEditPopup = () => {
    4458        setEditPopupOpen(true);
     
    5165    const handleEditSubmit = async () => {
    5266        const updatedMap = {
    53             ...map,
    54             mapName: editedName,
    55             gmaps_url: editedGmapsUrl,
     67            initialName: map.mapName,
     68            name: editedName,
     69            gmapsUrl: editedGmapsUrl,
     70            type: editedType,
    5671        };
    57 
    58         try {
    59             //await onUpdate(updatedMap);
    60             setEditPopupOpen(false);
    61         } catch (error) {
    62             console.error("Error updating map:", error);
    63         }
    64     };
    65 
    66     const openPublishModal = async () => {
    67         const httpService = new HttpService(true);
    68         const respForm = await httpService.get(`${config.my_maps.publish_get}?mapName=${map.mapName}`)
    69         setLoadedFormData(respForm);
    70         setPublishFormOpen(true)
    71     };
    72 
    73     const sendPublishRequest = async (formData) => {
    74         const httpService = new HttpService(true);
    75         formData.mapName = map.mapName;
    76         console.log("FORMDATA: "+JSON.stringify(formData))
    77         await httpService.post(`${config.my_maps.publish}?username=${username}`,formData);
    78         setPublishFormOpen(false)
    79         onPublish()
    80     }
     72        onUpdate(updatedMap);
     73        closeEditPopup()
     74    };
    8175
    8276    return (
    8377        <div className={styles.modalOverlay} onClick={onClose}>
    84             <div className={styles.modalContent} onClick={(e) => e.stopPropagation()}>
    85                 {publishFormOpen && (
    86                     <PublishForm mapName={map.mapName} formData={loadedFormData} onSubmit={sendPublishRequest} onCancel={() => setPublishFormOpen(false)}/>
    87                 )}
    88 
    89                 <img src={map.image_url} alt="Map Thumbnail" className={styles.mapImage}/>
    90                 <h2 className={styles.title}>
    91                     {map.mapName}
    92                     <img
    93                         src={edit_icon}
    94                         alt="Edit"
    95                         className={styles.editIcon}
    96                         onClick={openEditPopup}
    97                     />
    98                 </h2>
    99                 <p><strong>Status:</strong> {map.status}</p>
    100 
    101                 <p><strong>Created At:</strong> {new Date(map.created_at).toLocaleString()}</p>
    102                 <p><strong>Modified At:</strong> {new Date(map.modified_at).toLocaleString()}</p>
    103                 <p><strong>Published
    104                     At:</strong> {map.published_at ? new Date(map.published_at).toLocaleString() : "Not published yet"}
    105                 </p>
    106                 <p>
    107                     <strong>Google Maps URL:</strong>
    108                     <a href={map.gMapsUrl}  rel="noopener noreferrer">
    109                         Open in Google Maps
    110                     </a>
    111                 </p>
    112 
    113                 <div className={styles.buttonContainer}>
    114                     <button className={styles.viewButton} onClick={handleView}>
    115                         View
    116                     </button>
    117                     <button className={styles.editButton} onClick={handleEdit}>
    118                         Edit
    119                     </button>
    120                     <button className={styles.deleteButton} onClick={handleDelete}>
    121                         Delete
    122                     </button>
    123                     {!map.is_published && !published && (
    124                         <button className={styles.publishButton} onClick={openPublishModal}>
    125                             Publish
     78            <div
     79                className={styles.modalContent}
     80                onClick={(e) => e.stopPropagation()}
     81            >
     82                {!loading && (
     83                    <>
     84                        <img
     85                            src={map.image_url}
     86                            alt="Map Thumbnail"
     87                            className={styles.mapImage}
     88                        />
     89                        <h2 className={styles.title}>
     90                            {map.mapName}
     91                            <img
     92                                src={edit_icon}
     93                                alt="Edit"
     94                                className={styles.editIcon}
     95                                onClick={openEditPopup}
     96                            />
     97                        </h2>
     98                        <p>
     99                            <strong>Status:</strong> {map.status}
     100                        </p>
     101                        <p>
     102                            <strong>Created At:</strong>{" "}
     103                            {new Date(map.created_at).toLocaleString()}
     104                        </p>
     105                        <p>
     106                            <strong>Modified At:</strong>{" "}
     107                            {new Date(map.modified_at).toLocaleString()}
     108                        </p>
     109                        <p>
     110                            <strong>Published At:</strong>{" "}
     111                            {map.published_at
     112                                ? new Date(map.published_at).toLocaleString()
     113                                : "Not published yet"}
     114                        </p>
     115                        <p>
     116                            <strong>Google Maps URL:</strong>{" "}
     117                            <a href={`${map.gmaps_url}`} rel="noopener noreferrer">
     118                                Open in Google Maps
     119                            </a>
     120                        </p>
     121
     122                        <div className={styles.buttonContainer}>
     123                            <button className={styles.viewButton} onClick={handleView}>
     124                                View
     125                            </button>
     126                            <button className={styles.editButton} onClick={handleEdit}>
     127                                Edit
     128                            </button>
     129                            <button className={styles.deleteButton} onClick={handleDelete}>
     130                                Delete
     131                            </button>
     132                            {!map.is_published && !published && (
     133                                <button
     134                                    className={styles.publishButton}
     135                                    onClick={() => setPublishFormOpen(true)}
     136                                >
     137                                    Publish
     138                                </button>
     139                            )}
     140                        </div>
     141                        <button className={styles.closeButton} onClick={onClose}>
     142                            Close
    126143                        </button>
    127                     )}
    128                 </div>
    129                 <button className={styles.closeButton} onClick={onClose}>
    130                     Close
    131                 </button>
    132 
    133                 {isEditPopupOpen && (
    134                     <div className={styles.editPopupOverlay} onClick={closeEditPopup}>
    135                         <div
    136                             className={styles.editPopupContent}
    137                             onClick={(e) => e.stopPropagation()}
    138                         >
    139                             <h3 className={styles.title}>Edit Map Details</h3>
    140                             <div className={styles.editField}>
    141                                 <label>Map Name:</label>
    142                                 <input
    143                                     type="text"
    144                                     value={editedName}
    145                                     onChange={(e) => setEditedName(e.target.value)}
    146                                 />
     144
     145                        {isEditPopupOpen && (
     146                            <div
     147                                className={styles.editPopupOverlay}
     148                                onClick={closeEditPopup}
     149                            >
     150                                <div
     151                                    className={styles.editPopupContent}
     152                                    onClick={(e) => e.stopPropagation()}
     153                                >
     154                                    <h3 className={styles.title}>Edit Map Details</h3>
     155                                    <div className={styles.editField}>
     156                                        <label>Map Name:</label>
     157                                        <input
     158                                            type="text"
     159                                            value={editedName}
     160                                            onChange={(e) => setEditedName(e.target.value)}
     161                                        />
     162                                    </div>
     163                                    <div className={styles.editField}>
     164                                        <label>Google Maps URL:</label>
     165                                        <input
     166                                            type="text"
     167                                            value={editedGmapsUrl}
     168                                            onChange={(e) => setEditedGmapsUrl(e.target.value)}
     169                                        />
     170                                    </div>
     171                                    <div className={styles.editField}>
     172                                        <label>Map Type:</label>
     173                                        <input
     174                                            type="text"
     175                                            value={editedType}
     176                                            onChange={(e) => setEditedType(e.target.value)}
     177                                        />
     178                                    </div>
     179                                    <div className={styles.editPopupButtons}>
     180                                        <button
     181                                            className={styles.submitButton}
     182                                            onClick={handleEditSubmit}
     183                                        >
     184                                            Submit
     185                                        </button>
     186                                        <button
     187                                            className={styles.cancelButton}
     188                                            onClick={closeEditPopup}
     189                                        >
     190                                            Cancel
     191                                        </button>
     192                                    </div>
     193                                </div>
    147194                            </div>
    148                             <div className={styles.editField}>
    149                                 <label>Google Maps URL:</label>
    150                                 <input
    151                                     type="text"
    152                                     value={editedGmapsUrl}
    153                                     onChange={(e) => setEditedGmapsUrl(e.target.value)}
    154                                 />
    155                             </div>
    156                             <div className={styles.editPopupButtons}>
    157                                 <button className={styles.submitButton} onClick={handleEditSubmit}>
    158                                     Submit
    159                                 </button>
    160                                 <button className={styles.cancelButton} onClick={closeEditPopup}>
    161                                     Cancel
    162                                 </button>
    163                             </div>
    164                         </div>
    165                     </div>
     195                        )}
     196                    </>
    166197                )}
    167198            </div>
  • imaps-frontend/src/components/MapInfoModal/MapInfoModal.module.css

    r0c6b92a r79a0317  
    1414
    1515.modalContent {
     16    position: relative;
    1617    background-color: white;
    1718    padding: 20px;
     
    2223    text-align: center;
    2324    animation: fadeIn 0.3s ease-out;
     25
    2426}
    2527
  • imaps-frontend/src/components/Modals/CreateMapModal/CreateMapModal.jsx

    r0c6b92a r79a0317  
    1010        const mapDetails = {
    1111            name: mapName,
    12             type: mapType,
    1312        };
    1413
     
    3433                        />
    3534                    </label>
    36                     <label>
    37                         Map Type:
    38                         <input
    39                             type="text"
    40                             value={mapType}
    41                             onChange={(e) => setMapType(e.target.value)}
    42                             required
    43                         />
    44                     </label>
    4535                    <div className={styles.modalButtons}>
    4636                        <button type="submit" className={styles.modalSubmitButton}>
  • imaps-frontend/src/components/Modals/CreateMapModal/CreateMapModal.module.css

    r0c6b92a r79a0317  
    3636/* Modal Buttons Container */
    3737.modalButtons {
    38     margin-top: 1.5rem;
    3938    display: flex;
    4039    justify-content: space-between;
  • imaps-frontend/src/components/Modals/EntranceModal/EntranceModal.jsx

    r0c6b92a r79a0317  
    4242        setConnections(connections);
    4343        setIsOpen(true);
     44        event.detail.map.detachKeyPressEventListeners();
    4445        console.log(connections, "Loaded pins on modal open");
    4546    },"openEntranceModalEvent")
     
    5253            <ModalSelectRoom shapeInfo={shapeInfo} availableRooms={ShapeQuery.findAllByTypeAndFloor(shape?.floorNum,"Room")} updateModalData={updateModalData}/>
    5354            <ModalSelectConnections2
    54                 availableShapes={ShapeQuery.findAllByType("InfoPin","Entrance")} // najubo ke e entrance samo so room da mozit
     55                availableShapes={ShapeQuery.findAllByTypeAndFloor(map?.floorNum,"Entrance","InfoPin")} // najubo ke e entrance samo so room da mozit
    5556                addConnection={addConnection}
    5657                updateModalData={updateModalData}
  • imaps-frontend/src/components/Modals/EntranceModal/EntranceModal.module.css

    r0c6b92a r79a0317  
    3434  left: 50%;
    3535  transform: translate(-50%, -50%);
    36   background-color: #2c2f33;
     36  background-color: #2c2f33f7;
    3737  color: #ffffff !important;
    3838  padding: 20px;
     
    9090  margin-bottom: 15px;
    9191}
     92
     93/*tuka novo start*/
     94
     95label {
     96  font-size: 16px;
     97  font-weight: bold;
     98  margin-bottom: 8px;
     99}
     100
     101.customFileInput {
     102  position: relative;
     103}
     104
     105.uploadButton {
     106  background-color: #007bff;
     107  color: white;
     108  border: none;
     109  padding: 10px 20px;
     110  border-radius: 5px;
     111  cursor: pointer;
     112  font-size: 16px;
     113  transition: background-color 0.3s ease;
     114}
     115
     116.uploadButton:hover {
     117  background-color: #0056b3;
     118}
     119
     120.uploadButton:focus {
     121  outline: none;
     122  box-shadow: 0 0 4px rgba(0, 123, 255, 0.6);
     123}
     124
     125.uploadButton:active {
     126  background-color: #003f7f;
     127}
     128
     129/*tuka novo end*/
    92130
    93131.formGroup label {
  • imaps-frontend/src/components/Modals/Hooks/useConnections.jsx

    r0c6b92a r79a0317  
    55
    66    const addConnection = () => {
    7         console.log("TOSKA",shapeInfo)
    87        if (!shapeInfo.selectedPin || connections.includes(shapeInfo.selectedPin)) return;
    98
  • imaps-frontend/src/components/Modals/Hooks/useModalState.jsx

    r0c6b92a r79a0317  
    1313            map.updateRoomNames();
    1414            triggerMapSave();
    15             console.log("SHAPE BEF CLOSE:",shape.info)
     15            map.attachKeyPressEventListeners();
    1616        }
    1717        setIsOpen(!isOpen);
  • imaps-frontend/src/components/Modals/InfoPinModal/InfoPinModal.jsx

    r0c6b92a r79a0317  
    3737        setConnections(shape.info.selectedPins || []);
    3838        setIsOpen(true);
     39        event.detail.map.detachKeyPressEventListeners();
    3940
    4041        console.log(shape.info.selectedPins, "Loaded pins on modal open");
  • imaps-frontend/src/components/Modals/RoomModal/RoomModal.jsx

    r0c6b92a r79a0317  
    77import useModalState from "../Hooks/useModalState.jsx";
    88import {useModalEvent} from "../Hooks/useModalEvent.jsx";
     9import ModalUploadRoomImage from "../Components/ModalUploadRoomImage.jsx";
    910
    1011export default function RoomModal({map,roomTypes}) {
     
    2021        setShapeInfo(shape.info);
    2122        setIsOpen(true);
     23        event.detail.map.detachKeyPressEventListeners();
    2224
    2325    },"openRoomModalEvent")
     
    3032            <ModalRoomTypes updateModalData={updateModalData} shapeInfo={shapeInfo} roomTypes={roomTypes}/>
    3133            <ModalDescriptionField shapeInfo={shapeInfo} updateModalData={updateModalData}/>
     34            <ModalUploadRoomImage></ModalUploadRoomImage>
    3235            <ModalSaveButton saveDetails={saveDetails}/>
    3336        </Modal>
  • imaps-frontend/src/components/Modals/RoomTypeModal/RoomTypeModal.jsx

    r0c6b92a r79a0317  
    3131          <div onClick={toggleModal} className={styles.overlay}></div>
    3232          <div className={styles.modalContent}>
    33             <h2>Manage Room Types</h2>
     33            <h2 style={{color: 'white'}}>Manage Room Types</h2>
    3434
    3535            <form className={styles.form}>
  • imaps-frontend/src/components/Modals/StairsModal/StairsModal.jsx

    r0c6b92a r79a0317  
    3333        setIsOpen(true);
    3434        console.log(roomObj.info.selectedPins, "Loaded pins on modal open");
     35        event.detail.map.detachKeyPressEventListeners();
    3536    },"openStairsModalEvent")
    3637
  • imaps-frontend/src/components/Profile/Profile.jsx

    r0c6b92a r79a0317  
    1 import React, { useState, useRef, useEffect, useContext } from "react";
    2 import { useNavigate, Link } from "react-router-dom";
     1import React, { useState, useRef, useEffect } from "react";
     2import ReactDOM from "react-dom";
     3import { useNavigate } from "react-router-dom";
    34import profile from "../../assets/person_icon.png";
    45import styles from "./Profile.module.css";
     
    89    const { username, isAuthenticated } = useAppContext();
    910    const [open, setOpen] = useState(false);
    10     const menuRef = useRef(null);
     11    const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
    1112    const imgRef = useRef(null);
    1213    const navigate = useNavigate();
     
    1617    useEffect(() => {
    1718        const handleClickOutside = (e) => {
    18             if (menuRef.current && imgRef.current) {
    19                 if (!menuRef.current.contains(e.target) && !imgRef.current.contains(e.target)) {
    20                     setOpen(false);
    21                 }
     19            if (imgRef.current && !imgRef.current.contains(e.target)) {
     20                setOpen(false);
    2221            }
    2322        };
     
    3029    }, []);
    3130
     31    const toggleMenu = () => {
     32        if (imgRef.current) {
     33            const rect = imgRef.current.getBoundingClientRect();
     34            setMenuPosition({
     35                top: rect.bottom + window.scrollY,
     36                left: rect.left + window.scrollX,
     37            });
     38        }
     39        setOpen(!open);
     40    };
     41
    3242    const handleMenuClick = (menu) => {
    3343        if (menu === "My Maps") {
     
    3646            localStorage.removeItem("token");
    3747            window.location.reload();
     48        } else if (menu === "Login") {
     49            navigate("/Login");
    3850        }
    3951        setOpen(false);
    4052    };
    4153
     54    const renderDropdown = () => {
     55        if (!open) return null;
     56
     57        return ReactDOM.createPortal(
     58            <div
     59                className={styles.dropdownMenu}
     60                style={{
     61                    position: "absolute",
     62                    top: menuPosition.top,
     63                    left: menuPosition.left,
     64                }}
     65            >
     66                {isAuthenticated && <div className={styles.username}>{username}</div>}
     67                <ul className={styles.menuList}>
     68                    {menus.map((menu) => (
     69                        <li
     70                            key={menu}
     71                            className={styles.menuItem}
     72                            onClick={() => handleMenuClick(menu)}
     73                        >
     74                            {menu}
     75                        </li>
     76                    ))}
     77                </ul>
     78            </div>,
     79            document.body
     80        );
     81    };
     82
    4283    return (
    43         <div className={position === "fixed" ? styles.fixedProfileContainer : styles.inlineProfileContainer}>
     84        <div
     85            className={
     86                position === "fixed" ? styles.fixedProfileContainer : styles.inlineProfileContainer
     87            }
     88        >
    4489            <div className={styles.profileWrapper}>
    45                 <div className={styles.profileIconContainer} onClick={() => setOpen(!open)}>
    46                     <img src={profile} alt="profile" className={styles.profileImage} ref={imgRef} />
     90                <div
     91                    className={styles.profileIconContainer}
     92                    onClick={toggleMenu}
     93                    ref={imgRef}
     94                >
     95                    <img src={profile} alt="profile" className={styles.profileImage} />
    4796                </div>
    48                 {open && (
    49                     <div ref={menuRef} className={styles.dropdownMenu}>
    50                         {isAuthenticated && <div className={styles.username}>{username}</div>}
    51                         <ul className={styles.menuList}>
    52                             {menus.map((menu) =>
    53                                 menu === "Login" ? (
    54                                     <li key={menu} className={styles.menuItem}>
    55                                         <Link to="/login" className={styles.linkStyle}>{menu}</Link>
    56                                     </li>
    57                                 ) : (
    58                                     <li key={menu} onClick={() => handleMenuClick(menu)} className={styles.menuItem}>
    59                                         {menu}
    60                                     </li>
    61                                 )
    62                             )}
    63                         </ul>
    64                     </div>
    65                 )}
     97                {renderDropdown()}
    6698            </div>
    6799        </div>
  • imaps-frontend/src/components/PublishForm/PublishForm.jsx

    r0c6b92a r79a0317  
    1515    );
    1616    const [errors, setErrors] = useState({});
     17    const [denyReason, setDenyReason] = useState('');
     18    const [isDenying, setIsDenying] = useState(false);
    1719
    1820    const mapTypeOptions = ['Hospital', 'Faculty', 'House', 'Other'];
     
    4244            onSubmit(form);
    4345        }
     46    };
     47
     48    const handleDeny = () => {
     49        if (!denyReason.trim()) {
     50            alert('Please provide a reason for denial.');
     51            return;
     52        }
     53        onDeny(form.id, form.mapName, denyReason);
     54        setIsDenying(false);
    4455    };
    4556
     
    130141                                <button
    131142                                    className={styles.cancelButton}
    132                                     onClick={onCancel}
     143                                    onClick={() => {
     144                                        setIsDenying(false);
     145                                        onCancel();
     146                                    }}
    133147                                >
    134148                                    Cancel
     
    136150                                <button
    137151                                    className={styles.approveButton}
    138                                     onClick={() => onApprove(form.id,form.mapName)}
     152                                    onClick={() => onApprove(form.id, form.mapName)}
    139153                                >
    140154                                    Approve
     
    142156                                <button
    143157                                    className={styles.denyButton}
    144                                     onClick={() => onDeny(form.id,form.mapName,"Reason")}
     158                                    onClick={() => setIsDenying(true)}
    145159                                >
    146160                                    Deny
    147161                                </button>
    148162                            </div>
     163                            {isDenying && (
     164                                <div className={styles.denyReason}>
     165                                    <textarea className={styles.denyReasonTextArea}
     166                                        placeholder="Enter reason for denial..."
     167                                        value={denyReason}
     168                                        onChange={(e) => setDenyReason(e.target.value)}
     169                                    />
     170                                    <div className={styles.buttonGroup}>
     171                                        <button
     172                                            className={styles.denySubmitButton}
     173                                            onClick={handleDeny}
     174                                        >
     175                                            Submit
     176                                        </button>
     177                                    </div>
     178                                </div>
     179                            )}
    149180                        </div>
    150181                    )}
  • imaps-frontend/src/components/PublishForm/PublishForm.module.css

    r0c6b92a r79a0317  
    1010    z-index: 999; /* Behind the modal */
    1111}
     12
     13.denyReason {
     14    display: flex;
     15    flex-direction: column;
     16    margin-top: 10px;
     17    border-radius: 20px;
     18}
     19.denyReasonTextArea{
     20    padding-bottom: 4rem;
     21}
     22
     23.denySubmitButton {
     24    color: white;
     25    margin-right: 10px;
     26}
     27
    1228
    1329/* Modal styles */
  • imaps-frontend/src/components/RoomInfoPanel/RoomInfoPanel.jsx

    r0c6b92a r79a0317  
    5656                Navigate
    5757              </button>
     58              <p style={{fontSize: '0.8em'}}><i>From Main Entrance</i></p>
    5859            </>
    5960        ) : (
  • imaps-frontend/src/components/SearchBar/SearchBar.jsx

    r0c6b92a r79a0317  
    66import styles from "./SearchBar.module.css";
    77
    8 function SearchBar({ map, handleDirectionsSubmit, setIsPanelOpen, setSelectedRoom, availableShapes,handleFloorChange }) {
     8function SearchBar({
     9                     map,
     10                     handleDirectionsSubmit,
     11                     setIsPanelOpen,
     12                     setSelectedRoom,
     13                     availableShapes,
     14                     handleFloorChange,
     15                   }) {
    916  const [isExpanded, setIsExpanded] = useState(false);
    1017  const [from, setFrom] = useState("");
     
    1421  const [dropdownVisible, setDropdownVisible] = useState(false);
    1522  const [inputFieldType, setInputFieldType] = useState("");
    16   const dropdownRef = useRef(null);
    17 
    18   const toggleExpanded = () => {
    19     setIsExpanded(!isExpanded);
    20   };
    21 
    22   function searchRoom() {
    23     let foundRoom = availableShapes.find(sh => sh.info.name === from)
    24     console.log("map fnum",map.floorNum)
    25     if(foundRoom.floorNum !== map.floorNum){
     23
     24  const wrapperRef = useRef(null);
     25  const dropdownContainerRef = useRef(null);
     26  const activeInputRef = useRef(null); // Track the currently focused input field
     27
     28  const toggleExpanded = () => setIsExpanded(!isExpanded);
     29
     30  const handleClickOutside = (event) => {
     31    if (
     32        wrapperRef.current &&
     33        !wrapperRef.current.contains(event.target) &&
     34        dropdownContainerRef.current &&
     35        !dropdownContainerRef.current.contains(event.target)
     36    ) {
     37      setDropdownVisible(false);
     38    }
     39  };
     40
     41  useEffect(() => {
     42    document.addEventListener("mousedown", handleClickOutside);
     43    return () => {
     44      document.removeEventListener("mousedown", handleClickOutside);
     45    };
     46  }, []);
     47
     48  const searchRoom = () => {
     49    const foundRoom = availableShapes.find((sh) => sh.info.name === from);
     50    if (foundRoom && foundRoom.floorNum !== map.floorNum) {
    2651      handleFloorChange(foundRoom.floorNum);
    2752    }
    28 
    29 
    30     console.log("FOUND ROOM: " + foundRoom)
    3153    map.highlightShape(from);
    3254    setSelectedRoom(foundRoom);
    3355    setIsPanelOpen(true);
    34   }
    35 
    36   const handleInputFocus = (field) => {
     56  };
     57
     58  const handleInputFocus = (field, inputRef) => {
    3759    if (availableOptions.length === 0 && map) {
    3860      setAvailableOptions(
     
    4264      );
    4365    }
     66    setInputFieldType(field);
    4467    setDropdownVisible(true);
    45     setInputFieldType(field);
     68    activeInputRef.current = inputRef; // Set the active input ref
    4669  };
    4770
     
    6992    if (!dropdownVisible || filteredOptions.length === 0) return null;
    7093
    71     const position = dropdownRef.current?.getBoundingClientRect() || { top: 0, left: 0, width: 0 };
     94    const position = activeInputRef.current?.getBoundingClientRect() || {
     95      top: 0,
     96      left: 0,
     97      width: 0,
     98    };
    7299
    73100    return ReactDOM.createPortal(
    74101        <ul
     102            ref={dropdownContainerRef}
    75103            className={styles.dropdown}
    76104            style={{
    77105              position: "absolute",
    78               top: position.top + position.height,
    79               left: position.left,
     106              top: position.top + position.height + window.scrollY,
     107              left: position.left + window.scrollX,
    80108              width: position.width,
    81109            }}
     
    91119          ))}
    92120        </ul>,
    93         document.body // Portal renders outside the parent hierarchy
     121        document.body
    94122    );
    95123  };
    96124
    97125  return (
    98       <div className={styles.wrapper}>
     126      <div className={styles.wrapper} ref={wrapperRef}>
    99127        {!isExpanded ? (
    100128            <div className={styles.searchBar}>
     
    104132                  placeholder="Search location"
    105133                  aria-label="Search"
    106                   ref={dropdownRef} // Attach the input to calculate dropdown position
    107                   onFocus={() => handleInputFocus("from")}
     134                  onFocus={(e) => handleInputFocus("from", e.target)}
    108135                  onChange={handleInputChange(setFrom)}
    109136                  value={from}
     
    111138              {renderDropdown()}
    112139              <div className={styles.buttons}>
    113                 <button type="button" className={styles.iconButton} onClick={searchRoom}>
     140                <button
     141                    type="button"
     142                    className={styles.iconButton}
     143                    onClick={searchRoom}
     144                >
    114145                  <img src={searchIcon} alt="Search Icon" />
    115146                </button>
    116                 <button type="button" className={styles.iconButton} onClick={toggleExpanded}>
     147                <button
     148                    type="button"
     149                    className={styles.iconButton}
     150                    onClick={toggleExpanded}
     151                >
    117152                  <img src={routeIcon} alt="Route Icon" />
    118153                </button>
     
    127162                    aria-label="From"
    128163                    value={from}
    129                     onFocus={() => handleInputFocus("from")}
     164                    onFocus={(e) => handleInputFocus("from", e.target)}
    130165                    onChange={handleInputChange(setFrom)}
    131166                    className={styles.inputField}
    132                     ref={inputFieldType === "from" ? dropdownRef : null}
    133167                />
    134168                <input
     
    137171                    aria-label="To"
    138172                    value={to}
    139                     onFocus={() => handleInputFocus("to")}
     173                    onFocus={(e) => handleInputFocus("to", e.target)}
    140174                    onChange={handleInputChange(setTo)}
    141175                    className={styles.inputField}
    142                     ref={inputFieldType === "to" ? dropdownRef : null}
    143176                />
    144177                {renderDropdown()}
     
    152185                  <img src={searchIcon} alt="Submit Directions" />
    153186                </button>
    154                 <button type="button" className={styles.iconButton} onClick={toggleExpanded}>
     187                <button
     188                    type="button"
     189                    className={styles.iconButton}
     190                    onClick={toggleExpanded}
     191                >
    155192                  <img src={closeIcon} alt="Close Icon" />
    156193                </button>
  • imaps-frontend/src/components/SearchBar/SearchBar.module.css

    r0c6b92a r79a0317  
    2626.inputField {
    2727  flex-grow: 1;
    28   padding: 10px 15px;
     28  /*padding: 10px 15px;*/
    2929  font-size: 16px;
    3030  border: 1px solid #ddd;
     
    3333  margin-right: 10px;
    3434  min-width: 0;
    35 
     35  margin-top: 12px;
    3636}
    3737
  • imaps-frontend/src/main.jsx

    r0c6b92a r79a0317  
    44import './index.css'
    55
     6if (typeof GestureEvent === "undefined") {
     7    window.GestureEvent = class GestureEvent {};
     8}
     9
     10
    611ReactDOM.createRoot(document.getElementById('root')).render(
    712   <App />
  • imaps-frontend/src/pages/AdminPage/AdminPage.jsx

    r0c6b92a r79a0317  
    1111import Profile from "../../components/Profile/Profile.jsx";
    1212import Toast from "../../components/Toast/Toast.jsx";
     13import ListReports from "../../components/ListReports/ListReports.jsx";
    1314
    1415const renderTile = ({data, isDragging}, handleApprove, handleDeny, openMapInfoModal, openPublishForm) => (
     
    8283                gmaps_url: elem.gmaps_url,
    8384                image_url: card,
    84             })).filter((tile) => tile.status === "INVALID");
     85            })).filter((tile) => tile.status === "PENDING");
    8586
    8687            setPendingMaps(mapTiles);
     
    145146
    146147        try {
    147             await httpService.post(url, formData); // Assuming formData contains all required fields
     148            await httpService.post(url, formData);
    148149            setPendingMaps((prev) => prev.filter((map) => map.mapName !== formData.mapName));
    149150            alert(`Map "${formData.mapName}" published successfully.`);
     
    187188            <Profile></Profile>
    188189            <h1>Pending Maps for Approval</h1>
     190            <hr/>
    189191
    190192            {publishFormMap && (
     
    210212                />
    211213            </div>
     214            <hr/>
    212215            {isMapInfoModalOpen && (
    213216                <MapInfoModal
     
    223226            )}
    224227            {toastMessage && <Toast message={toastMessage} type={toastType} onClose={() => setToastMessage(null)}/>}
    225 
     228        <hr/>
     229            <ListReports></ListReports>
    226230        </div>
    227231    );
  • imaps-frontend/src/pages/BrowseMaps/BrowseMaps.jsx

    r0c6b92a r79a0317  
    11import styles from "./Maps.module.css";
    22import "react-tiles-dnd/esm/index.css";
    3 import { TilesContainer } from "react-tiles-dnd";
    4 import { Link } from "react-router-dom";
     3import {TilesContainer} from "react-tiles-dnd";
     4import {Link} from "react-router-dom";
    55import card from "../../assets/card-map.png";
    66import star_icon from "../../assets/star_icon.png"; // Unfilled star icon
    77import star_filled_icon from "../../assets/star_filled_icon.png"; // Filled star icon
    8 import { useEffect, useState } from "react";
     8import {useEffect, useState} from "react";
    99import HttpService from "../../scripts/net/HttpService.js";
    1010import Logo from "../../components/Logo/Logo.jsx";
    1111import Profile from "../../components/Profile/Profile.jsx";
    1212import config from "../../scripts/net/netconfig.js";
    13 import { useAppContext } from "../../components/AppContext/AppContext.jsx";
     13import {useAppContext} from "../../components/AppContext/AppContext.jsx";
     14import FilterMaps from "../../components/FilterMaps/FilterMaps.jsx";
    1415
    1516let loadedTiles = [];
    1617
    17 const renderTile = ({ data, isDragging, toggleFavorite }) => (
    18     <div style={{ padding: "1rem", width: "100%", position: "relative" }}>
    19         <Link to={`/Maps/${data.text}/View`} className={styles.linkStyle}>
     18const renderTile = ({data, isDragging, toggleFavorite}) => (
     19    <div style={{padding: "1rem", width: "100%", position: "relative"}}>
     20        <Link to={`/Maps/View/${data.text}`} className={styles.linkStyle}>
    2021            <div
    2122                className={`${styles.tile} ${isDragging ? styles.dragging : ""}`}
    22                 style={{ width: "100%", height: "100%" }}
     23                style={{width: "100%", height: "100%"}}
    2324            >
    24                 <img src={card} className={styles.imgStyle} alt="Map Thumbnail" />
    25                 <div style={{ fontFamily: "exo" }}>
     25                <img src={card} className={styles.imgStyle} alt="Map Thumbnail"/>
     26                <div style={{fontFamily: "exo"}}>
    2627                    {data.text} {isDragging ? "DRAGGING" : null}
    2728                </div>
     
    3233                src={data.isFavorite ? star_filled_icon : star_icon}
    3334                alt="Favorite Icon"
    34                 style={{ width: "20px", height: "20px" }}
     35                style={{width: "20px", height: "20px"}}
    3536            />
    3637        </div>
     
    4647    const [searchTerm, setSearchTerm] = useState("");
    4748    const [tiles, setTiles] = useState([]);
    48     const { username, isAuthenticated } = useAppContext();
     49    const {username, isAuthenticated} = useAppContext();
     50    const [filter, setFilter] = useState("all")
     51    const [mapFilters, setMapFilters] = useState([])
    4952
    5053    useEffect(() => {
     
    5255            const httpService = new HttpService();
    5356            let mapTiles = [];
    54 
     57            let mapTypes = ['Hospital', 'Faculty', 'House', 'Other'];
    5558            if (isAuthenticated) {
    5659                // :D
     
    6366                    rows: 1,
    6467                    isFavorite: true,
     68                    type: elem.mapType,
    6569                }));
    6670
     71                console.log("TUKA")
    6772                // Load all maps
    6873                const allResp = await httpService.get(config.view_maps.display);
    6974                console.log("RESPONSE MAPS PUBLIC", allResp);
     75
     76                // mapTypes = allResp.filter(elem => elem.mapType != null && elem.mapType !== "").map(elem => elem.mapType);
     77
    7078
    7179                const nonFavMapTiles = allResp
     
    7684                        rows: 1,
    7785                        isFavorite: false,
     86                        type: elem.mapType,
    7887                    }));
     88
    7989
    8090                mapTiles = [...favMapTiles, ...nonFavMapTiles];
     
    8292                const allResp = await httpService.get(config.view_maps.display);
    8393                console.log("RESPONSE MAPS PUBLIC", allResp);
     94                // mapTypes = allResp.filter(elem => elem.mapType != null && elem.mapType !== "").map(elem => elem.mapType);
    8495
    8596                mapTiles = allResp.map((elem) => ({
     
    8899                    rows: 1,
    89100                    isFavorite: false,
     101                    type: elem.mapType,
    90102                }));
    91103            }
     104
     105            console.log("TYPES:", mapTypes);
     106            setMapFilters(mapTypes);
    92107
    93108            loadedTiles = [...mapTiles];
     
    114129
    115130        const updatedTiles = tiles.map((tile) =>
    116             tile.text === tileName ? { ...tile, isFavorite: !tile.isFavorite } : tile
     131            tile.text === tileName ? {...tile, isFavorite: !tile.isFavorite} : tile
    117132        );
    118133
     
    140155    };
    141156
     157    const onFilter = (selectedFilter) => {
     158        setFilter(selectedFilter);
     159
     160        if (selectedFilter === "all") {
     161            // Show all tiles
     162            setTiles(loadedTiles);
     163        } else {
     164            // Filter tiles by selected type
     165            const filteredTiles = loadedTiles.filter((tile) => tile.type === selectedFilter);
     166            setTiles(filteredTiles);
     167        }
     168    };
     169
     170
    142171    return (
    143172        <div className={styles.container}>
     
    152181                    onChange={handleSearchChange}
    153182                />
     183
    154184            </div>
    155 
     185            <div className={styles.filterBar}>
     186            <FilterMaps mapTypes={mapFilters} setFilter={onFilter}></FilterMaps>
     187            </div>
     188            {filter !== "all" && tiles.length === 0 && <p>No maps of type {filter} found</p>}
    156189            <TilesContainer
    157190                data={tiles}
    158                 renderTile={(props) => renderTile({ ...props, toggleFavorite })}
     191                renderTile={(props) => renderTile({...props, toggleFavorite})}
    159192                tileSize={tileSize}
    160                 forceTileWidth={150}
    161                 forceTileHeight={170}
     193                forceTileWidth={170}
     194                forceTileHeight={200}
    162195            />
    163196        </div>
  • imaps-frontend/src/pages/BrowseMaps/Maps.module.css

    r0c6b92a r79a0317  
    1919  margin-bottom: 30px;
    2020  text-align: center;
     21  margin-top: 5em;
    2122}
    2223
    2324.searchBar input {
    24   width: 300px;
     25  /*width: 300px;*/
     26  width: 33vw;
    2527  padding: 10px;
    2628  font-size: 16px;
     
    3436  border-color: #1e90ff;
    3537  box-shadow: 0 0 8px rgba(30, 144, 255, 0.5);
     38}
     39
     40.filterBar{
     41  margin-left: 6vw;
    3642}
    3743
  • imaps-frontend/src/pages/Draw/Draw.jsx

    r0c6b92a r79a0317  
    1 import { useContext, useEffect, useState } from "react";
     1import {useContext, useEffect, useState} from "react";
    22import styles from "./Draw.module.css";
    33import RoomModal from "../../components/Modals/RoomModal/RoomModal.jsx";
     
    88import SaveMap from "../../components/SaveMap/SaveMap.jsx";
    99import Logo from "../../components/Logo/Logo.jsx";
    10 import { Link, useNavigate, useParams, useSearchParams } from "react-router-dom";
     10import {Link, useNavigate, useParams, useSearchParams} from "react-router-dom";
    1111import Profile from "../../components/Profile/Profile.jsx";
    1212import HttpService from "../../scripts/net/HttpService.js";
     
    1818import config from "../../scripts/net/netconfig.js";
    1919import ShapeRegistry from "../../scripts/util/ShapeRegistry.js";
     20import {TopPanel} from "./TopPanel/TopPanel.jsx";
    2021
    2122function Draw() {
    22   const { mapName } = useParams();
    23   const { username } = useAppContext();
     23    const {mapName} = useParams();
     24    const {username} = useAppContext();
    2425
    25   const [isPopupVisible, setIsPopupVisible] = useState(false);
    26   const [errorMessage, setErrorMessage] = useState("Error");
    27   const [hasError, setHasError] = useState(false);
    28   const [searchParams, setSearchParams] = useSearchParams();
     26    const [isPopupVisible, setIsPopupVisible] = useState(false);
     27    const [errorMessage, setErrorMessage] = useState("Error");
     28    const [hasError, setHasError] = useState(false);
     29    const [searchParams, setSearchParams] = useSearchParams();
    2930
    30   const[roomTypes,setRoomTypes] = useState([]);
     31    const [roomTypes, setRoomTypes] = useState([]);
    3132
    32   const {app,floors,saveFloor,setFloors} = useMapLoader(mapName,username,searchParams,setSearchParams)
    33   const {addRoomType} = useRoomTypesLoader(setRoomTypes,mapName,username);
     33    const {app, floors, saveFloor, setFloors} = useMapLoader(mapName, username, searchParams, setSearchParams)
     34    const {addRoomType} = useRoomTypesLoader(setRoomTypes, mapName, username);
    3435
    35   const addFloorHandler = async (newFloorNum) => {
    36     const httpService = new HttpService();
    37     httpService.setAuthenticated();
     36    const addFloorHandler = async (newFloorNum) => {
     37        const httpService = new HttpService();
     38        httpService.setAuthenticated();
    3839
    39     const payload = {
    40       num: newFloorNum,
    41       mapName: mapName,
     40        const payload = {
     41            num: newFloorNum,
     42            mapName: mapName,
     43        };
     44
     45        try {
     46            await httpService.put(`${config.floors.add}`, payload);
     47            console.log(`Added floor ${newFloorNum}`);
     48            setFloors((prevFloors) => [...prevFloors, {num: newFloorNum}]);
     49        } catch (error) {
     50            console.error("Error adding floor:", error);
     51        }
    4252    };
    4353
    44     try {
    45       await httpService.put(`${config.floors.add}`, payload);
    46       console.log(`Added floor ${newFloorNum}`);
    47       setFloors((prevFloors) => [...prevFloors, { num: newFloorNum }]);
    48     } catch (error) {
    49       console.error("Error adding floor:", error);
    50     }
    51   };
     54    const deleteFloorHandler = async (floorNum) => {
     55        if (floorNum === 0) return
    5256
    53   const deleteFloorHandler = async (floorNum) => {
    54     if(floorNum === 0) return
     57        const httpService = new HttpService();
     58        httpService.setAuthenticated();
    5559
    56     const httpService = new HttpService();
    57     httpService.setAuthenticated();
     60        try {
     61            await httpService.delete(`${config.floors.delete}?floorNum=${floorNum}&mapName=${mapName}`);
     62            setFloors((prevFloors) => prevFloors.filter(f => f.num !== floorNum))
    5863
    59     try {
    60       await httpService.delete(`${config.floors.delete}?floorNum=${floorNum}&mapName=${mapName}`);
    61       setFloors((prevFloors) => prevFloors.filter(f => f.num !== floorNum))
     64            const currFloor = searchParams.get("floor");
     65            if (currFloor == floorNum) {
     66                setSearchParams({floor: "0"}, {replace: true})
     67            }
     68            console.log(`Deleted floor ${floorNum}`);
     69        } catch (error) {
     70            console.error("Error deleting floor:", error);
     71        }
     72    };
    6273
    63       const currFloor = searchParams.get("floor");
    64       if(currFloor == floorNum){
    65         setSearchParams({floor:"0"},{replace:true})
    66       }
    67       console.log(`Deleted floor ${floorNum}`);
    68     } catch (error) {
    69       console.error("Error deleting floor:", error);
    70     }
    71   };
    72 
    73   useEffect(() => {
    74     return () => {
    75       ShapeRegistry.clear();
    76     }
    77   }, []);
     74    useEffect(() => {
     75        return () => {
     76            ShapeRegistry.clear();
     77        }
     78    }, []);
    7879
    7980
    80   const handleSaveClick = async () => {
    81     saveFloor().then(r => {
    82      floors.forEach(flr => {
    83        setIsPopupVisible(true);
    84        setTimeout(() => {
    85          setIsPopupVisible(false);},
    86            3000);
    87        console.log("floor after save: " + JSON.stringify(flr))
    88      })
    89     });
     81    const handleSaveClick = async () => {
     82        saveFloor()
     83        setIsPopupVisible(true);
     84        setTimeout(() => {
     85                setIsPopupVisible(false);
     86            },
     87            3000);
     88    };
    9089
     90    return (
     91        <div className={styles.wrapper} id="wrapper">
     92            {/* <SideBar></SideBar> */}
     93            <Logo></Logo>
     94            <div id="container" className={styles.cont}></div>
     95            <div className={styles.panel}>
     96                <div className={styles.topPanelH}>
     97                    <Profile position="inline"></Profile>
     98                </div>
     99                <Link to={`/myMaps/View/${mapName}`} className={styles.titleLink}>
     100                    <h1 className={styles.title}>{mapName}</h1>
     101                </Link>
     102                <div className={styles.guideWrapper}>
     103                    <DrawGuide/>
     104                </div>
     105                <hr/>
     106                <br/>
     107                {/* {<h2 className={styles.paragraph}>Objects:</h2>} */}
     108                <ul className={styles.shapeOptions} id="shapeOptions">
     109                    <li data-info="Entrance" className={`${styles.shapeOption} ${styles.entrance}`}></li>
     110                    <li data-info="Wall" className={`${styles.shapeOption} ${styles.wall}`} id="wall"></li>
     111                    <li data-info="Room" className={`${styles.shapeOption} ${styles.room}`} id="room"></li>
     112                    <li data-info="Stairs" className={`${styles.shapeOption} ${styles.stairs}`} id="stairs"></li>
     113                </ul>
     114                <RoomTypeModal map={app} roomTypes={roomTypes} addRoomTypeDB={addRoomType}></RoomTypeModal>
     115                <br/>
     116                <hr/>
     117                <br/>
     118                <FloorSelector floorConfig={{
     119                    floors, searchParams,
     120                    setSearchParams, addFloorHandler,
     121                    deleteFloorHandler
     122                }}></FloorSelector>
    91123
    92   };
     124                <br/>
    93125
    94   return (
    95     <div className={styles.wrapper} id="wrapper">
    96       {/* <SideBar></SideBar> */}
    97       <Logo></Logo>
    98       <div id="container" className={styles.cont}></div>
    99       <div className={styles.panel}>
    100         <div className={styles.topPanelH}>
    101           <Profile position="inline"></Profile>
     126                <hr/>
     127                <br/>
     128                {hasError && <p style={{color: "red", textAlign: "center"}}>{errorMessage}</p>}
     129                <div className={styles.templateCont}>
     130                    <SaveMap submitHandler={handleSaveClick}></SaveMap>
     131                </div>
     132
     133                <div className={styles.hide}>
     134                    <RoomModal map={app} roomTypes={roomTypes}></RoomModal>
     135                    <EntranceModal map={app}></EntranceModal>
     136                    <InfoPinModal map={app}></InfoPinModal>
     137                    <StairsModal map={app}></StairsModal>
     138                </div>
     139            </div>
     140
     141            {isPopupVisible && (
     142                <div className={styles.popup}>
     143                    <div className={styles.popupContent}>
     144                        <h2>Map Saved!</h2>
     145                        <p>Your map has been successfully saved.</p>
     146                    </div>
     147                </div>
     148            )}
    102149        </div>
    103         <Link to={`/myMaps/${mapName}/View`} className={styles.titleLink}>
    104           <h1 className={styles.title}>{mapName}</h1>
    105         </Link>
    106         <div className={styles.guideWrapper}>
    107           <DrawGuide/>
    108         </div>
    109         <hr/>
    110         <br/>
    111         {/* {<h2 className={styles.paragraph}>Objects:</h2>} */}
    112         <ul className={styles.shapeOptions} id="shapeOptions">
    113           <li data-info="Entrance" className={`${styles.shapeOption} ${styles.entrance}`}></li>
    114           <li data-info="Wall" className={`${styles.shapeOption} ${styles.wall}`} id="wall"></li>
    115           <li data-info="Room" className={`${styles.shapeOption} ${styles.room}`} id="room"></li>
    116           <li data-info="Stairs" className={`${styles.shapeOption} ${styles.stairs}`} id="stairs"></li>
    117         </ul>
    118         <RoomTypeModal map={app} roomTypes={roomTypes} addRoomTypeDB={addRoomType}></RoomTypeModal>
    119         <br/>
    120         <hr/>
    121         <br/>
    122         <FloorSelector floorConfig={{
    123           floors,searchParams,
    124           setSearchParams,addFloorHandler,
    125           deleteFloorHandler
    126         }}></FloorSelector>
    127 
    128         <br/>
    129 
    130         <hr/>
    131         <br/>
    132         {hasError && <p style={{color: "red", textAlign: "center"}}>{errorMessage}</p>}
    133         <div className={styles.templateCont}>
    134           <SaveMap submitHandler={handleSaveClick}></SaveMap>
    135         </div>
    136 
    137         <div className={styles.hide}>
    138           <RoomModal map={app} roomTypes={roomTypes}></RoomModal>
    139           <EntranceModal map={app}></EntranceModal>
    140           <InfoPinModal map={app}></InfoPinModal>
    141           <StairsModal map={app}></StairsModal>
    142         </div>
    143       </div>
    144 
    145       {isPopupVisible && (
    146           <div className={styles.popup}>
    147             <div className={styles.popupContent}>
    148               <h2>Map Saved!</h2>
    149               <p>Your map has been successfully saved.</p>
    150             </div>
    151           </div>
    152       )}
    153     </div>
    154   );
     150    );
    155151}
    156152
  • imaps-frontend/src/pages/Draw/Draw.module.css

    r0c6b92a r79a0317  
    2525  font-weight: 700;
    2626  text-transform: uppercase;
    27   color: #333333;
     27  color: #c0c0c0;
    2828  font-family: "Oswald", sans-serif;
    2929  margin-bottom: 0;
  • imaps-frontend/src/pages/IMaps/components/Button.css

    r0c6b92a r79a0317  
    2020
    2121.btn--outline {
    22   background-color: transparent;
     22  background-color: #ffffffc2;
    2323
    24   color: #fff;
     24  color: #252627;
    2525  padding: 8px 20px;
     26  border: 1px solid #252627 ;
    2627  /* border: 1px solid var(--primary); */
    2728  transition: all 0.3s ease-out;
     
    3738  font-size: 24px;
    3839}
     40.btn--outline:hover{
     41  transition: all 0.3s ease-out;
     42  background: #000000;
     43  color:#ffffffc2;
     44}
    3945
    4046.btn--large:hover,
     
    4349  background: #ffffffc2;
    4450  color: #000000;
    45   transition: 250ms;
    4651}
  • imaps-frontend/src/pages/IMaps/components/Cards.jsx

    r0c6b92a r79a0317  
    1919              text="Create intricate floor plans for your building with precision and ease."
    2020              label="Create"
    21               path="/services"
     21              path="#"
    2222            />
    2323            <CardItem
     
    2525              text="Explore and navigate through complex building layouts seamlessly."
    2626              label="Explore"
    27               path="/services"
     27              path="#"
    2828            />
    2929          </ul>
     
    3333              text="Add custom icons, labels, and markers to personalize your indoor maps."
    3434              label="Customize"
    35               path="/services"
     35              path="#"
    3636            />
    3737            <CardItem
     
    3939              text="Ensure accessibility by mapping out routes and facilities for all users."
    4040              label="Accessibility"
    41               path="/products"
     41              path="#"
    4242            />
    4343            <CardItem
     
    4545              text="Share your maps with others and collaborate in real-time for efficient space planning."
    4646              label="Collaboration"
    47               path="/sign-up"
     47              path="#"
    4848            />
    4949          </ul>
  • imaps-frontend/src/pages/IMaps/components/Footer.css

    r0c6b92a r79a0317  
    4747.footer-links {
    4848  width: 100%;
    49   max-width: 1000px;
    5049  display: flex;
    51   justify-content: center;
     50  justify-content: space-evenly;
     51}
     52.footer-link-wrapper a {
     53  margin-top: 1em;
     54}
     55.footer-link-wrapper h2 {
     56  width: 100%;
    5257}
    5358
    54 .footer-link-wrapper {
    55   display: flex;
    56 }
    5759
    5860.footer-link-items {
    5961  display: flex;
    6062  flex-direction: column;
    61   align-items: flex-start;
     63  align-items: center;
    6264  margin: 16px;
    6365  text-align: left;
     
    100102
    101103.social-media {
    102   max-width: 1000px;
    103   width: 100%;
    104104}
    105105
    106106.social-media-wrap {
    107107  display: flex;
    108   justify-content: space-between;
     108  flex-direction: column;
    109109  align-items: center;
    110   width: 90%;
    111   max-width: 1000px;
    112   margin: 40px auto 0 auto;
    113110}
    114111
     
    123120  color: #fff;
    124121  justify-self: start;
    125   margin-left: 20px;
    126122  cursor: pointer;
    127123  text-decoration: none;
     
    131127  margin-bottom: 16px;
    132128}
     129.social-media-wrap img {
     130  width: 25%;
     131}
    133132
    134133.website-rights {
    135134  color: #fff;
    136   margin-bottom: 16px;
     135  align-content: flex-end;
     136  width: 50%;
     137  margin-top: 2em;
    137138}
    138139
  • imaps-frontend/src/pages/IMaps/components/Footer.jsx

    r0c6b92a r79a0317  
    22import "./Footer.css";
    33import { Button } from "./Button";
    4 import logo from "../../../assets/logo_icon.png";
     4import logo from "../../../assets/novo_logo_nobg_cropped.png";
     5import {Link} from "react-router-dom";
    56
    67function Footer() {
    78  return (
    89    <div className="footer-container">
    9       {/* <div className="footer-links">
     10      <div className="footer-links">
    1011        <div className="footer-link-wrapper">
    1112          <div className="footer-link-items">
     
    1314            <a href="#">Contact</a>
    1415            <a href="#">Support</a>
    15             <a href="#">Destinations</a>
    16             <a href="#">Sponsorships</a>
    1716          </div>
    1817        </div>
    19         <div className="footer-link-wrapper">
    20           <div className="footer-link-items">
    21             <h2>Social Media</h2>
    22             <a href="#">Instagram</a>
    23             <a href="#">Facebook</a>
    24             <a href="#">Youtube</a>
    25             <a href="#">Twitter</a>
     18          <div className="social-media-wrap">
     19
     20            <div className="footer-logo">
     21              <img src={logo} alt={"logo"}/>
     22            </div>
     23            <small className="website-rights">iMaps © {2024}</small>
     24          </div>
     25        <div>
     26          <div className="footer-link-wrapper">
     27            <div className="footer-link-items">
     28              <h2>Legal & Privacy</h2>
     29              <Link to={"/privacy-policy"}>Privacy Policy</Link>
     30              <Link to={"/terms-of-service"}>Terms Of Service</Link>
     31
     32            </div>
    2633          </div>
    2734        </div>
    28       </div> */}
    29       <section className="social-media">
    30         <div className="social-media-wrap">
    31           <div className="footer-logo">
    32             <a href="#" className="social-logo">
    33               iMaps
    34             </a>
    35           </div>
    36           <small className="website-rights">iMaps © {2024}</small>
    37           <div className="social-icons">
    38             <a className="social-icon-link facebook" href="#" aria-label="Facebook">
    39               <i className="fab fa-facebook-f" />
    40             </a>
    41             <a className="social-icon-link instagram" href="#" aria-label="Instagram">
    42               <i className="fab fa-instagram" />
    43             </a>
    44             <a className="social-icon-link youtube" href="#" aria-label="Youtube">
    45               <i className="fab fa-youtube" />
    46             </a>
    47             <a className="social-icon-link twitter" href="#" aria-label="Twitter">
    48               <i className="fab fa-twitter" />
    49             </a>
    50             <a className="social-icon-link linkedin" href="#" aria-label="LinkedIn">
    51               <i className="fab fa-linkedin" />
    52             </a>
    53           </div>
    54         </div>
    55       </section>
     35
     36      </div>
     37
    5638    </div>
    5739  );
  • imaps-frontend/src/pages/IMaps/components/Navbar.css

    r0c6b92a r79a0317  
    11.modern-navbar {
    2   /*background-color: #ffffff;*/
    3   /*background: linear-gradient(*/
    4   /*        90deg,*/
    5   /*        #fafafa 0%,*/
    6   /*        #c07e7e 25%,*/
    7   /*        #af2525 50%,*/
    8   /*        #443a3a 75%,*/
    9   /*        #8d1010 100%*/
    10   /*);*/
    11   /*background: linear-gradient(to right, #ffffff, #c12c2c);*/
    12   /*background: linear-gradient(90deg, #3b3131, #ffffff);*/
    13   background: linear-gradient(90deg, #9b1818, #efefef);
     2  background: linear-gradient(90deg, #ffffff, #fffefe);
    143  color: #ffffff;
    154  /*color: #333333;*/
     
    2413  border-radius: 8px;
    2514  margin: 5px;
     15  height: 70px;
     16  overflow: hidden;
    2617}
    2718
  • imaps-frontend/src/pages/IMaps/components/Navbar.jsx

    r0c6b92a r79a0317  
    1010function Navbar() {
    1111  const [click, setClick] = useState(false);
    12   const [button, setButton] = useState(true);
    13 
    14   const handleClick = () => setClick(!click);
    15   const closeMobileMenu = () => setClick(false);
    1612
    1713  const { isAuthenticated } = useAppContext();
     
    2319                <div className="navbar-left">
    2420                    <Logo position="relative"/>
    25                     <h1 className="navbar-title">iMaps</h1>
    2621                </div>
    2722
  • imaps-frontend/src/pages/IMaps/components/pages/Home.jsx

    r0c6b92a r79a0317  
    1414        <h2 className="description">Create and explore detailed indoor maps.</h2>
    1515        <div className="hero-btns">
    16           <Link to="/myMaps">
    17             <Button className="btns" buttonSize="btn--large">
    18               Create Maps
     16          <Link to="/Maps">
     17            <Button buttonSize="btn--large" >
     18              Browse Maps <i className="far fa-play-circle" />
    1919            </Button>
    2020          </Link>
    21           <Link to="/Maps">
    22             <Button className="btns" buttonSize="btn--large">
    23               Browse Maps <i className="far fa-play-circle" />
     21          <Link to="/myMaps">
     22            <Button buttonSize="btn--large" buttonStyle="btn--outline">
     23              Create Maps
    2424            </Button>
    2525          </Link>
  • imaps-frontend/src/pages/IMaps/components/pages/Home.scss

    r0c6b92a r79a0317  
    5050  width: 100vw;
    5151  /* img size is 50x50 */
    52   background: url($bg-url) repeat 0 0;
     52  /* Set a base background color */
     53  background: #87898d url($bg-url) repeat 0 0;
     54
     55  /* Apply hue rotation to the image */
    5356
    5457  -webkit-animation: bg-scrolling-reverse .92s infinite; /* Safari 4+ */
     
    98101
    99102  font-size: 6em;
    100 
    101 
    102103
    103104
  • imaps-frontend/src/pages/Login/Login.jsx

    r0c6b92a r79a0317  
    66import HttpService from "../../scripts/net/HttpService.js";
    77import {useAppContext} from "../../components/AppContext/AppContext.jsx";
    8 import config from "../../scripts/net/netconfig.js";
     8import config, {API_BASE_URL} from "../../scripts/net/netconfig.js";
     9import google_icon from "../../assets/Logo-google-icon-PNG.png"
     10import github_icon from "../../assets/github-mark-white.png";
     11import { v4 as uuidv4 } from 'uuid';
    912
    1013const LoginPage = () => {
     
    2629    const handleLogin = async () => {
    2730        const httpService = new HttpService();
    28         return httpService.post(config.auth.login, payload)
    29 
     31        return httpService.post(config.auth.login, payload);
    3032    };
    31 
    3233
    3334    const login = async (e) => {
     
    3738            .then(resp => {
    3839                if (resp.token) {
    39                     navigate(targetPath)
     40                    navigate(targetPath);
    4041                    localStorage.setItem("token", resp.token);
    4142                    setUsername(resp.username);
    4243                    setIsAuthenticated(true);
    43                     console.log("ROLES",resp.roles)
     44                    console.log("ROLES", resp.roles);
    4445                } else {
    4546                    setError("Invalid username or password.");
     
    4748            }).catch(reason => {
    4849            console.error("Login failed", reason);
    49             setError("Login failed. Please try again.")
    50         })
     50            setError("Login failed. Please try again.");
     51        });
     52    };
    5153
    52         // fetch("http://localhost:8080/api/auth/login", {
    53         //   method: "POST",
    54         //   headers: {
    55         //     "Content-Type": "application/json",
    56         //   },
    57         //   body: JSON.stringify(payload),
    58         // })
    59         //   .then((response) => {
    60         //     if (!response.ok) {
    61         //       throw new Error("Login failed: resp = " + response.statusText);
    62         //     }
    63         //     return response.json();
    64         //   })
    65         //   .then((data) => {
    66         //     if (data.token) {
    67         //       navigate(targetPath);
    68         //       handleLogin(data);
    69         //     } else {
    70         //       setError("Invalid username or password.");
    71         //     }
    72         //   })
    73         //   .catch((error) => {
    74         //     console.error("Login failed", error);
    75         //     setError("Login failed. Please try again.");
    76         //   });
     54    const continueWithGitHub = async () => {
     55        const httpService = new HttpService();
     56        httpService.setResponseType('text');
     57        const state = await httpService.get(config.auth.oauth.github.state)
     58        const clientId = 'Iv23liqzhX5wMYNDHtnz';
     59        const redirectUri = encodeURI(`${API_BASE_URL}/oauth/callback/github`);
     60
     61        const githubAuthUrl = `https://github.com/login/oauth/authorize?client_id=${encodeURI(clientId)}&redirect_uri=${redirectUri}&state=${encodeURI(state)}&scope=user:email`;
     62
     63        window.location.href = githubAuthUrl;
     64
     65    };
     66
     67    const continueWithGoogle = async () => {
     68        console.log("Continue with Google");
     69        const httpService = new HttpService();
     70        httpService.setResponseType('text');
     71        const state = await httpService.get(config.auth.oauth.github.state)
     72        const clientId = '1024418489231-ml40ukvqcg9ad1h5ejor5dm6ipt6p8fo.apps.googleusercontent.com';
     73        const redirectUri = encodeURI(`${API_BASE_URL}/oauth/callback/google`);
     74        const googleAuthUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${encodeURIComponent(clientId)}
     75        &redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(state)}&response_type=code&scope=${encodeURIComponent("openid profile email")}`;
     76        window.location.href = googleAuthUrl
    7777    };
    7878
     
    111111                    <button type="submit">Submit</button>
    112112                </form>
     113                <div className={styles.or}>OR</div>
     114                <div className={styles.socialButtons}>
     115                    <button className={styles.socialButton} onClick={continueWithGoogle}>
     116                        <img src={google_icon} alt="Facebook Icon" className={styles.socialIcon}/>
     117                        Sign In With Google
     118                    </button>
     119                    <button className={styles.socialButton} onClick={continueWithGitHub}>
     120                        <img src={github_icon} alt="GitHub Icon" className={styles.socialIcon}/>
     121                        Sign In With GitHub
     122                    </button>
     123                </div>
    113124                <p>
    114125                    Don't have an account? <Link to="/Signup"> Sign Up </Link>
  • imaps-frontend/src/pages/Login/Login.module.css

    r0c6b92a r79a0317  
    1313
    1414.wrapper {
    15   display: -webkit-box;
    16   display: -ms-flexbox;
    1715  display: flex;
     16  justify-content: flex-end; /* Move content towards the right */
     17  align-items: center; /* Center content vertically */
    1818  height: 100vh;
    19   -webkit-box-align: center;
    20   -ms-flex-align: center;
    21   align-items: center;
    22 }
    23 
    24 .wrapper p {
    25   font-size: 0.85rem;
    26   margin-top: 1rem;
    27 }
     19  margin-left: 30%;
     20  padding: 2rem;
     21}
     22
     23
    2824
    2925.form {
    30   padding: 1.5rem;
    31   -ms-flex-preferred-size: 100vw;
    32   flex-basis: 100vw;
     26  padding: 2rem;
     27  display: flex;
     28  flex-direction: column; /* Stack form elements vertically */
     29  align-items: center; /* Center form elements horizontally */
     30  justify-content: center; /* Vertically center the form */
     31  background-color: #fff;
     32  border-radius: 8px; /* Rounded corners for the form */
     33  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Add subtle shadow to form */
     34  width: 100%;
     35  max-width: 400px; /* Restrict the form width for better appearance */
    3336}
    3437
    3538.form .heading {
    36   font-size: 1.5rem;
     39  font-size: 1.8rem;
    3740  font-weight: bold;
    3841  text-align: center;
     42  margin-bottom: 1.5rem; /* Spacing between heading and form */
    3943}
    4044
    4145.or {
    42   margin: 1rem 0;
     46  margin-top: 1em;
     47  text-align: center;
     48  font-weight: bold;
     49  color: #666;
    4350}
    4451
     
    4653  display: block;
    4754  margin: 1.25rem 0 1rem 0;
     55  font-size: 0.95rem;
     56  color: #333;
    4857}
    4958
     
    5160  height: 40px;
    5261  width: 100%;
     62  max-width: 350px; /* Slightly larger max-width */
    5363  padding: 15px;
    5464  background-color: #f1f9ff;
    5565  border: 2px solid #bce0fd;
    5666  border-radius: 8px;
     67  box-sizing: border-box;
     68  font-size: 1rem; /* Make input text a bit larger */
    5769}
    5870
     
    6072  height: 40px;
    6173  width: 100%;
     74  max-width: 350px;
     75  margin-top: 1.5rem;
    6276  background-color: #258de6;
    6377  color: white;
     
    6579  letter-spacing: 1px;
    6680  border: none;
    67   display: block;
    68   margin: 0 auto;
    69   font-weight: bold;
    70   margin-top: 1.5rem;
     81  font-weight: bold;
    7182  border-radius: 8px;
     83  font-size: 1rem; /* Increase font size for better readability */
     84}
     85
     86.socialButtons {
     87  display: flex;
     88  flex-direction: column; /* Stack social buttons vertically */
     89  align-items: center; /* Center them horizontally */
     90}
     91
     92.socialButton {
     93  display: inline-flex; /* Align text and image inline */
     94  align-items: center; /* Vertically center content */
     95  justify-content: flex-start; /* Align items to the left */
     96  padding: 10px 15px;
     97  border: none;
     98  border-radius: 8px;
     99  cursor: pointer;
     100  font-size: 0.8rem;
     101  font-weight: bold;
     102  transition: background-color 0.2s, transform 0.2s;
     103  width: 100%;
     104  max-width: 350px; /* Limit max width */
     105}
     106
     107.socialButton img {
     108  width: 20px;
     109  height: 20px;
     110  margin-right: 10px;
     111  vertical-align: middle;
     112}
     113
     114.socialButton:hover {
     115  filter: brightness(95%);
     116}
     117
     118.socialButton:active {
     119  transform: scale(0.98);
     120}
     121
     122.socialButton:nth-child(2) {
     123  background-color: #333; /* GitHub button */
     124  color: white;
     125}
     126
     127.socialButton:nth-child(1) {
     128  background-color: #ffffff; /* Google button */
     129  color: #000000;
     130  box-shadow: 1px 2px #2f2525;
    72131}
    73132
    74133@media (min-width: 542px) {
    75134  body {
    76     display: -webkit-box;
    77     display: -ms-flexbox;
    78135    display: flex;
    79     -webkit-box-pack: center;
    80     -ms-flex-pack: center;
    81136    justify-content: center;
    82137  }
     138
    83139  .wrapper {
    84     display: -webkit-box;
    85     display: -ms-flexbox;
    86140    display: flex;
    87141    height: 100vh;
    88     -webkit-box-align: center;
    89     -ms-flex-align: center;
    90142    align-items: center;
    91     -ms-flex-pack: distribute;
    92     justify-content: space-around;
    93     padding: 1.5rem;
     143    justify-content: flex-end; /* Keep the form to the right */
     144    padding: 2rem;
    94145    max-width: 1100px;
    95146  }
     147
    96148  .form {
    97     -ms-flex-preferred-size: auto;
    98149    flex-basis: auto;
    99   }
     150    align-items: center; /* Center form in larger screens */
     151    width: 100%;
     152    max-width: 400px; /* Keep form max width consistent */
     153  }
     154
    100155  .form input {
    101     width: 250px;
    102   }
     156    width: 100%; /* Ensures the input expands fully */
     157    max-width: 350px;
     158  }
     159
     160  .illustration{
     161    margin-right: 40%;
     162  }
     163
    103164  .illustration img {
    104     max-width: 80%;
     165    max-width: 100%;
    105166    height: auto;
    106167  }
     
    108169
    109170@media (max-width: 680px) {
     171  .wrapper {
     172    flex-direction: column; /* Stack form and illustration vertically */
     173    align-items: center; /* Center content horizontally */
     174    padding: 1rem;
     175  }
     176
    110177  .illustration {
    111     display: none;
    112   }
    113 }
    114 
    115 .signUp .illustration {
    116   order: 2;
    117   justify-self: flex-end;
    118   margin-left: 2rem;
     178    display: none; /* Hide the illustration on mobile */
     179  }
     180
     181  .form {
     182    width: 100%; /* Full width for mobile */
     183    align-items: center; /* Center form elements horizontally */
     184  }
     185
     186  .form input,
     187  .form button,
     188  .socialButton {
     189    width: 100%; /* Full width for mobile */
     190    max-width: 300px; /* Optional: Limit max width */
     191  }
    119192}
    120193
     
    126199  transform: scale(0.98);
    127200}
     201
     202.error {
     203  color: red;
     204  font-size: 0.9rem;
     205  margin-top: 0.5rem;
     206}
  • imaps-frontend/src/pages/MapView/MapView.jsx

    r0c6b92a r79a0317  
    1414import ShapeRegistry from "../../scripts/util/ShapeRegistry.js";
    1515import {useAppContext} from "../../components/AppContext/AppContext.jsx";
     16import Report from "../../components/Report/Report.jsx";
     17import {Button} from "../IMaps/components/Button.jsx";
    1618
    1719const MapView = ({isPrivate}) => {
    1820    const {mapName} = useParams();
    1921    const {username} = useAppContext();
     22    const {isAuthenticated} = useAppContext();
    2023
    2124    const [mapLoaded, setMapLoaded] = useState(false);
     
    2831    const [searchParams, setSearchParams] = useSearchParams();
    2932    const [mainEntrance, setMainEntrance] = useState({});
     33    const [canDisplayNavDownload,setCanDisplayNavDownload] = useState(false);
     34    const [from,setFrom] = useState("");
     35    const [to,setTo] = useState("");
    3036
    3137    const defaultNavObj = {
     
    115121                setFloors(respFloors);
    116122
     123                console.log("FLOOR DATA: " + tlFloor?.mapData)
     124
    117125                appInstance.loadMapN(tlFloor?.mapData)
    118126                setApp(appInstance);
     
    163171        const toEncoded = encodeURIComponent(toSearch).trimEnd()
    164172
     173        setFrom(fromSearch);
     174        setTo(toSearch);
     175
    165176        httpService.get(`${config.view_maps.navigate}?from=${fromEncoded}&to=${toEncoded}`).then(path => {
    166177            app.drawRouteNEW(path);
     178
     179
    167180        }).catch(reason => {
    168181            console.log("err", reason)
     
    185198    }
    186199
     200    useEffect(() => {
     201        const handleNavigateEnd = (event) => {
     202            console.log("DETAIL END",event.detail)
     203            setCanDisplayNavDownload(true);
     204        }
     205
     206        window.addEventListener("navend",handleNavigateEnd)
     207        return () => {
     208            window.removeEventListener("navend",handleNavigateEnd)
     209        }
     210    }, [app]);
     211
    187212
    188213    useEffect(() => {
     
    216241        app.loadMapN(chFloor.mapData)
    217242        app.floorNum = floorNum;
    218 
    219243
    220244        console.log(`Floor changed to: ${floorNum}`);
     
    257281                                handleFloorChange={handleFloorChange}
    258282                            />
     283                            {canDisplayNavDownload &&
     284                                (<div className={styles.downloadRouteButton}>
     285                                    <button onClick={() => {
     286                                        app.getRouteImages({
     287                                            mapName: mapName,
     288                                            from: from,
     289                                            to: to
     290                                        })
     291                                        setCanDisplayNavDownload(false)
     292                                    }}> Download Route</button>
     293                                </div>) }
    259294                            <FilterBar map={app} roomTypes={roomTypes}/>
    260295                        </div>
    261296                    )}
     297
     298                    {isAuthenticated && <Report mapName = {mapName}></Report>}
    262299                    <div className={styles.profileContainer}>
    263300                        <Profile position="relative"/>
     
    265302                </div>
    266303            </div>
     304            <div id="temp"></div>
    267305
    268306            <div className={styles.floorSelectorContainer}>
     
    283321            </div>
    284322
     323
    285324        </div>
    286325    );
  • imaps-frontend/src/pages/MyMaps/CreateMaps.module.css

    r0c6b92a r79a0317  
    5555  margin-bottom: 30px;
    5656  text-align: center;
     57  margin-top: 5em;
     58
    5759}
    5860
    5961.searchBar input {
    60   width: 300px;
     62  /*width: 300px;*/
     63  width: 33vw;
    6164  padding: 10px;
    6265  font-size: 16px;
     
    8386  display: flex;
    8487  flex-direction: column;
    85   align-items: center; /* Center items horizontally */
    86   justify-content: flex-start; /* Ensure tiles stack from the top */
     88  align-items: center;
     89  justify-content: flex-start;
    8790  padding: 10px;
     91  /*padding-left: 2.5em;*/
    8892  background-color: #f9f9f9;
    8993  border-radius: 8px;
    9094  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    91 }
     95
     96}
     97
    9298
    9399.tile {
     
    127133}
    128134
     135.plusButton {
     136  width: 50px;
     137  height: 50px;
     138  border: none;
     139  cursor: pointer;
     140  display: flex;
     141  align-items: center;
     142  justify-content: center;
     143  transition: transform 0.3s ease-in-out;
     144  background: #4ebc3b;
     145  margin-right: 20px;
     146}
     147
     148.plusButton img {
     149  width: 50px;
     150  height: auto;
     151  margin-left: 0;
     152}
     153
     154.plusButton:hover {
     155  transform: scale(1.1);
     156  background: #46be2f;
     157}
     158
     159.header {
     160  display: flex;
     161  align-items: center;
     162  justify-content: space-between;
     163  background-color: #f8f9fa; /* Light grey background for a clean look */
     164  border-bottom: 1px solid #ddd; /* Subtle separation from the rest of the page */
     165  padding: 10px 20px; /* Add some space around the content */
     166  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* Light shadow for subtle elevation */
     167}
     168
     169.header .logo-wrapper,
     170.header .profile-wrapper {
     171  display: flex;
     172  align-items: center;
     173}
     174
     175.header h1 {
     176  font-size: 1.5rem; /* Slightly larger font size */
     177  color: #333; /* Dark grey for a professional tone */
     178  margin: 0 auto; /* Center-align the title */
     179  font-weight: 600; /* Semi-bold text for emphasis */
     180  text-align: center;
     181  flex-grow: 1; /* Allow it to take space in the middle */
     182}
     183
     184.logo-wrapper {
     185  margin-right: auto; /* Push logo to the far left */
     186}
     187
     188.profile-wrapper {
     189  margin-left: auto; /* Push profile to the far right */
     190}
     191
     192
     193
     194
    129195@media (max-width: 768px) {
    130196  .mapsContainer {
  • imaps-frontend/src/pages/MyMaps/MyMaps.jsx

    r0c6b92a r79a0317  
    1010import {useAppContext} from "../../components/AppContext/AppContext.jsx";
    1111import config from "../../scripts/net/netconfig.js";
    12 import {element} from "prop-types";
    1312import Toast from "../../components/Toast/Toast.jsx";
     13import plus_icon from "../../assets/plus_icon.png";
    1414
    1515const renderTile = ({data, isDragging}, openMapInfo) => (
     
    6565        setToastMessage(message);
    6666        setToastType(type);
    67         setTimeout(() => setToastMessage(null), 3000); // Automatically hide the toast after 3 seconds
    68     };
    69 
     67        setTimeout(() => setToastMessage(null), 3000);
     68    };
    7069
    7170    const handleUpdate = async (updatedMap) => {
    72         // Placeholder for map update logic
     71        try {
     72            let httpService = new HttpService(true);
     73            const response = await httpService.post(
     74                config.my_maps.edit_map_info,
     75                updatedMap
     76            );
     77            closeMapInfoModal()
     78            window.location.reload();
     79
     80        } catch (error) {
     81            showToast("Map Name already taken", 0)
     82            closeMapInfoModal()
     83        }
    7384    };
    7485
     
    8394                setTiles((prevTiles) => prevTiles.filter((tile) => tile.mapName !== mapName));
    8495                setAllTiles((prevTiles) => prevTiles.filter((tile) => tile.mapName !== mapName));
    85                 showToast("Map deleted", 1)
     96                showToast("Map deleted", 1);
    8697            })
    8798            .catch((error) => {
    8899                const errorMessage = error.response?.data?.error || error.message || "Unknown error";
    89                 // alert(`Error deleting the map: ${errorMessage}`);
    90                 showToast(`Error deleting the map: ${errorMessage}`, 0)
     100                showToast(`Error deleting the map: ${errorMessage}`, 0);
    91101            });
    92102    };
     
    97107
    98108        httpService
    99             .put(`${config.my_maps.add}?username=${username}`, mapDetails)
     109            .put(`${config.my_maps.add}?username=${encodeURI(username)}`, mapDetails)
    100110            .then((respMap) => {
    101                 console.log("RESP NEW MAP: " + respMap)
    102111                const mapTile = {
    103112                    mapName: respMap.mapName,
     
    108117                    modified_at: respMap.modifiedAt,
    109118                    published_at: respMap.published_at,
    110                     gmaps_url: respMap.gmaps_url,
     119                    gmaps_url: respMap.gmapsUrl,
    111120                    image_url: card,
    112121                    is_published: respMap.is_published,
     122                    mapType: respMap.mapType
    113123                };
    114124
     
    116126                setTiles((prevTiles) => [...prevTiles, mapTile]);
    117127                showToast("Map added successfully!");
    118 
    119 
    120128            })
    121129            .catch((error) => {
    122                 showToast("Map name already taken", 0)
     130                showToast("Map name already taken", 0);
    123131            });
    124132    };
     
    129137            httpService.setAuthenticated();
    130138
    131             const respMaps = await httpService.get(`${config.my_maps.display}?username=${username}`);
     139            const respMaps = await httpService.get(
     140                `${config.my_maps.display}?username=${encodeURI(username)}`
     141            );
    132142
    133143            const mapTiles = respMaps.map((elem) => ({
     
    139149                modified_at: elem.modifiedAt,
    140150                published_at: elem.published_at,
    141                 gmaps_url: elem.gMapsUrl,
     151                gmaps_url: elem.gmapsUrl,
    142152                image_url: card,
    143153                numFavourites: elem.numFavourites,
    144154            }));
    145 
    146155
    147156            setTiles(mapTiles);
     
    154163        setPublicMaps(tiles.filter((tile) => tile.status === "PUBLIC"));
    155164        setPrivateMaps(tiles.filter((tile) => tile.status === "PRIVATE"));
    156         setPendingMaps(tiles.filter((tile) => tile.status === "INVALID"));
     165        setPendingMaps(tiles.filter((tile) => tile.status === "PENDING"));
    157166    }, [tiles]);
    158167
     
    164173    return (
    165174        <div className={styles.container}>
    166             <Logo/>
    167             <Profile/>
     175
     176            <div className={`${styles.logoWrapper}`}>
     177                <Logo/>
     178            </div>
    168179            <h1>Your Maps</h1>
    169 
    170             <div className={styles.actionButtons}>
    171                 <button className={styles.createMapsButton} onClick={openCreateModal}>
    172                     Create Map
    173                 </button>
    174             </div>
     180            <div className={`${styles.profileWrapper}`}>
     181                <Profile/>
     182            </div>
     183
    175184
    176185            <div className={styles.searchBar}>
    177                 <input type="text" placeholder="Search for maps..." onChange={handleSearch}/>
     186                <input
     187                    type="text"
     188                    placeholder="Search for maps..."
     189                    onChange={handleSearch}
     190                />
    178191            </div>
    179192
     
    201214                        forceTileHeight={170}
    202215                    />
     216                    <button className={styles.plusButton} onClick={openCreateModal}>
     217                        <img src={plus_icon} alt="Add Map"/>
     218                    </button>
    203219                </div>
    204220
     
    222238                onDelete={deleteMap}
    223239                onUpdate={handleUpdate}
    224                 onPublish={() => {
     240                onPublish={(updatedMap = null) => {
     241
     242                    const updatedTile = {
     243                        mapName: updatedMap.mapName,
     244                        cols: 1,
     245                        rows: 1,
     246                        status: updatedMap.mapStatus,
     247                        created_at: updatedMap.createdAt,
     248                        modified_at: updatedMap.modifiedAt,
     249                        published_at: updatedMap.published_at,
     250                        gmaps_url: updatedMap.gMapsUrl,
     251                        image_url: card,
     252                        numFavourites: updatedMap.numFavourites,
     253                    }
    225254                    showToast(`Map ${selectedMap.mapName} published successfully!`);
    226255                    setPrivateMaps((prevMaps) => prevMaps.filter(m => m.mapName !== selectedMap.mapName))
    227                     setPendingMaps((prevMaps) => [...prevMaps,allTiles.find(m => m.mapName = selectedMap.mapName)])
     256                    setPendingMaps((prevMaps) => [...prevMaps, updatedTile])
    228257                    closeMapInfoModal()
    229258                }}
     
    236265            />
    237266
    238             {toastMessage && <Toast message={toastMessage} type={toastType} onClose={() => setToastMessage(null)}/>}
     267            {toastMessage && (
     268                <Toast
     269                    message={toastMessage}
     270                    type={toastType}
     271                    onClose={() => setToastMessage(null)}
     272                />
     273            )}
    239274        </div>
    240275    );
  • imaps-frontend/src/pages/Signup/Signup.jsx

    r0c6b92a r79a0317  
    44import styles from "./Signup.module.css";
    55import Logo from "../../components/Logo/Logo";
     6import netconfig from "../../scripts/net/netconfig.js";
    67
    78export default function Signup() {
     
    910  const [email, setEmail] = useState("");
    1011  const [password, setPassword] = useState("");
     12  const [confirmPassword, setConfirmPassword] = useState("");
    1113  const [message, setMessage] = useState("");
    12   const [messageType, setMessageType] = useState(""); // New state to manage message type
     14  const [messageType, setMessageType] = useState("");
    1315  const navigate = useNavigate();
    1416
    1517  const handleSubmit = async (e) => {
    1618    e.preventDefault();
     19
     20    if (password !== confirmPassword) {
     21      setMessageType("error");
     22      setMessage("Passwords do not match.");
     23      return;
     24    }
    1725
    1826    const payload = {
     
    2331
    2432    try {
    25       const response = await fetch("http://localhost:8080/api/auth/register", {
     33      const response = await fetch(netconfig.auth.register, {
    2634        method: "POST",
    2735        headers: {
     
    3543        setMessage("User registered successfully!");
    3644
    37         // Wait 3 seconds and then redirect to login page
    3845        setTimeout(() => {
    3946          navigate("/login");
    40         }, 3000);
     47        }, 1000);
    4148      } else if (response.status === 409) {
    4249        setMessageType("error");
     
    5461
    5562  return (
    56     <div className={styles.wrapper}>
    57       <Logo></Logo>
    58       <div className={styles.illustration}>
    59         <img src={illustration} alt="illustration" />
     63      <div className={styles.wrapper}>
     64        <Logo></Logo>
     65        <div className={styles.illustration}>
     66          <img src={illustration} alt="illustration" />
     67        </div>
     68        <div className={styles.form}>
     69          <div className={styles.heading}>CREATE AN ACCOUNT</div>
     70          <form onSubmit={handleSubmit}>
     71            <div>
     72              <label htmlFor="name">Username</label>
     73              <input
     74                  type="text"
     75                  id="name"
     76                  value={name}
     77                  onChange={(e) => setName(e.target.value)}
     78                  placeholder="Enter your username"
     79                  required
     80              />
     81            </div>
     82            <div>
     83              <label htmlFor="email">E-Mail</label>
     84              <input
     85                  type="email"
     86                  id="email"
     87                  value={email}
     88                  onChange={(e) => setEmail(e.target.value)}
     89                  placeholder="Enter your email"
     90                  required
     91              />
     92            </div>
     93            <div>
     94              <label htmlFor="password">Password</label>
     95              <input
     96                  type="password"
     97                  id="password"
     98                  value={password}
     99                  onChange={(e) => setPassword(e.target.value)}
     100                  placeholder="Enter your password"
     101                  required
     102              />
     103            </div>
     104            <div>
     105              <label htmlFor="confirmPassword">Confirm Password</label>
     106              <input
     107                  type="password"
     108                  id="confirmPassword"
     109                  value={confirmPassword}
     110                  onChange={(e) => setConfirmPassword(e.target.value)}
     111                  placeholder="Confirm your password"
     112                  required
     113              />
     114            </div>
     115            <button type="submit">Submit</button>
     116            {message && (
     117                <p className={messageType === "success" ? styles.successMessage : styles.errorMessage}>
     118                  {message}
     119                </p>
     120            )}
     121          </form>
     122          <p>
     123            Have an account? <Link to="/Login"> Login </Link>
     124          </p>
     125        </div>
    60126      </div>
    61       <div className={styles.form}>
    62         <div className={styles.heading}>CREATE AN ACCOUNT</div>
    63         <form onSubmit={handleSubmit}>
    64           <div>
    65             <label htmlFor="name">Username</label>
    66             <input
    67               type="text"
    68               id="name"
    69               value={name}
    70               onChange={(e) => setName(e.target.value)}
    71               placeholder="Enter your username"
    72               required
    73             />
    74           </div>
    75           <div>
    76             <label htmlFor="email">E-Mail</label>
    77             <input
    78               type="email"
    79               id="email"
    80               value={email}
    81               onChange={(e) => setEmail(e.target.value)}
    82               placeholder="Enter your email"
    83               required
    84             />
    85           </div>
    86           <div>
    87             <label htmlFor="password">Password</label>
    88             <input
    89               type="password"
    90               id="password"
    91               value={password}
    92               onChange={(e) => setPassword(e.target.value)}
    93               placeholder="Enter your password"
    94               required
    95             />
    96           </div>
    97           <button type="submit">Submit</button>
    98 
    99           {/* Display message with appropriate styling */}
    100           {message && (
    101             <p className={messageType === "success" ? styles.successMessage : styles.errorMessage}>
    102               {message}
    103             </p>
    104           )}
    105           <h2 align="center" className={styles.or}>
    106             OR
    107           </h2>
    108         </form>
    109         <p>
    110           Have an account? <Link to="/Login"> Login </Link>
    111         </p>
    112       </div>
    113     </div>
    114127  );
    115128}
  • imaps-frontend/src/pages/Signup/Signup.module.css

    r0c6b92a r79a0317  
    1 * {
    2   margin: 0;
    3   padding: 0;
    4   -webkit-box-sizing: border-box;
    5   box-sizing: border-box;
    6   font-family: sans-serif;
    7 }
    8 
    9 .illustration img {
    10   max-height: 500px;
    11   width: auto;
    12 }
    13 
    141.wrapper {
    15   display: -webkit-box;
    16   display: -ms-flexbox;
    172  display: flex;
    18   height: 100vh;
    19   -webkit-box-align: center;
    20   -ms-flex-align: center;
    21   align-items: center;
    22 }
    23 
    24 .wrapper p {
    25   font-size: 0.85rem;
    26   margin-top: 1rem;
     3  align-items: center; /* Vertically center content */
     4  justify-content: center; /* Horizontally center content */
     5  height: 100vh; /* Full viewport height */
     6  background-color: #ffffff; /* Light background */
     7  padding: 1rem;
     8  gap: 2rem; /* Space between form and image */
    279}
    2810
    2911.form {
    30   padding: 1.5rem;
    31   -ms-flex-preferred-size: 100vw;
    32   flex-basis: 100vw;
     12  padding: 2rem;
     13  background-color: white;
     14  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
     15  border-radius: 10px;
     16  flex-basis: 100%;
     17  max-width: 400px; /* Restrict form width */
     18  text-align: center; /* Center text inside the form */
    3319}
    3420
    3521.form .heading {
    36   font-size: 1.5rem;
     22  font-size: 1.8rem;
    3723  font-weight: bold;
    38   text-align: center;
    39 }
    40 
    41 .or {
    42   margin: 1rem 0;
     24  color: #258de6;
     25  margin-bottom: 1rem;
    4326}
    4427
    4528.form label {
    4629  display: block;
    47   margin: 1.25rem 0 1rem 0;
     30  margin: 1.25rem 0 0.5rem 0;
     31  font-size: 0.9rem;
     32  font-weight: bold;
     33  color: #555;
    4834}
    4935
     
    5137  height: 40px;
    5238  width: 100%;
    53   padding: 15px;
     39  padding: 10px 15px;
    5440  background-color: #f1f9ff;
    5541  border: 2px solid #bce0fd;
    5642  border-radius: 8px;
     43  font-size: 1rem;
     44}
     45
     46.form input:focus {
     47  outline: none;
     48  border-color: #258de6;
     49  background-color: #e6f4ff;
    5750}
    5851
    5952.form button {
    60   height: 40px;
     53  height: 45px;
    6154  width: 100%;
    6255  background-color: #258de6;
     
    6558  letter-spacing: 1px;
    6659  border: none;
     60  margin-top: 1.5rem;
     61  font-weight: bold;
     62  border-radius: 8px;
     63  cursor: pointer;
     64  transition: all 0.2s ease;
     65}
     66
     67.form button:hover {
     68  filter: brightness(90%);
     69}
     70
     71.form button:active {
     72  transform: scale(0.96);
     73}
     74
     75.form p {
     76  text-align: center;
     77  margin-top: 1rem;
     78  font-size: 0.9rem;
     79}
     80
     81.form p a {
     82  color: #258de6;
     83  text-decoration: none;
     84  font-weight: bold;
     85}
     86
     87.form p a:hover {
     88  text-decoration: underline;
     89}
     90
     91.illustration {
     92  text-align: center; /* Center image container */
     93}
     94
     95.illustration img {
     96  max-width: 100%;
     97  height: auto;
     98  border-radius: 10px;
    6799  display: block;
    68   margin: 0 auto;
     100  margin: 0 auto; /* Center the image */
     101}
     102
     103@media (min-width: 768px) {
     104  .wrapper {
     105    flex-direction: row;
     106    gap: 4rem; /* Space between form and image */
     107  }
     108
     109  .form {
     110    flex-basis: 50%;
     111  }
     112
     113  .illustration {
     114    flex-basis: 50%;
     115  }
     116
     117  .illustration img {
     118    max-height: 600px;
     119    width: auto;
     120  }
     121}
     122
     123@media (max-width: 768px) {
     124  .wrapper {
     125    flex-direction: column;
     126    text-align: center; /* Center all content in column layout */
     127  }
     128
     129  .illustration {
     130    margin-bottom: 2rem; /* Space between image and form */
     131  }
     132}
     133
     134.errorMessage,
     135.successMessage {
     136  font-size: 0.9rem;
    69137  font-weight: bold;
    70   margin-top: 1.5rem;
     138  text-align: center;
     139  margin-top: 1rem;
     140  padding: 10px;
    71141  border-radius: 8px;
    72142}
    73143
    74 @media (min-width: 542px) {
    75   body {
    76     display: -webkit-box;
    77     display: -ms-flexbox;
    78     display: flex;
    79     -webkit-box-pack: center;
    80     -ms-flex-pack: center;
    81     justify-content: center;
    82   }
    83   .wrapper {
    84     display: -webkit-box;
    85     display: -ms-flexbox;
    86     display: flex;
    87     height: 100vh;
    88     -webkit-box-align: center;
    89     -ms-flex-align: center;
    90     align-items: center;
    91     -ms-flex-pack: distribute;
    92     justify-content: space-around;
    93     padding: 1.5rem;
    94     max-width: 1100px;
    95   }
    96   .form {
    97     -ms-flex-preferred-size: auto;
    98     flex-basis: auto;
    99   }
    100   .form input {
    101     width: 250px;
    102   }
    103   .illustration img {
    104     max-width: 80%;
    105     height: auto;
    106   }
     144.errorMessage {
     145  color: #e74c3c;
     146  background-color: #fbeaea;
     147  border: 1px solid #e74c3c;
    107148}
    108149
    109 @media (max-width: 680px) {
    110   .illustration {
    111     display: none;
    112   }
     150.successMessage {
     151  color: #2ecc71;
     152  background-color: #ebf9f1;
     153  border: 1px solid #2ecc71;
    113154}
    114 
    115 .signUp .illustration {
    116   order: 2;
    117   justify-self: flex-end;
    118   margin-left: 2rem;
    119 }
    120 
    121 button:hover {
    122   filter: brightness(95%);
    123 }
    124 
    125 button:active {
    126   transform: scale(0.98);
    127 }
    128 
    129 /* Error message style */
    130 .errorMessage {
    131   color: #e74c3c; /* Red color for error */
    132   background-color: #fbeaea;
    133   padding: 10px;
    134   border: 1px solid #e74c3c;
    135   border-radius: 5px;
    136   margin-top: 1rem;
    137   text-align: center;
    138   font-size: 0.9rem;
    139   font-weight: bold;
    140 }
    141 
    142 /* Success message style */
    143 .successMessage {
    144   color: #2ecc71; /* Green color for success */
    145   background-color: #ebf9f1;
    146   padding: 10px;
    147   border: 1px solid #2ecc71;
    148   border-radius: 5px;
    149   margin-top: 1rem;
    150   text-align: center;
    151   font-size: 0.9rem;
    152   font-weight: bold;
    153 }
  • imaps-frontend/src/scripts/base/MapShape.js

    r0c6b92a r79a0317  
    77        }
    88
    9         console.log(config.x,"FFF");
    109        super(config);
    1110        this.layer = layer;
     
    6463            text: this._info.name,
    6564            fontSize: 12,
    66             fontFamily: 'Verdana',
     65            fontFamily: 'Exo',
    6766            fill: 'white',
    6867        });
     
    134133    }
    135134
     135    onPlace(){
     136        //this.snapToGrid();
     137    }
     138
    136139
    137140    get info() {
  • imaps-frontend/src/scripts/main/MapBuilder.js

    r0c6b92a r79a0317  
    2121
    2222        // TODO AKO DRAGNIT NEKOJ OD POCETOK NA STAGE POZICIIVE KE SA ZEZNAT
    23         // TODO jwt vo cookie
    2423        // TODO placed shape i mouseMoveHandler da ne callback ( da ne vrakjat funkcija)
    2524        // TODO text na top layer sekogas
     
    3534        this.textLayer = new Konva.Layer();
    3635        this.gridLayer.listening(false);
     36
     37        this.isDrawEventBound = false;
    3738
    3839
     
    9697    setupEventListeners() {
    9798        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));
     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));
    102103        window.addEventListener("resize", this.handleResize.bind(this));
    103104
     
    107108        this.boundEfficientDrawingModeEventListener = this.toggleEfficientDrawingMode.bind(this);
    108109
    109         //this.attachKeyPressEventListeners();
     110        this.attachKeyPressEventListeners();
    110111
    111112        this.stage.on("mousedown touchstart", this.handleMouseDown.bind(this));
     
    119120
    120121    detachKeyPressEventListeners() {
     122        console.log("DETACH")
    121123        window.removeEventListener("keydown", this.boundEscapeEventListener);
    122124        window.removeEventListener("keydown", this.boundDeleteEventListener);
     
    126128
    127129    attachKeyPressEventListeners() {
     130        console.log("ATTACH")
    128131        window.addEventListener("keydown", this.boundEscapeEventListener);
    129132        window.addEventListener("keydown", this.boundDeleteEventListener);
     
    269272        let infoPin = Factory.createShape("InfoPin", attrs);
    270273        addEventHandling(infoPin, this, "dblclick");
    271         //this.shapes.push(infoPin);
    272274        ShapeRegistry.add(infoPin)
    273275        this.mainLayer.add(infoPin);
     
    307309
    308310        const placedObj = Factory.createShape(this.hoverObj.type, attrs);
     311
    309312        if (!placedObj) return;
    310313
     
    312315
    313316        this.mainLayer.add(placedObj);
    314         //this.shapes.push(placedObj);
    315         console.log("VO PLACED SHAEPS WALL ZITI SE: " + placedObj.className);
    316317        ShapeRegistry.add(placedObj);
    317318        addEventHandling(placedObj, this, "dblclick");
    318319        this.mainLayer.draw();
    319 
    320320        // site ovie func da se vo edna funkcija vo shape.
    321321
    322322        placedObj.displayName(this.textLayer);
    323323        placedObj.snapToGrid();
     324        placedObj.onPlace();
    324325
    325326        triggerMapSave();
     
    328329            this.stopDrawing();
    329330        }
     331        this.mainTransformer.nodes([])
    330332    }
    331333
    332334    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);
     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
    338344    }
    339345
     
    365371        this.dragLayer.add(this.hoverObj);
    366372        this.dragLayer.moveToTop();
     373
     374
    367375        this.boundMouseMoveHandler = this.mouseMoveHandler.bind(this);
    368376        this.boundPlaceShapeHandler = this.placeShape.bind(this);
     377        this.isDrawEventBound = true;
    369378
    370379        this.stage.on("mousemove", this.boundMouseMoveHandler);
     
    374383    selectShape(e) {
    375384        if (e.target.tagName === "LI") {
     385          this.stopDrawing()
    376386            const shapeType = e.target.getAttribute("data-info");
    377387            this.startDrawing(shapeType);
    378             this.mainTransformer.nodes([]);
     388            this.mainTransformer.nodes([])
    379389        }
    380390    }
     
    464474
    465475    saveShapeDetails() {
    466         // this.shapes.forEach(shape => {
    467         //     shape.saveShapeDetails();
    468         //     console.log(shape.info);
    469         // });
    470476        ShapeRegistry.saveDetails();
    471477        console.log("thisflornum",this.floorNum)
     
    478484    }
    479485
    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     }
    489486
    490487
    491488    handleStageClick(e) {
    492489        if (this.selectionRectangle.visible()) {
     490            console.log("STAGECLICK1")
    493491            return;
    494492        }
    495493
    496494        if (e.target === this.stage) {
     495            console.log("STAGECLICK2")
    497496            this.mainTransformer.nodes([]);
    498497            return;
     
    500499
    501500        if (!e.target.hasName("mapObj")) {
     501            console.log("STAGECLICK3")
    502502            return;
    503503        }
     
    505505        const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey;
    506506        const isSelected = this.mainTransformer.nodes().indexOf(e.target) >= 0;
     507
    507508
    508509        if (!metaPressed && !isSelected) {
     
    527528    }
    528529
    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 
    551530
    552531    drawConnection(node1Name, node2Name) {
    553532
    554533        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];
    559534    }
    560535
     
    571546    }
    572547
    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     }
    585548
    586549    clearMap() {
     
    653616        this.mainLayer.add(this.selectionRectangle);
    654617
    655         ShapeRegistry.getShapes(this.floorNum).forEach((shape) => shape.displayName(this.textLayer));
     618        ShapeRegistry.getShapes(this.floorNum).forEach((shape) => {
     619            shape.displayName(this.textLayer);
     620            if(shape.className === "Entrance"){
     621                shape.setHighlight();
     622            }
     623        });
    656624    }
    657625
  • imaps-frontend/src/scripts/main/MapDisplay.js

    r0c6b92a r79a0317  
    44import {zoomStage} from "../util/zoomStage.js";
    55import {addEventHandling} from "../util/addEventHandling.js";
     6import ShapeRegistry from "../util/ShapeRegistry.js";
    67import triggerNavigate from "../util/triggerNavigate.js";
    78import config from "../net/netconfig.js";
     9import {dispatchCustomEvent} from "../util/dispatchCustomEvent.js";
     10import { jsPDF } from "jspdf";
     11
    812
    913export class MapDisplay {
     
    1822        });
    1923
     24        this.selectedRoom = {
     25            id: 1,
     26            name: "223",
     27            type: "Laboratory",
     28            image:
     29                "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMSEhUTExIVFhUXGBsYGBcWFxgXFhcYFRgXFxUVFRYYHiggGBolGxUXITEhJSkrLi4uFx8zODMtNygtLisBCgoKDg0OGxAQGysmHSUtLS0rLS0tLS0tLS0tLS0rKy0tKy0rLi0tLS0tKy4tLSstLS0vLS0tLS0rLS0tKy0tLf/AABEIAMIBAwMBIgACEQEDEQH/xAAcAAACAgMBAQAAAAAAAAAAAAAFBgMEAAECBwj/xABOEAACAQIDBAcEBwQHBAkFAAABAhEAAwQSIQUGMUETIlFhcYGRMqGxwQcUI1JyktEzQmLwQ3OCorLC4RUWJFM0RIOTs8PS4vEXVGNko//EABsBAAMBAQEBAQAAAAAAAAAAAAECAwAEBQYH/8QAMREAAgIBAwIDBgYCAwAAAAAAAAECEQMSITEEQVFhgQUTIjJx0UKhscHh8BSRFdLx/9oADAMBAAIRAxEAPwB4UV1FaFbpxDIrUVusrBOCK4YVIa4Vg3ODMQQRWMQOKgdau3LRqvcWtRijcWql1KIXBVW4tAwMupVO6lFLi1TurSsIJvJqKy1YAJIGp4+Qj4Ct7RuFYIUt3CPnQ25t4ISGRhrHL5UAlbeUENbgkGCNDB4ipcDJUTroKhxuKt4iD1lKzB059oq5hSoA6w4eHuosxZRanVKjS4v3l9RUq30++vqKUx2qVIqVGMTb++v5hUiYlPvisYkCV2ErauO/0P6VIGHf+U/pWMR5KwpUvSLIGsnQSCJ0nmK7KVglUpUbLVpkqJlrGKzLUbLVllqJhWMVWWonFWXFQuKxiO3gDcnrKq/eaYnsECSatYTY6qZN9D4K/wClD8SxEQTx7asbOk8ST41jBhbNv/mj8rVlQ7Ow7LbUPBfXMRwkknSfGspgHoQrdcit0wpusrVaomMNR43aa22VYJlHaQRxt5OpHac/uqSaVbr27lwg38hzPqVkdbioIiDIXjxiqY0m9wPjYJ7V2kF6N3FxShdmRTPsKCQ0aMsMD5+NXHtj9p98DTwH+tD8bszprIm6GckEOBxWBbI75HGrODuHKUJBymFMzKwCJ8CSPKjNLegp3FXyY4qtcFWnqvcFRMU7i1TvCr9wVUuigYEe0ygjn/PGlvelLVp1BDZrkkERAIIGs+NOCp1h40B3w2ZcuPZKBSq5s85Z4qdJ7poUEBYS3pNFtl2QV4cz8TXHRZVireyR1fM/EmgEt27I7K1i0yqI01/WrCCo8eOqPH5VjFJSTMk8KMYPZ6FVbWSAePnQm2OPh8xTNg16i/hHwoIxtMOO+plwwqS0tTBaJis2FXQxw4VnQirRWuMtYxQxdsBT5fEVTuVf2jovmPjNeX7U3rxAu3FVlyq7AdWdASPlWow9MaiY159/vXifvL+UVwd6MT95fy0aMPztULmkb/ebEfeX8taO8d/tX8v+tCjDdieI86v7NXSlzY2Na8oLRIBmBA1OnuFNGzl0FYxcfFIpgnWsrdrCq4zEamT5T1fdFapgD2Kya5Brc0QG5rJrU1qsY2TXnNu+elvIetFxwJ4jrtlgzXok1wyjsFNF07CnsANnY4W7B6TTIdSx5mNR2jUkdmorvD4tWvLFwPmUkQCDliVzDxJ1oubK/dX0Fckd1M52A4eq71O9V7lIYguVVu1ZuVWuUGYpO0GRxFDsbZzozsoBzCOffRG7UGKH2J/EKVhAlwDn2f8AxVnZg6v89pqvfq1sz2f57TQMX0FQ4/gPGp0qHaHBaLMVbY0Pl8aaMOOqvgPhSujaHypssroPAVkZktsVMq1wgruaJjoioytd61yaBgftXgv4vgprwvEvmdz2sx9STXtu3LkCexXb0H+teGCsE6rK5zVmaiY2K2awVjVjDXusn2Z8qc8GIFK+7luLY7zTTaHVjt09dKUwRwqQi/hHwrKlC1lMAZwa3NcA1uiA6mtTWprU1gmya5JrRNVNo4gpauOvFUZhPCQCRNFK9gFomo2pMt7xbRZVdcGLiMoYECJnjEMdO/3VFc30xSANc2e4VvZaXCtEzlYpB4Hh2V1/4OZuklf1QkckJOk0/Uc3NV7hpQ/+oaj28LcHbDjTyKij+yNrW8UjsgIyRpmRvaMa5Tpw50mTpM2NapxpDlhteFQvab7p9KobzLOGcdrWhqY/6xa58vGtNuVhi6nLdAZ4OW6csFWYnMCRMiY8qOLp4zhqlKvS/wBxZNxV068ef03/ACJL9h/uN6Gq2KQi1GRiS6qANDLGBxpfv7KtnEdFbv31Jd4+0YjIhuKDoO1V56z36Hd2rbWsQts32ZWslybpLAEdFrqBIl29B2TXXL2ZoWpyvvVDU/79uQdfwl4Ak4TEgKATC22gMYBMP21FgscqyrWsQIksxtKFUAmSxz6AdvdRTaW/KhcVay5biRbtlQOjudG7S7+yVnTTXiaqYcudmZm6xe1fJJME9Z9YI15V0w9nQ03OFb0t33ViRcq3f9/2wiVgkVW2h+75/KrV32m8TVXH/u+fyrwJLcdFS2DrJEDgI11k6691ONsaUoqND4j4GjeD2i7GMq+poILDIrtRVNb7/dX8x/SuxiG+6Pzf+2iAs1waq2scWzdQiCRxGscx3VSxW1GynJbcNyLKCPODQMdbTshyVPAoQfBtPlSBc3LGuoVQGYksYCqCxJMHgAae9nO91hnEEkLwjmdYPiKEbW3gwgS6hN8Bhds5glpoPWts2XpJjjEgTVcWGWR1FBuhKv7tWlMHFWgewuQdRI0Kdhqvc3fXlicOf+1UfEa04jebZ5t9HJ9gKCcJanSZaQ8yZqLbu1dm4hy1spZHR5Aowp0OV1zSJk9dTP8AAPL0I9BDVTbrxr+CalPul/v+BTtbvEmBesE9gv2yfSpbu7N1DlPODplIIOo1FMGBGAmLdyWmdLV4sAOk4RbJGjp+Twg9j7IDKeRUQCGUjJ1CGVwCDKniKh1nTRw1pbfpRROwNs2yFAUcAT8aPWR7H4vkaD4ISaNYISx7hHmdT7orhCFAKytisomLlneLCtwv2/zCrdvaVluF1D4MK8mOw16JXDtJ6PQp1ftGZSQ3YI8zI5a7TdzWTcQQRAuLoevk7ToNSfCvafsnwkU90z2C3cDcCD4Gtmk36Obk9OuVBkISUBUNBY5iCeOseAFOyWGbgpPlXl58TxTcH2JtUyA1S2sPsLv9W3+E0XOEj2iq+cn0FU9q2l6G4AWYlCBAjiOzianHlAAG7D2BhrHSFk+yEMzW0Vj1dAzKZJ1I4mBy4VS3lxv/AAFglvs0fKIM+1buATl1Pj39mlLm28S1zA4ex0F5WtFJzWWHs22UmZ11PZQS5tS89oWLl5mtoRkt9XTKCBqdVABjXtr6fF0uqamnvfd9vI3xSlqaSaeyXH1b8/Db6jDsS90g6Ug5mdm/fkfaWxo41J1PafiprdBIu4wNPs2P8GnEcKXNiYy0lkK7KGDHq5wDq9vjI7FJ4/6mNgLbcY0dIAh6DrBgR7IkTw46UfaVrp5t/wB3ReW8Ugnt+9be0yL1iGtMyrLvlW/aLHKoJ0HdQXfDeS/n6LCZksghluLauWnDEQ8SF07dOZrrEX7eGP2KlyeJTSBxjMTB8BQzH7auuZVsXZPcrRz7xXg9J10cS3jq9Tmlg+NS32KG7uIC4vpcQ5EyWuXJkliASec8+NG7WNtu7G2ely4R5UBX1z2Bly6g8OBoBg9vYxLoFzF3FT+NZJnuy8aY9oW9o3bLN0oSyUljcCocoOaSVUsugnyrr6n2vGcWlCrVLf8AgdQbAWKxpCnLgwG5ThlAntOUDSrexr6Oh6TCoGAJfLbZRxMcGE6dk1Ds/YONv2y1m+lxNRK3WjhqOso7aL293sTbUM6KABqc68fM15EutzxWzf8AtlVhiwjh36QZ14NrwI9x1FR4+y2hjQTS7j9q9Hla3cXN1uBB1ykj3ii2CxT3bSPcbMxWewCewVzxyyfKM8SRInDz+VX9l+35H5UPTh5/KiOyfaPh8xVkRYYFbrkVuaYUiw/DxJ+NbeuMMeoK0+0LKkKWljOiwcscQdeNJOairZSMXJndi8ts9K5hLY6RjBOianQUD2Ftmxh7IQ49lVnLhvq7rIJZiEziDq8yZq5t/GWzg8UUOvRRwI9shfnQnY2xMHdwdtr1u4XECOlYHVQSyWi3skniNOVet7NhrxOd967P9SPUfBs8bn6WGsJvFg1a6xxlhzcVVBuW4IChpkcPaaaUvqWHKopv4IwVLHOgJyqwIkoCQWYHXs1mAKMYndDANcsJbzoLt0JJuqwC5WYyQTGi1BvhuPg8Pcw6WL1xuleDwuEKFJJQKNTIEePnXd004456YyacuzXh4JcehLE7VRxNLj5aKLbIsm0yo2GZiDDZ7JOY2raTmJkDMLjd0jnrRNsG4s2ERrZKWkUxcT2hJcce0xSg+wFmyFuMTctPcb7M6ZC+i/e0TU8AZ7K6XdkhWctBVLTqrIYfpmhRmOgWJOY6GIrq6rocnUKpSWz8PTxOje+B22bgm4Nay94ZW9IPdzHOrtmx0ZMnQmQToCIHv04Uq7tvlxd20EsnoRdYXEtC2zEDowJ4hTmnL20z7AxC31XMW00iT1W5q686+c6rB7iehjLctdOn3l9RW6vNg3BjKfKsqADzJcfme0gkAMoYaHMQ+n86U3o50AzgZhoHWAPrMjQ8IPPmetwpGwyjpVE5jnHszHtDtEmnG+okTIJLf0f/AOxBgg+PCdRlr7Bb0dsXZc+jt8t3FkmIu6kxAiZJPCmPae+NtCQtzpDHsoCTP9kRSduvcVbe0CwBUM0g6AgAzM8BV7ZW2sPoossOwIA0+kGvlPaeRx6iSRJQUm7LZ3jv3B9laQE8TdZge/qBfnVS4+Jukh78DsT9F19TRH/b+DGjE94NtuPeIqHG4yxiUyWsX0EceqFmeQLRHka8/wB9vuF4l2MweGYatdcxyLz6/p/Ig21ibltGYNMKSuaCAYJHH+fDiQt3cdYLLtC1A1JIB94eq67mXH0THYd+7pCD86vHNFdyTxy8AFh968RdMHDYa83YcPnb0Bo4u2V6JrX1e1Ya4FLhV6NuqZEqe+pMDuZtKyfsr1pQfuXWHjpkFNO5mFvQUxdtFA1DXXtu5Y6aKrERHM61suduFKe3hY0ItPdCJj7n2T5TDZTBnnGlC323iIIW8+UjhIIgjh769+/3dwrDXD2W8baGfdVa/ujs9VzNhMOqjiciqB5iK5IOKLO2eSbJxRa2jSZKgnxohi8Y623YMZCkiSeIEiju18HgVVhhcMBAPXzXFUQP3Vza+deY3NqXCIzt7qTRqew10htw30g4lWFsIjAlQCdDrEcu+jWJvs5zOxY9/AfhHAV5rsdpuWp450HowHyr0S5VJbOheUec4jCayXT2u0EiTEwDPOm/ZjDobQB0yLrw4ga0ExWXI5Eg5THXbjGmk0cC9VdNMo08qeW1E07svMmnU60EgyQI0FWME7ISShOnIqfnQu3gV5IPSplw3iPAmq6iNB5ccOaOP7M/A1Nhr4uMEQMWbQDKw95Gg76XltMODv8AmNdPvFfwK57ZDFiAc4BMdgJGlCWSkGMLdD1a3aZVA6QSB2H9aWr24V8YlrwuWspzadYGWFvuj9w+tDdj72Yxi7G8xAuMoBhhCmOffNEn36xI0K2j4qR8DXJPM3szqhirdFbbuykt4e5av4hLJulQGIlRkbNHETIoxsfeK0iWra42wQhExk9lQoGUTObTmYpJ+kzaLXrWFZwAz53IXhoFAifGquI3Nxt62HFvDZUgFkhGJIgBpGtfSdF0ql00baq73S5OPL1ywSak0r8fL7Wel7WxSYq7hz9a+zt3TcJUgMDkYKRl1OrHSYj0oHvXhr9x7D2UF0WxcJ66oZeANWmRAngONeeXNx8cBm+rkrrqrIRoSDznkapf7LxaObYt3s4AJVMxIDaAwvbXXhx6ZpwnFtdv6/M5cXUY+079f2+4z7F2FjEe4160zRZdEBvdsABcjTpxC6DTWmI7OuZsuRspuYVSQWYZbSyxJfkDxPBSNBXm92/jbPtNibcfeNxY8c1dW95cYvDE3PMg/EV2vJ1D3qP5/UvGarYYtni9afaGIKMHCHKWU9Yu/EaDNw5VmwdoXeldoAuLBdYgOukafeHbQa1vnjV/pp8UX5CrNvfzFjj0R8UI+Bryur6XNnyvI0h1NJUemptpiJUgryPbWV5wN/r/APybPof1rK5f8DP4G1IYcZuzZt2yzXmtgR1yqnKZ0OYAHzqjY2WG/Y7UtMeUkEzMj9489aZdubAx1+09pTbyMIgHz1JWqOx/or6OHum7dYQRbt5UkjUAFuPqKMet6hfiG94yC1sp8Pgsd0l225u22IZTxJUjmBrJoDulijkkGGXQxx0puxW5633KfVfqw5lndrmuuqzlB586Vt5djNssKbF3pEdpYkAFSsAKYkZTJ104Vw5c3vcmqXJSnVkW2rpfGlu20pbvaSAT5AelBNpuwuESYZJGvNDJjymiuxtj4nat641h0tEIJnNk6sKFzAGDz9av4j6LNpkgl7Dx2XW58faQVCda7bGi3ppIUFvvyZvU0Y2ffLW1Y6mBPzq7d+jraSf9WzfhuWz8WBqlc2LjcJbm/h3tpMAsBGp01BpJpSWxSDae4QtGo9pXyltmXiBPpQ1dqZeIHrXN3auYMsLB051FYnZVzVDDa+kLG2oVbkqAMshTpAgaqaZcFtW7jLa3bzEnkvBRpxygAT5V5TYuyqyRIAH5dPlXpW6pnDL/ADyFPOKRNO0X740PgfhXndrE3mVSbSNMAk21OswfZbuPKvR7o0NeX7O2hlzAPHWJ9et/mNNiSdk8ja4CeEwWa6qiVCMLpHR5OLyFJIkiQ3OOFODikzZmPJvk5pzZE8RJP+angpWycghdCdjthuqTmmWUcDPWYL86Zdn4Dqrm4KAO8kCjlvBmBIqX6v3U73Jp0DmsDkK0MPRP6vU+Cs2s6i9cW2nMsYmOQ76z2AtzrYu6pvIXZ8g/d6sz2niNKg2/9HNy8gVL6AhgesrAGOWhNXtub/W8PiVsWRaup0YY5W4akQGEjgBpFW8Nv9hm9tbiHwDD1Bn3VzSyOzojj7oU8F9HeLtKQWstLM0q7fvMTzUdtVcZuXjBMWgfB1+Zr0JN7sE39OB4q4+VbfefB/8A3Fv1P6VN03ZROSPFd/rDdJhLEdYWwpHYzMFI9RTTa2Xi0sHqXWudJ1b/AEsrkyibeRuJkEzS7vsTc2raC8Ytx452b503Xb+KSyCFu5gWJY5GtMo4ZREg9s19t066iHT41i0/SV+nHbxRHBj6PK3HqJQu9lJrjx389jlMHjkVM1jFNKgnItoqZ6xgKsnjznxNQ7m4tRjcY+JlGXolUMjZxlDEdUEQ2oozZ21ZcW+j2gpdBlHXSQCFzAQdQSo8Y1oRuohZ8bfN1WK4hoNyW6To1A8Dw51LIlolKUVBv8SW/P7ngZep6Gm8WJOSq1Wnb68HH0sbVR8OEGVmdkhsuV4nMQwBM8KU8MFGItBrVhgtkwhNooeqx+2MKGuAE8etKrRT6SNoB7dmRbXO4uHomDZeqZUgRlYZuFJm0tqFnzW2ZRABiV4ciMx8ePE16/s/HLDgcZu278vL7no4oe7T149Dv5btvzvzOsBhA632yFsgHIEKCwEk5hry4HjyojZ2KufCh7NzLdhjlS4HupMsLYgyYEAqI1E1DstlGEu5i+ZnGSNEkR0mY5CDKkaBhEcDpR2zeVcVZK4hl6OzJdykJcCnSyFZNCcvMNqa9Fu4vbx7eX3ZaMLQqY/DWhccW1ITMcoYnMFnqhpA6wGh041lE7WAu3BnFpWBJM5uOpnUtJ1rVdSjgSppfkBxPerW0RztXB4BG/wtQjevaOONlvqaC3wAEqbzAkT1ictrSeEnvFL1vfeP6E/n/wBKmXfkc7H9/wD0r8mfUzOtdPEq7D2XtNiue50KDiCwus2stPESddZnWni/sy0VANpSOwqD8aE4TfDCkDMzISOBUmPNQaK294cIw0vp5mD6HWoym5DqGktYCwqaKAo7AIHoKtYjEW7a5nZVHafl2mlTaG9mpXDrJ++3DyX9fSl++z3Wz3XLt3nQdwFKrC0Mm0d7ZlcOn/aMPgv6+lKO8Vhr9p+kcsxjVjoOsNB2VdUcKobyOVw11gSMoDSND1WU6d+lPFboD2QotseSQmQkGCAdQYmOHZrUDbvXDwX0M13hMQGvM1vEX8rIHBypmzJmQhswI0BXXTj4Ua2cbjABMSfO2jRMnWAI1nnXQ8bXcl76+wmvgGDdFkbpS4VRGnWiCT2fpXpG6tkpYyNEoSpjgcpiR3aUPu2bv1nCi5dR1LuerbyEZbTRJzGeI5UwYNIa4B96fUAmlycI0HbJWFKV3cj7reo/Sm80UtWe6hjBkPMtkbrtnZ9T0d2IA45Msx769M2bs4LDvx5L2d57+7+RatWQOVTXCFBZiAAJJPACqNW7J6nVEVxZrg2amw2JsOARiLMn91nCn+9Vi7ZC2zcLJkXiwdSvGOIPaRTaGJqRR6Cl/fdCMKfH4Bj8qZrN5HEowYTGhnXs99L+/wBphT4n/A9JLgfH8yPKNjMxxKkkmFOpM+Gp8abXOlI9rEFHzKYMevdRK1vG40ZAfDSoZsUpO0deOSQed4qNDmdV7WA9SBQPFbwDLKoc08+EeVEdmYsnEYJSATeKPpplBciO/RJnvpYdPPkMs0VsEMWwfbgzTlV1HVBY6WwdAJJMmnnbO0bS2MVlu3B0dtujRhGuQkq2YDK3d3UlbY3cxy4x8TZVTLkqVdAwEQNHI105VBtHHbTFm5ZazdKXTNzqM8kxqXUkToO+vuMePHmxQ05EqS2tep8/1vs/F1C1u9afHavvYjxTruxvlawuEfDPYLM2frjKYz8IBjhSjcEHK4Kt3iPVT8q6sIFMtBHISDJ8uVepn6bF1MdE+OTtxTlCVxDm+O3rWMNvobPR5ZBEAFpgAwPA+tL3QNMZT6VOWcklUA/CoEeBAqFUPAtHcZ+FUhgWOKjHgbLkllnqlyMeDRfqltMyF3vaoMuqiQDcYOD7RYAEDQyDRXEYhxiMQ/7QLa6POQygJlChQGzmSOEnlxrMJbYjAWc1ph+0yHMq2yTOa65DdUhVcwIAJ0qricepOLfo7RN0wDlELLEk2SCsDQ/unQjtqkVqdVfP5yS/Rf8ApeK2SLOyrgW0g6BTodWFkkySZ69smNdJPCK1THsqyq2bQbZV66cinpOmZc4YBlbKLvVlSNPhWVw5Opjrfw9/GP8A3F0oVhXOKUlGCmDBg9/KqVjaqkhcrZjyEGreIxWQS1twO3q/+qvzrRJb0dmpcC+l8vaViTmRipPOG6y+moqWzi3BHXb8xqsbwFy6ADluCRPbOYe+a0rGuhpEotnpuyv2SeHwJq0DQ/Y9ybQ8/wBfnVlrtc5RljNVXbizhrw7bbfCuumHbVgYY30e1bAZihHEAaggak08Yu9hG1R5Bh7pUoRMqSvbpy+Aoph9qMpnN7qNYncjEICWsXBGpgBoHacpPZQv/ZA5E10y8yCRh203S2mkdUP/AHgB8qed2sSbtsueJPw0+VeePs/LeVeMqT6EV6FubaOQqBJmABzmall4VFMfLDVuwWIVRJOgFMgwUVNsvZ4siTBcjU8h/CP51qdta0I0JOVlB1CgsSABqSeAA7aStvbZN85VkWwdO1iP3j3dg/kQb2b22muvhxcCrbYqwMjMynWSdIB4Chti8riVdGH8LqT6AzXVjh3ZCT7HQFaYcABJOgAEns0HOunMCTTf9Huws5+t3BoP2QPMjQ3PAageZ7K2bIoIOOGpjTulsL6thwlwAuxztIBgtHV74jj2zVvamwsNiEKXbYZTyBZeRHFSDwNXgT21hNeY5N7nXpoRcX9Euzm1UXrZ/huk/wDiBqE4r6HbX9HinH40VvepWvTiK1NHXIZHzvv9uK+zrSXGvJcV3yCAytOVmkgyI6vbSrYvXrt2wqk9IMlq1k6rDrQgBEay3GvpHfXdxdoYfoHdkAYNKhZkcAcwMDwivP8AA/RU9i/bupezBGDQV1kagzOhmDwrox5ko0+SU4NysXVbbNgkOuJyjmydIPWGqZd78WnthD+JCp9xFew2bhiCVBHKaV333wLyrXYjQh0aOMdhFZZJPgdxS5E8b6X8oN7A3MhEqwD5WU8GXMkEEd9Vf95Nm3J6TCBe09Eg18bZmvV9m7xYS5CLibGUKAqrdVeAGmWQfKvLfpWsq+KGUBQba6rEGC4mRxrvjKcVcZM5XJeBDabZVzg/Rzw69xT/AP1kV2+62Eu628WfzW3+GWkK5hyKd93NiYO/ZU3bbq2oLo54g81aRwiuiPX9UlSm/V3+ptmtzTbi3F1t4lZ5SrJ71JqC5uzj1UqtwMpMlVunKT2lXgE1Y2hufdUj6piXnse4UkaRkKiJ4yD3VFZ2dthDC3MxH7pdSfRxMd9VXtjqIOm0ZQvgqnZW0P8AkjyWx8q1RE39sjToQe/IPkayq/8AO5fL++odMvFgVNjBBpmL6HpQxWNZhFHEHtPbyqTEKzGWJJ76g2XtoKttCYCqQSwJzMWYgg8lC5R5GiuG2hZe6bQZDwKvMKxMSNeBGvpXzs1ks6McoJC7j7UQ3YamVeymHaODQucOQA7IWU/uwJ1B7RHDuoPZS4iFGw9zOkycsWwBrmLeHrRWprgOqKlyNuw7v2I8vgKnuXNaFbAv/YBj2TwjTWDHhVZ9s27hgOsd5A9ZilhjcnQZzS3CWIxnIev6UPuXJqM3Qefpr7xVS/iZ0X1/SuuMVFbHNKTZYu491BRXaDxAY5fMcDTNufgFuWSzCTnI9y/rSWor0b6O1nDv/WH/AArS5LaMi3ht2LPTdMZJyZMpjKNZJGkzTHsrZ9qySbaAE8Tx+PDyraLVi0tSSCWi9ckVoLXRFMA+bN7v+m4n+uf/ABGg8Ub3yH/H4oD/AJz/ABoNFVQr5HX6L9gXcdiCpZhYQA3WkxrwROx21EjgJPZX0PZshVCqAFUAAAQABoAOwRXz9uPvTewtprVslQpLsCikHNOuup0EeVeh7M3/ACVU3F4gGVXt14ZtK4c7bkdWOPw2egxXJWlzD72KwlbbXBz6Ih2Xva2crjxgjvqbDb4YR/6XKf41ZffEVGh6YcM1wSahw+0bVz2LqN+FlPwNTMaxiNm7qjJqQmuawQTtqwvRu5HsqxnwBNfPN+yew89eXGa+l7qAgggEHQg8weVLmL3MwLz/AMMintSUP92K6MOVQ5J5Meo+f3HhXAEcNPCvZ8Z9G2Fb2Wur/bzf4waB4v6L/uX/AM9v5qRXQuogyTwyPNc57TRLAbfv2VyKVKzMFQdSNTPHl20wYr6OsUvsm0/9plPvB+NCcVuljE44djz6hVvgZ91OssH3E93Jdi3h987yjW2h8JHlqTRWxvtbIi5ZYdhRhp3gGAD30m38BdTR7Nxe9rbD3xFVy44aT4/LyotRluzJyiekpvkkaOY/jtgt5lXAPpWq81JPL4VlL7qI3vJBDZ7oLahrasIB1k+ccKj2stk5WW1lAIzhdAy8/A9/fXOGEKPAV1ecEEdoodzVsdY6yiBbuHe51NQGM5R/BIqbG7UxPQOGvo4cDN1QHjTKVIiQdATx7aq2F6kd0VVv24tgTOtEDQ6XGXocqMCOjRdO5ApHupBpu2XqjAknXSTPZpS6cLbOgvqDzDq6wewMoYHxMUmPlj5OERYPRxRlaEpayvEgwRqplYPf50WWqkiVa9G+jU/YXB/+T/ItecrXo30aWW6G4xBCl9D2woBjt1pZcBQ621qYLXCGuppAkk1grkVsGsY+fd8NqXRjcSmYMgvPlW4iXVXrfui4rZfKONBb+NV1I6G0rH99A6nv6ufoxPcg7oq7vmf+PxX9c/xoVdw7KAWUjxiefEcRwPHsNWEDO7yjLdj7qz49erP15hdtJPVyj5/pVfdlZW94D/NWY1Mt6z4Af3m/WuaSTmzpi6ghqtuRBBgjmOI7weVX22qzftlS93uCH/71CrnzJHdQ+2NKxjXLdFy2ww7cGvWj3hL6/wDlsB5tVmziLifssco7i1+16gqV99CIrgmtfkHcarO8ONX+nsv43LB/zBqIWt7cSBL4ZXHM220/u5qQGNVmxwRwJgnhGnvrKN8GvxPTbG/tg+0lxfDKw+M+6iFjerCPwvAfiBX3kRXie0nJusxYyY1nU6DUnnVa3i7gYwxgCO3Xjz8qdY7Qrkj6Fs422/sXEb8LA/A101eJjC4pQD0YbSeqyM3ojEg90Vibw37TZS11D2ZmH900uh9g2j2dlFRtaFeXYffi+ONwn8Sg+8a0Sw+/7fvLbbwJU++aDizbD02HFUsTsm0/tW0b8Sg/EUFsb72zxtsPwkMPlV6zvXhm4uV/Ep+U0N0Zogbc/Bkz9WtflFZREbcw5/prf5hWU2tg0+R4dBIHZWZaPW8BpXL7Prr1nNQETSq+L5eNHG2fQ3aGFKtbHafhFGMlZmti/sh9G8v591LuKX7Rh/EfjTJg7WSZ5il/HSLrEaGZoQ+ZhyfKiO1IYA6aijqUCthmObUxEnjHITTtuxsB8XcgSttfbfs/hH8R/wBasyKLW6O7jYt8zSLKnrNzY/cXv7Tyr1jD2VRQigKqiABwAHKocHhUtItu2oVVEADl2+J76nmpt2MSJXU1GDXS0DEsGuhUYNbrGPm/eozjMT/XXP8AGaGvdZtCzEDgCSQPAGnP6WFA2g0ACbaEwAJJzST2nvpMHhVUK+Q/ukNLn9n51Y28o6awe0gf3l/Wud3b5fpWIE9XgIHPlW94n1st91p9Cp+VS/Gyr+RB29iLdvR7iqSCVBmWy8QIB18a1avq4lWB8D8RSnma/ca8QYOiDsUVvIQZBIPdpUJwjdIvjtq2NZqJ6AptK6v70/iE++phtg809DU9DHCT0v7U/bW/L4miQ2kD+6fdQraVzNdtkfzBmqYlTEycE2Mudc/zyqLZ41WebAnzM0T2NhRfvsIkBWPjplUa9rMtMG08Fh7Vq0Ra67r1WGkEKCS0d1Nfwit/EBv9oKXKc6uriWIyk5l+6wDr6NIFLGD62JY9k/pTGlSnFRHhKzi9hLLcbZQ9ttjH5WkekVSv7GB9i8v9tSh9RI99EHqMmgpNBpMDXdh3wSVUHQQUdDwnkDPZUgweLHC3d9CaJVo0/vH3F0JcAxvrg/orn/dn9KyiWbvrVHX5IGl+IQStnn41qsokjYFBtt/t7Pn8RWVlNHkDL20dMsdp+FGr1hCsFVIjgQCPSsrKMQvhCRtu0qXIVQo7gB8K9n3JtgYKzAAlZMDiSTJPaaysqr4Jhp6xOVZWUrCdL/PuqUfz6VqsrAOlrf8APwrKysY8T+lf/p5/qrfwNJT1lZVVwLLkPbr+zd8vnXW3/YT8XyrKyo/jZX8CCGDUdEug9kfCoMQKysrk/Edq4KF0VwtarKqKTCqWK/aJ51lZTY+SeX5Rq3C/bt+H9T8QPSi+8o+xwv4f/LFZWUHwI/nETYn7S5/PM0xpWVlLm5HxfKafhULVlZUihlctWVlEBo1lZWURT//Z",
     30            description: "223 is a laboratory with a capacity of 40 people",
     31        };
     32        this.toggleSearch = false;
     33
    2034        this.shapes = [];
    2135        this.roomTypes = [];
     
    2842        this.stage.add(this.textLayer);
    2943
     44
     45        this.cachedCanvases = [];
     46
    3047        this.floorNum = floorNum;
    3148
     
    6481        dsrData.forEach((shape) => {
    6582            if (shape.className !== "InfoPin") {
     83                console.log("SHAPE ATTRS CREATE: " + JSON.stringify(shape.attrs));
    6684                const renderedShape = Factory.createRenderedShape(shape.className, shape.attrs);
    6785                addEventHandling(renderedShape, this, "click");
    6886                this.shapes.push(renderedShape);
     87                console.log(renderedShape)
    6988            }
    7089        });
     
    7897    }
    7998
    80 
    81 
    8299    loadMapN(floorData) {
    83100        if (floorData == null || floorData === "") return;
     
    97114
    98115
     116
    99117    drawRouteNEW(nodes, offset = 0) {
    100118
     
    106124        let buff = [nodes[idx].coordinates.x, nodes[idx].coordinates.y];
    107125
    108 
    109126        ++idx;
    110127
     
    115132
    116133            if (idx >= nodes.length){
     134                console.log("FLOOR NUM DISPLAY: " + this.floorNum)
     135                let stageInfo = {stage:this.stage.clone(),floor:this.floorNum};
     136                this.cachedCanvases.push(stageInfo);
     137                dispatchCustomEvent("navend",this.downloadURI)
    117138                return;
    118139            }
     
    122143
    123144            if (nextNode.floorNumber !== currentNode.floorNumber) {
     145                let stageInfo = {stage:this.stage.clone(),floor:this.floorNum};
     146                this.cachedCanvases.push(stageInfo);
    124147                triggerNavigate(nodes, idx, nextNode.floorNumber, nextNode);
    125148                return;
     
    131154            const endY = nextNode.coordinates.y;
    132155
    133             const numSegments = 12;
     156            const numSegments = 10;
    134157
    135158            const deltaX = (endX - startX) / numSegments;
     
    157180                    clearInterval(interval);
    158181                    idx++;
    159                     setTimeout(drawNextSegment, 150);
     182                    setTimeout(drawNextSegment, 60);
    160183                }
    161             }, 50);
     184            }, 30);
    162185        };
    163186
    164187        drawNextSegment();
    165188    }
    166 
    167 
    168189
    169190    initializeRoomTypes() {
     
    173194    }
    174195
    175     getRoomTypes() {
    176         return this.roomTypes;
    177     }
    178 
    179 
    180     getShapeByName(name){
    181         return this.shapes.find(shape => shape.info.name === name)
    182     }
    183 
    184196    getShapeByType(type) {
    185197        return this.shapes.filter((shape) => shape.class === type)
    186     }
    187 
    188     toggleSearchRoom() {
    189         this.toggleSearch = !this.toggleSearch;
    190198    }
    191199
     
    196204    }
    197205
    198     getMainEntrance() {
    199         return this.shapes.filter(shape => shape.class === "Entrance").filter(el => el.info.isMainEntrance === true)[0];
    200     }
    201 
    202     setFilter(filter) {
     206     downloadURI(uri, name) {
     207        let link = document.createElement('a');
     208        link.download = name;
     209        link.href = uri;
     210        document.body.appendChild(link);
     211        link.click();
     212        document.body.removeChild(link);
     213    }
     214
     215
     216    getRouteImages(mapDetails = { mapName: "mapName", from: "from", to: "to" }) {
     217
     218        const pdf = new jsPDF("p", "mm", "a4");
     219        const pageWidth = pdf.internal.pageSize.getWidth();
     220        const pageHeight = pdf.internal.pageSize.getHeight();
     221        const margin = 10;
     222        let yOffset = margin;
     223
     224        const imagesData = [];
     225
     226        let minWidth = 800;
     227
     228        this.cachedCanvases.forEach((canvas, index) => {
     229            console.log("CANVASL " + JSON.stringify(canvas.stage))
     230            let dsrStage = Konva.Node.create(canvas.stage, document.createElement("div"));
     231
     232            let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
     233
     234            dsrStage.getLayers().forEach(layer => {
     235                layer.getChildren().forEach(shape => {
     236                    const shapeX = shape.x();
     237                    const shapeY = shape.y();
     238                    const width = shape.width() * shape.scaleX();
     239                    const height = shape.height() * shape.scaleY();
     240
     241                    minX = Math.min(minX, shapeX - shape.offsetX() * shape.scaleX());
     242                    minY = Math.min(minY, shapeY - shape.offsetY() * shape.scaleY());
     243                    maxX = Math.max(maxX, shapeX + width - shape.offsetX() * shape.scaleX());
     244                    maxY = Math.max(maxY, shapeY + height - shape.offsetY() * shape.scaleY());
     245                });
     246            });
     247
     248            let padding = 50;
     249
     250            const virtualWidth = maxX - minX + padding;
     251            const virtualHeight = maxY - minY + padding;
     252
     253            const scaleDownFactor = Math.min(pageWidth / virtualWidth, pageHeight / virtualHeight);
     254
     255            dsrStage.size({ width: virtualWidth, height: virtualHeight });
     256            dsrStage.position({ x: -minX, y: -minY });
     257            dsrStage.batchDraw();
     258
     259            let canvasImageURI = dsrStage.toDataURL({
     260                pixelRatio: 1
     261            });
     262
     263            imagesData.push(canvasImageURI);
     264            console.log(`Generated Image ${index + 1}`);
     265        });
     266
     267
     268
     269        imagesData.forEach((imageURI, index) => {
     270            const text = `Floor: ${this.cachedCanvases[index].floor}`;
     271            pdf.text(text, margin, yOffset + 5);
     272
     273            const img = new Image();
     274            img.src = imageURI;
     275
     276            const maxWidth = pageWidth - 2 * margin;
     277            const maxHeight = pageHeight - 2 * margin;
     278            let imgWidth = maxWidth;
     279            let imgHeight = maxHeight / 2
     280
     281            if (imgHeight > maxHeight) {
     282                imgHeight = maxHeight;
     283                imgWidth = (pageWidth * maxHeight) / pageHeight;
     284            }
     285
     286            if (yOffset + imgHeight > pageHeight - margin) {
     287                pdf.addPage();
     288                yOffset = margin;
     289            }
     290
     291            pdf.addImage(imageURI, "PNG", margin, yOffset, imgWidth, imgHeight);
     292            yOffset += imgHeight + 10;
     293        });
     294
     295        pdf.save(`${mapDetails.mapName}-${mapDetails.from}-->${mapDetails.to}.route.pdf`);
     296    }
     297
     298
     299
     300setFilter(filter) {
    203301        let rooms = this.getShapeByType("Room")
    204302        if (filter === "All") {
  • imaps-frontend/src/scripts/net/HttpService.js

    r0c6b92a r79a0317  
    55    constructor(auth = false) {
    66      this.auth = auth;
     7      this.responseType = 'json';
    78    }
    89
     
    1011        this.auth = true;
    1112    }
     13    setResponseType(type){
     14        if(type === 'text'){
     15            this.responseType = 'text';
     16        } else if (type === 'json'){
     17            this.responseType = 'json'
     18        } else {
     19            console.error('unsupported response type in http service')
     20        }
     21    }
     22
    1223
    1324    async request(method, endpoint, data = null) {
     
    5364
    5465      console.log("HTTPSERVICE: RESPONSE:",response);
    55  
    56       return response.json();
     66
     67      if(this.responseType === 'json'){
     68          return response.json();
     69      } else if (this.responseType === 'text'){
     70          return response.text()
     71      } else{
     72          console.error('bad resp type')
     73      }
    5774    }
    5875 
  • imaps-frontend/src/scripts/net/netconfig.js

    r0c6b92a r79a0317  
    1414        publish: `${API_PROTECTED}/publish/add`,
    1515        publish_get: `${API_PROTECTED}/publish/get`,
     16        edit_map_info: `${API_PROTECTED}/my-maps/edit`,
    1617    },
    1718    room_types: {
     
    3435        navigate: `${API_PUBLIC}/navigate`,
    3536        add_favourite: `${API_PROTECTED}/favourites/add`,
     37        add_report: `${API_PROTECTED}/reports/create`,
    3638    },
    3739    favourites: {
     
    4345        login: `${API_AUTH}/login`,
    4446        register: `${API_AUTH}/register`,
    45         verify: `${API_AUTH}/verify`
     47        verify: `${API_AUTH}/verify`,
     48        oauth: {
     49            github: {
     50                state: `${API_BASE_URL}/oauth/state`,
     51                redirectUri : `${API_BASE_URL}/oauth/callback`
     52            }
     53        }
    4654
    4755    },
     
    5058        load_pr: `${API_BASE_URL}/admin/load-pr`,
    5159        approve_pr: `${API_BASE_URL}/admin/pr/approve`,
    52         deny_pr: `${API_BASE_URL}/admin/pr/deny`
     60        deny_pr: `${API_BASE_URL}/admin/pr/deny`,
     61        load_reports: `${API_BASE_URL}/admin/load-reports`
    5362    }
    5463};
  • imaps-frontend/src/scripts/rendered_shapes/RenderedEntrance.js

    r0c6b92a r79a0317  
    88      x: attrs.x,
    99      y: attrs.y,
    10       width: attrs.width * scaleX,
    11       height: attrs.height * scaleY,
     10      width: attrs.width,
     11      height: attrs.height,
    1212      fill: "#7fef83",
    1313      stroke: "black",
  • imaps-frontend/src/scripts/rendered_shapes/RenderedRoom.js

    r0c6b92a r79a0317  
    77      x: attrs.x,
    88      y: attrs.y,
    9       width: attrs.width * scaleX,
    10       height: attrs.height * scaleY,
     9      width: attrs.width,
     10      height: attrs.height,
    1111      fill: "#A2D9FF",
    1212      stroke: "black",
  • imaps-frontend/src/scripts/rendered_shapes/RenderedStairs.js

    r0c6b92a r79a0317  
    88            x: attrs.x,
    99            y: attrs.y,
    10             width: attrs.width * scaleX,
    11             height: attrs.height * scaleY,
     10            width: attrs.width ,
     11            height: attrs.height,
    1212            fill: "rgb(199,190,133)",
    1313            stroke: "rgb(16,15,15)",
  • imaps-frontend/src/scripts/rendered_shapes/RenderedWall.js

    r0c6b92a r79a0317  
    77            x: attrs.x,
    88            y: attrs.y,
    9             width: attrs.width * scaleX,
    10             height: attrs.height * scaleY,
     9            width: attrs.width,
     10            height: attrs.height,
    1111            fill: '#4B4B4B',
    1212            stroke: 'black',
  • imaps-frontend/src/scripts/shapes/Entrance.js

    r0c6b92a r79a0317  
    22import MapNode from "../base/MapNode.js";
    33import {node} from "prop-types";
     4import ShapeRegistry from "../util/ShapeRegistry.js";
     5import ShapeQuery from "../util/ShapeQuery.js";
     6import Konva from "konva";
    47
    58export default class Entrance extends MapNode {
     
    1417                x: attrs.position.x,
    1518                y: attrs.position.y,
    16                 width: attrs.width,
    17                 height: attrs.height,
     19                width: attrs.width * attrs.scaleX,
     20                height: attrs.height * attrs.scaleY,
    1821                fill: "rgb(126,238,167)",
    1922                stroke: "#252627",
     
    4649        this.initText();
    4750        this.moveToTop();
     51
     52        console.log("room CONNECT: " + attrs.connected_room,this.info.connectedRoom + "CON")
     53        console.log("entrance: " + this.info.name)
    4854    }
    4955
    5056    loadInfo(attrs) {
    51         this.info.name = attrs.obj_name;
    52         this.info.connectedRoom = attrs.connected_room;
    53         this.info.description = attrs.description;
    54         this.info.isMainEntrance = attrs.is_main_entrance;
    55         this.info.selectedPins = attrs.connected_pins;
    56         this.floorNum = attrs.floor_num;
     57        this.info.name = attrs.obj_name ?? `Entrance${this.id} [${this.floorNum}F]`;
     58        this.info.connectedRoom = attrs.connected_room ?? ``;
     59        this.info.description = attrs.description ?? ``;
     60        this.info.isMainEntrance = attrs.is_main_entrance ?? false;
     61        this.info.selectedPins = attrs.connected_pins ?? [];
    5762    }
     63
    5864
    5965    saveShapeDetails() {
    6066        console.info("fnum entrance",this.attrs.floorNum)
    61 
    6267        this.setAttr("connected_pins", this.info.selectedPins);
    6368        this.setAttr("obj_name", this.info.name);
     
    7580
    7681    setInfo(infoObj) {
    77         console.log("SA VIKNA SETINFO")
    7882        this.info = infoObj;
     83        this.setHighlight();
     84    }
     85
     86    setHighlight(){
     87        console.log("info room: " + this.info.connectedRoom)
    7988        if(this.info.connectedRoom == null || this.info.connectedRoom === "" ){
     89            console.log("vleze if")
    8090            this.strokeWidth(2);
    81             this.stroke("#a10114")
     91            this.stroke("#8a000d")
    8292        }else{
    8393            this.strokeWidth(1)
     
    8595        }
    8696    }
     97
     98    onPlace() {
     99        ShapeQuery
     100            .findAllByTypeAndFloor(this.floorNum,"Room")
     101            .forEach(room => {
     102                if(Konva.Util.haveIntersection(room.getClientRect(),this.getClientRect())){
     103                    this.info.connectedRoom = room.info.name;
     104                }
     105            })
     106        this.setHighlight()
     107    }
    87108}
    88109
  • imaps-frontend/src/scripts/util/Factory.js

    r0c6b92a r79a0317  
    4545    let scaleX = (attrs.scaleX ? parseFloat(attrs.scaleX) : 1);
    4646    let scaleY =  (attrs.scaleY ? parseFloat(attrs.scaleY) : 1);
     47
     48    attrs.width *= scaleX;
     49    attrs.height *= scaleY
     50
     51    console.log("SCALEX " + scaleX)
     52    console.log("SCALEY " + scaleY)
    4753    switch (shapeType) {
    4854      case "Entrance":
Note: See TracChangeset for help on using the changeset viewer.