Changeset 0c6b92a for imaps-frontend/src
- Timestamp:
- 12/12/24 17:06:06 (5 weeks ago)
- Branches:
- main
- Parents:
- d565449
- Location:
- imaps-frontend/src
- Files:
-
- 70 added
- 13 deleted
- 41 edited
- 6 moved
Legend:
- Unmodified
- Added
- Removed
-
imaps-frontend/src/App.css
rd565449 r0c6b92a 1 2 1 3 body { 2 background-color: white; 4 margin: 0; 5 padding: 0; 6 height: 100%; 7 background: white; 8 backdrop-filter: blur(10px); 3 9 } 4 10 .loading-container { … … 7 13 justify-content: center; 8 14 align-items: center; 9 height: 100vh; /* Full-screen loader */10 background-color: #f7f9fc; /* Light background */15 height: 100vh; 16 background-color: #f7f9fc; 11 17 } 12 18 13 19 .spinner { 14 border: 8px solid #f3f3f3; /* Light grey border */15 border-top: 8px solid #1e90ff; /* Accent color for spinning part */20 border: 8px solid #f3f3f3; 21 border-top: 8px solid #1e90ff; 16 22 border-radius: 50%; 17 23 width: 60px; … … 32 38 margin-top: 20px; 33 39 font-size: 18px; 34 color: #2c3e50; /* Dark text for contrast */40 color: #2c3e50; 35 41 font-weight: 500; 36 42 } -
imaps-frontend/src/App.jsx
rd565449 r0c6b92a 1 1 // eslint-disable-next-line no-unused-vars 2 import React, { useState, useEffect } from "react"; 3 import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; 4 import FinkiMaps from "./pages/FinkiMaps/FinkiMaps"; 5 import Maps from "./pages/Maps/Maps"; 2 import React from "react"; 3 import {BrowserRouter as Router, Route, Routes} from "react-router-dom"; 4 import BrowseMaps from "./pages/BrowseMaps/BrowseMaps.jsx"; 6 5 import LoginPage from "./pages/Login/Login"; 7 6 import Signup from "./pages/Signup/Signup"; … … 11 10 import ProtectedRoute from "./components/ProtectedRoute/ProtectedRoute"; 12 11 import "./App.css"; 13 import HttpService from "./scripts/net/HttpService"; 12 import MapView from "./pages/MapView/MapView.jsx"; 13 import MyMaps from "./pages/MyMaps/MyMaps.jsx"; 14 15 import '@fontsource/roboto/300.css'; 16 import '@fontsource/roboto/400.css'; 17 import '@fontsource/roboto/500.css'; 18 import '@fontsource/roboto/700.css'; 19 import {AppProvider} from "./components/AppContext/AppContext.jsx"; 20 import AdminPage from "./pages/AdminPage/AdminPage.jsx"; 21 14 22 15 23 function App() { 16 const [isAuthenticated, setIsAuthenticated] = useState(false);17 const [loading, setLoading] = useState(true);18 24 19 useEffect(() => { 20 const token = localStorage.getItem("token"); 21 const httpService = new HttpService("http://localhost:8080/api/auth"); 25 return ( 22 26 23 const verifyToken = async () => { 24 try { 25 const response = await httpService.get(`/verify?token=${token}`); 26 if (response.username) { 27 setIsAuthenticated(true); 28 console.log("good"); 29 } 30 } catch (error) { 31 console.log("ERROR: ", error); 32 setIsAuthenticated(false); 33 } finally { 34 setLoading(false); 35 } 36 }; 27 <AppProvider> 28 <Router> 29 <Routes> 37 30 38 if (token) { 39 verifyToken(); 40 //setLoading(false); 41 } else { 42 setIsAuthenticated(false); 43 setLoading(false); 44 } 45 }, []); 31 <Route element={<ProtectedRoute/>}> 32 <Route path="/myMaps/:mapName/Draw" element={<Draw/>}/> 33 <Route path="/myMaps" element={<MyMaps/>}/> 34 <Route path="/myMaps/:mapName/View" element={<MapView isPrivate={true}/>}/> 35 <Route path="/Admin" element={<AdminPage/>}/> 36 </Route> 37 <Route path="/" element={<IMaps/>}/> 38 <Route path="/Maps/:mapName/View" element={<MapView isPrivate={false}/>}/> 39 <Route path="/Maps" element={<BrowseMaps/>}/> 46 40 47 const handleLogin = (token) => {48 localStorage.setItem("token", token);49 setIsAuthenticated(true);50 };51 41 52 if (loading) { 53 return ( 54 <div className="loading-container"> 55 <div className="spinner"></div> 56 <p>Loading, please wait...</p> 57 </div> 42 <Route path="/Login" element={<LoginPage/>}/> 43 <Route path="/Signup" element={<Signup/>}/> 44 45 <Route path="*" element={<Error/>}/> 46 </Routes> 47 </Router> 48 </AppProvider> 49 50 58 51 ); 59 }60 61 return (62 <Router>63 <Routes>64 <Route path="/" element={<IMaps />} />65 <Route path="/Maps/FinkiMaps/View" element={<FinkiMaps />} />66 <Route path="/Maps" element={<Maps />} />67 <Route path="/Login" element={<LoginPage onLogin={handleLogin} />} />68 <Route path="/Signup" element={<Signup />} />69 70 <Route element={<ProtectedRoute isAuthenticated={isAuthenticated} />}>71 <Route path="/Maps/FinkiMaps/Draw" element={<Draw />} />72 </Route>73 74 <Route path="*" element={<Error />} />75 </Routes>76 </Router>77 );78 52 } 79 53 -
imaps-frontend/src/TODO.txt
rd565449 r0c6b92a 1 1 PRIORITY HIGH 2 2 3 -Graph na backend 4 -Connections za infoPins i entrance 5 -Cuvanje ID vo _info (da se sovpagjat so modalite) 6 -Crtanje na ruta vo View 7 -Da se prenesat info od From i To vo Mapata kaj so se crtat 8 -Da se cuvaat connections vnatre vo js kodoj za da imat prev data ko ke otvoris nanovo modal 9 3 - Id fix na shapes vo Draw 10 4 11 5 PRIORITY MEDIUM 12 6 13 -Modluarnost za pojke mapi (ne samo finkimap) 14 -Refactor na kod 15 -Hover na sobi na View 16 -Filter na prostorii (Ke zemame od roomType modal) 17 -Multiple floor (Prerabotka na baza) 18 -Zoom na View (imame, da se napret globalno) 19 -Implementacija na stairs 20 -Shape sho ke selektiras za klavanje da sejt celo vreme ko selektiran, duri ne kliknis drug shape ili escape. 21 -Nacin da znes dodeka crtas koi sobi ti se povrzani 22 -Ako kliknis na room vo view, da ta odnesit do toj room (navigacija); 23 7 - Detach event na draw (keys: e, r) 8 - Text na shape vo Draw da e top layer 24 9 25 10 PRIORITY LOW 26 11 27 -Page za pregled na svoi mapi 28 -Refactor (Da se koristit net util klasa za pustanje do backend) 29 -AutoSave na draw duri crtas (Da se pret update request do baza) 30 -Refactor na stil na Draw 12 - Admin Page 31 13 32 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 33 17 34 ZA PREZ 18 19 bug: selectiras eden shape pa drug, pa celo vreme sa klavat posle shape -
imaps-frontend/src/components/FilterBar/FilterBar.jsx
rd565449 r0c6b92a 1 import React from "react";1 import React, { useState, useEffect, useCallback } from "react"; 2 2 import styles from "./FilterBar.module.css"; 3 3 4 function FilterBar() { 5 const filterLocation = (category) => { 6 console.log(`Filter locations by: ${category}`); 7 // filtering logic 8 }; 4 export default function FilterBar({roomTypes,map}) { 5 const [selectedCategory, setSelectedCategory] = useState("all"); 6 7 8 9 const filterLocation = useCallback((category) => { 10 map.setFilter(category) 11 setSelectedCategory(category); 12 },[]) 9 13 10 14 return ( 11 15 <div className={styles.wrapper}> 12 16 <div className={styles.scrollableContainer}> 13 <button className={styles.buttonValue} onClick={() => filterLocation("all")}> 17 <button 18 className={`${styles.buttonValue} ${selectedCategory === "all" ? styles.active : ""}`} 19 onClick={() => filterLocation("all")} 20 > 14 21 All 15 22 </button> 16 <button className={styles.buttonValue} onClick={() => filterLocation("Classrooms")}> 17 Classrooms 18 </button> 19 <button className={styles.buttonValue} onClick={() => filterLocation("Administrative")}> 20 Administrative 21 </button> 22 <button className={styles.buttonValue} onClick={() => filterLocation("Labs")}> 23 Labs 24 </button> 25 <button className={styles.buttonValue} onClick={() => filterLocation("Restrooms")}> 26 Restrooms 27 </button> 28 <button className={styles.buttonValue} onClick={() => filterLocation("Cafeteria")}> 29 Cafeteria 30 </button> 23 24 {roomTypes?.map((roomType, index) => ( 25 <button 26 key={index} 27 className={`${styles.buttonValue} ${selectedCategory === roomType.name ? styles.active : ""}`} 28 onClick={() => filterLocation(roomType.name)} 29 > 30 {roomType.name} 31 </button> 32 ))} 31 33 </div> 32 34 </div> … … 34 36 } 35 37 36 export default FilterBar; -
imaps-frontend/src/components/MapControls/MapControls.jsx
rd565449 r0c6b92a 1 import React, { useState} from "react";1 import React, {useEffect, useState} from "react"; 2 2 import styles from "./MapControls.module.css"; 3 3 import plusIcon from "../../assets/plus_icon.png"; 4 4 import minusIcon from "../../assets/minus_icon.png"; 5 5 import floorIcon from "../../assets/floor_icon.png"; 6 import HttpService from "../../scripts/net/HttpService.js"; 6 7 7 export default function MapControls({ onZoomIn, onZoomOut, onFloorChange }) { 8 const [currentFloor, setCurrentFloor] = useState(1); // Starting floor 8 export default function MapControls({onFloorChange,mapName }) { 9 const [currentFloor, setCurrentFloor] = useState(0); 10 const [floors, setFloors] = useState([]); 11 12 // Starting floor 9 13 10 14 // Handle floor selection … … 20 24 {/* Zoom Controls */} 21 25 <div className={styles.zoomControls}> 22 <button onClick={onZoomIn}className={styles.zoomButton}>26 <button className={styles.zoomButton}> 23 27 <img src={plusIcon} alt="Zoom In" /> 24 28 </button> 25 <button onClick={onZoomOut}className={styles.zoomButton}>29 <button className={styles.zoomButton}> 26 30 <img src={minusIcon} alt="Zoom Out" /> 27 31 </button> … … 29 33 30 34 {/* Floor Selector */} 31 <div className={styles.floorSelector}> 32 <img src={floorIcon} alt="Floor Icon" className={styles.floorIcon} /> 33 <select 34 value={currentFloor} 35 onChange={(e) => handleFloorChange(parseInt(e.target.value, 10))} 36 className={styles.floorDropdown} 37 > 38 <option value={1}>1F</option> 39 <option value={2}>2F</option> 40 <option value={3}>3F</option> 41 <option value={4}>4F</option> 42 </select> 43 </div> 35 {/*<div className={styles.floorSelector}>*/} 36 {/* <img src={floorIcon} alt="Floor Icon" className={styles.floorIcon} />*/} 37 {/* <select*/} 38 {/* value={currentFloor}*/} 39 {/* onChange={(e) => handleFloorChange(parseInt(e.target.value, 10))}*/} 40 {/* className={styles.floorDropdown}*/} 41 {/* >*/} 42 {/* {floors?.map(floor => (*/} 43 {/* <option key={floor.floorNumber} value={floor.floorNumber}>*/} 44 {/* {floor.floorNumber}F*/} 45 {/* </option>*/} 46 47 {/* ))}*/} 48 {/* /!*<option value={1}>1F</option>*!/*/} 49 {/* /!*<option value={2}>2F</option>*!/*/} 50 {/* /!*<option value={3}>3F</option>*!/*/} 51 {/* /!*<option value={4}>4F</option>*!/*/} 52 {/* </select>*/} 53 {/*</div>*/} 44 54 </div> 45 55 ); -
imaps-frontend/src/components/Modals/EntranceModal/EntranceModal.module.css
rd565449 r0c6b92a 14 14 } 15 15 16 /* Overlay: Dark background with optional blur */17 16 .overlay { 18 background: rgba(0, 0, 0, 0.7); /* Darken the background */ 19 backdrop-filter: blur(5px); /* Optional blur effect */ 20 z-index: 999; /* Ensure the overlay appears below the modal */ 17 /*background: rgba(255, 255, 255, 0.05);*/ 18 z-index: 999; 21 19 } 22 20 23 /* Modal content: Centered and styled */ 21 .draggableHeader { 22 cursor: move; 23 padding: 10px; 24 background-color: #2c2f33; 25 color: #ffffff; 26 border-bottom: 1px solid #444; 27 } 28 29 30 24 31 .modalContent { 25 32 position: absolute; … … 27 34 left: 50%; 28 35 transform: translate(-50%, -50%); 29 background-color: #2c2f33; /* Darker background for modal content */30 color: #ffffff ; /* White text for contrast */36 background-color: #2c2f33; 37 color: #ffffff !important; 31 38 padding: 20px; 32 39 border-radius: 8px; … … 34 41 width: 100%; 35 42 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); 36 z-index: 1000; /* Ensure the modal appears above the overlay */43 z-index: 1000; 37 44 } 38 45 39 /* Heading styles */ 40 h2, 41 h3 { 46 47 .title { 42 48 margin-bottom: 15px; 43 color: #ffffff ; /* White text for the headings */49 color: #ffffff ; 44 50 } 45 51 46 /* Button for opening the modal */47 52 .btnModal { 48 53 padding: 10px 20px; … … 61 66 } 62 67 63 /* Close button inside the modal */64 68 .closeModal { 65 69 position: absolute; … … 78 82 } 79 83 80 /* Form styles */81 84 .form { 82 85 display: flex; … … 88 91 } 89 92 90 /* Form labels */91 93 .formGroup label { 92 94 display: block; 93 95 margin-bottom: 5px; 94 color: #ffffff; /* White text for labels */96 color: #ffffff; 95 97 } 96 98 97 /* Input, select, and textarea styles */98 99 .formGroup input, 99 100 .formGroup select, … … 103 104 border-radius: 4px; 104 105 border: 1px solid #ccc; 105 background-color: #23272a; /* Darker input background */106 color: #ffffff; /* White text inside the input fields */106 background-color: #23272a; 107 color: #ffffff; 107 108 } 108 109 109 110 .formGroup input::placeholder, 110 111 .formGroup textarea::placeholder { 111 color: #888888; /* Slightly lighter color for placeholder text */112 color: #888888; 112 113 } 113 114 114 115 /* Checkbox alignment fix */ 115 116 .formGroupCheckbox { 117 116 118 display: flex; 117 align-items: center; /* Vertical alignment */ 118 color: #ffffff; 119 flex-direction: row; 120 vertical-align: middle; 121 align-items: flex-end; 122 /*justify-content: start;!* Vertical alignment *!*/ 123 /*color: #ffffff;*/ 124 } 125 126 .formGroupCheckbox input{ 127 width: 10%; 128 margin-bottom: 0.88em; 119 129 } 120 130 121 131 /* Add button */ 122 132 .addButton { 123 background-color: #28a745 ;133 background-color: #28a745 !important; 124 134 color: white; 125 135 padding: 8px 15px; … … 131 141 132 142 .addButton:hover { 133 background-color: #218838 ;143 background-color: #218838 !important; 134 144 } 135 145 136 146 /* Save button styling */ 137 147 .submitButton { 138 background-color: #28a745 ;148 background-color: #28a745 !important; 139 149 color: white; 140 150 padding: 10px 20px; … … 150 160 } 151 161 152 /* Pin list */153 162 .pinList { 154 163 list-style-type: none; … … 157 166 } 158 167 159 /* Individual pin item */160 168 .pinItem { 161 169 background-color: #444; … … 169 177 } 170 178 171 /* Remove button */172 179 .removeButton { 173 180 background-color: #dc3545; -
imaps-frontend/src/components/Profile/Profile.jsx
rd565449 r0c6b92a 1 import React, { useState, useRef, useEffect } from "react"; 1 import React, { useState, useRef, useEffect, useContext } from "react"; 2 import { useNavigate, Link } from "react-router-dom"; 2 3 import profile from "../../assets/person_icon.png"; 3 4 import styles from "./Profile.module.css"; 5 import { useAppContext } from "../AppContext/AppContext.jsx"; 4 6 5 function Profile() { 6 const menus = ["Profile", "Settings", "Support", "Logout"]; 7 const [open, setOpen] = useState(false); 8 const menuRef = useRef(null); 9 const imgRef = useRef(null); 7 function Profile({ position = "fixed" }) { 8 const { username, isAuthenticated } = useAppContext(); 9 const [open, setOpen] = useState(false); 10 const menuRef = useRef(null); 11 const imgRef = useRef(null); 12 const navigate = useNavigate(); 10 13 11 useEffect(() => { 12 const handleClickOutside = (e) => { 13 if (menuRef.current && imgRef.current) { 14 if (!menuRef.current.contains(e.target) && !imgRef.current.contains(e.target)) { 15 setOpen(false); 14 const menus = isAuthenticated ? ["My Maps", "Logout"] : ["Login"]; 15 16 useEffect(() => { 17 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 } 22 } 23 }; 24 25 document.addEventListener("click", handleClickOutside); 26 27 return () => { 28 document.removeEventListener("click", handleClickOutside); 29 }; 30 }, []); 31 32 const handleMenuClick = (menu) => { 33 if (menu === "My Maps") { 34 navigate("/MyMaps"); 35 } else if (menu === "Logout") { 36 localStorage.removeItem("token"); 37 window.location.reload(); 16 38 } 17 }39 setOpen(false); 18 40 }; 19 41 20 document.addEventListener("click", handleClickOutside); 21 22 return () => { 23 document.removeEventListener("click", handleClickOutside); 24 }; 25 }, []); 26 27 return ( 28 <div className={styles.profileContainer}> 29 <div className={styles.profileWrapper}> 30 <img 31 onClick={() => setOpen(!open)} 32 src={profile} 33 alt="profile" 34 className={styles.profileImage} 35 ref={imgRef} 36 /> 37 {open && ( 38 <div ref={menuRef} className={styles.dropdownMenu}> 39 <ul className={styles.menuList}> 40 {menus.map((menu) => ( 41 <li key={menu} onClick={() => setOpen(false)} className={styles.menuItem}> 42 {menu} 43 </li> 44 ))} 45 </ul> 46 </div> 47 )} 48 </div> 49 </div> 50 ); 42 return ( 43 <div className={position === "fixed" ? styles.fixedProfileContainer : styles.inlineProfileContainer}> 44 <div className={styles.profileWrapper}> 45 <div className={styles.profileIconContainer} onClick={() => setOpen(!open)}> 46 <img src={profile} alt="profile" className={styles.profileImage} ref={imgRef} /> 47 </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 )} 66 </div> 67 </div> 68 ); 51 69 } 52 70 -
imaps-frontend/src/components/Profile/Profile.module.css
rd565449 r0c6b92a 1 img:hover, 2 .menuItem:hover { 1 .fixedProfileContainer { 2 position: fixed; 3 top: 1em; 4 right: 2.5em; 5 z-index: 1000; 6 } 7 8 .inlineProfileContainer { 9 display: inline-block; 3 10 cursor: pointer; 4 11 } 5 12 6 .menuList { 7 list-style: none; 8 padding: 0; 9 margin: 0; 10 text-align: center; 13 .profileWrapper { 14 display: flex; 15 align-items: center; 16 cursor: pointer; 11 17 } 12 18 13 .profileContainer { 14 display: flex; 15 justify-content: center; 16 } 17 18 .profileWrapper { 19 position: relative; 19 .profileIconContainer { 20 position: relative; /* Makes dropdown position relative to this container */ 21 cursor: pointer; 20 22 } 21 23 22 24 .profileImage { 23 height: 48px;24 width: 48px;25 width: 60px; 26 height: 60px; 25 27 border-radius: 50%; 26 /* border: 4px solid #d1d5db; */ 27 object-fit: cover; 28 transition: box-shadow 0.3s ease; 28 29 } 29 30 30 /* Dropdown Menu */ 31 .profileImage:hover { 32 box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.2); 33 } 34 31 35 .dropdownMenu { 32 36 position: absolute; 33 top: 50px; /* Adjust according to image size and spacing */ 34 left: 50%; 35 transform: translateX(-50%); /* Center the menu below the image */ 36 width: 13rem; 37 background-color: white; 38 padding: 1rem; 39 box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); 37 top: 70px; 38 margin-left: 1.8em; 39 transform: translateX(-50%); 40 background-color: #ffffff; 41 box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15); 42 border-radius: 12px; 43 padding: 12px; 44 width: 180px; 45 transition: opacity 0.3s ease; 46 opacity: 1; 40 47 } 41 48 42 /* Menu List and Items */ 49 .username { 50 padding: 10px 0; 51 font-weight: 600; 52 text-align: center; 53 color: #333; 54 border-bottom: 1px solid #eee; 55 } 56 43 57 .menuList { 44 58 list-style-type: none; … … 49 63 .menuItem { 50 64 padding: 10px; 65 cursor: pointer; 66 transition: background-color 0.2s ease, color 0.2s ease; 51 67 text-align: center; 52 border-radius: 5px; 53 transition: background-color 0.3s ease; 68 color: #333; 69 font-weight: 500; 70 border-radius: 8px; 54 71 } 55 72 56 73 .menuItem:hover { 57 background-color: #f0f0f0; 74 background-color: #f5f5f5; 75 color: #007bff; /* Optional: Adds a blue highlight on hover */ 58 76 } -
imaps-frontend/src/components/ProtectedRoute/ProtectedRoute.jsx
rd565449 r0c6b92a 1 import React from "react"; 2 import { Outlet, Navigate } from "react-router-dom"; 1 import { Navigate, Outlet, useLocation } from "react-router-dom"; 2 import {useAppContext} from "../AppContext/AppContext.jsx"; 3 import HttpService from "../../scripts/net/HttpService.js"; 4 import React, {useEffect} from "react"; 5 import {LoadingContainer} from "../LoadingContainer/LoadingContainer.jsx"; 6 import config from "../../scripts/net/netconfig.js"; 7 import {verifyToken} from "../../scripts/util/verifyToken.js"; 3 8 4 const ProtectedRoute = ({ isAuthenticated }) => { 5 return isAuthenticated ? <Outlet /> : <Navigate to="/Login" />; 9 const ProtectedRoute = () => { 10 const location = useLocation(); 11 const {loading,isAuthenticated} = useAppContext(); 12 13 14 if(loading){ 15 return <LoadingContainer/> 16 } 17 18 19 if (!isAuthenticated) { 20 return <Navigate to="/Login" state={{ targetPath: location }} replace />; 21 } 22 23 return <Outlet />; 6 24 }; 7 25 -
imaps-frontend/src/components/SaveMap/SaveMap.jsx
rd565449 r0c6b92a 2 2 import HttpService from '../../scripts/net/HttpService'; 3 3 import styles from './SaveMap.module.css'; 4 import triggerMapSave from "../../scripts/util/triggerMapSave.js"; 4 5 5 6 const SaveMap = ({submitHandler}) => { … … 8 9 const handleSubmit = (event) => { 9 10 event.preventDefault(); 10 submitHandler(name); 11 // submitHandler(); 12 // const mapSaveEvent = new CustomEvent("mapsave",{}); 13 // window.dispatchEvent(mapSaveEvent) 14 triggerMapSave(); 11 15 }; 12 16 … … 15 19 <div className={styles.saveMapContainer}> 16 20 <form onSubmit={handleSubmit} className={styles.saveMapForm}> 17 <div>18 <label htmlFor="name">Map Name: </label>19 <input20 type="text"21 id="name"22 value={name}23 onChange={(e) => setName(e.target.value)}24 placeholder="Enter map name"25 required26 className={styles.saveMapInput}27 />28 </div>29 21 <div> 30 22 <button type="submit" className={styles.saveMapButton}>Save Map</button> -
imaps-frontend/src/components/SaveMap/SaveMap.module.css
rd565449 r0c6b92a 21 21 22 22 .saveMapButton { 23 background-color: #e67e22; 24 color: white; 23 width: 100%; 24 height: 50px; 25 background-color: #e67e22 !important; 26 color: #ffffff !important; 25 27 border: none; 26 border-radius: 5px; 28 border-radius: 8px; 29 font-size: 20px; 30 font-weight: bold; 27 31 cursor: pointer; 28 width: 8vw; 29 height: 2vw; 30 text-align: center; 32 display: flex; 33 justify-content: center; 34 align-items: center; 35 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 36 transition: background-color 0.3s, transform 0.2s, box-shadow 0.3s; 37 appearance: none; 38 -webkit-appearance: none; 31 39 } 32 40 33 41 .saveMapButton:hover { 34 background-color: #b35418; 42 background-color: #d2691e !important; 43 transform: scale(1.05); 44 box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15); 45 } 46 47 .saveMapButton:active { 48 background-color: #b35418 !important; 35 49 transform: scale(0.98); 36 50 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); -
imaps-frontend/src/components/SearchBar/SearchBar.jsx
rd565449 r0c6b92a 1 import React, { useState } from "react"; 1 import React, { useState, useEffect, useRef } from "react"; 2 import ReactDOM from "react-dom"; 2 3 import searchIcon from "../../assets/search_icon.png"; 3 4 import routeIcon from "../../assets/route_icon.png"; … … 5 6 import styles from "./SearchBar.module.css"; 6 7 7 function SearchBar( props) {8 function SearchBar({ map, handleDirectionsSubmit, setIsPanelOpen, setSelectedRoom, availableShapes,handleFloorChange }) { 8 9 const [isExpanded, setIsExpanded] = useState(false); 9 10 const [from, setFrom] = useState(""); 10 11 const [to, setTo] = useState(""); 12 const [availableOptions, setAvailableOptions] = useState([]); 13 const [filteredOptions, setFilteredOptions] = useState([]); 14 const [dropdownVisible, setDropdownVisible] = useState(false); 15 const [inputFieldType, setInputFieldType] = useState(""); 16 const dropdownRef = useRef(null); 11 17 12 18 const toggleExpanded = () => { … … 14 20 }; 15 21 16 function searchRoom(){ 17 props.map.search(); 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){ 26 handleFloorChange(foundRoom.floorNum); 27 } 18 28 29 30 console.log("FOUND ROOM: " + foundRoom) 31 map.highlightShape(from); 32 setSelectedRoom(foundRoom); 33 setIsPanelOpen(true); 19 34 } 20 35 21 const handleDirectionsSubmit = () => { 22 console.log(`From: ${from}, To: ${to}`); 23 const url = new URL('http://localhost:8080/api/public/navigate'); 24 url.searchParams.append('from', from); 25 url.searchParams.append('to', to); 36 const handleInputFocus = (field) => { 37 if (availableOptions.length === 0 && map) { 38 setAvailableOptions( 39 availableShapes 40 .filter((sh) => sh.className === "RenderedRoom") 41 .map((shape) => shape.info.name) 42 ); 43 } 44 setDropdownVisible(true); 45 setInputFieldType(field); 46 }; 26 47 27 fetch(url) 28 .then(response => { 29 if (!response.ok) { 30 throw new Error('Network response was not ok'); 31 } 32 return response.json(); 33 }) 34 .then(data => { 35 console.log('Success:', data); 36 const points = data.map(item => item.coordinates); 37 props.map.drawRoute(points); 38 }) 39 .catch(error => { 40 console.error('Error:', error); 41 }); 48 const handleInputChange = (setter) => (event) => { 49 const value = event.target.value; 50 setter(value); 51 setDropdownVisible(true); 52 53 const filtered = availableOptions.filter((option) => 54 option.toLowerCase().includes(value.toLowerCase()) 55 ); 56 setFilteredOptions(filtered); 57 }; 58 59 const handleOptionClick = (option) => { 60 if (inputFieldType === "from") { 61 setFrom(option); 62 } else if (inputFieldType === "to") { 63 setTo(option); 64 } 65 setDropdownVisible(false); 66 }; 67 68 const renderDropdown = () => { 69 if (!dropdownVisible || filteredOptions.length === 0) return null; 70 71 const position = dropdownRef.current?.getBoundingClientRect() || { top: 0, left: 0, width: 0 }; 72 73 return ReactDOM.createPortal( 74 <ul 75 className={styles.dropdown} 76 style={{ 77 position: "absolute", 78 top: position.top + position.height, 79 left: position.left, 80 width: position.width, 81 }} 82 > 83 {filteredOptions.map((option, index) => ( 84 <li 85 key={index} 86 className={styles.dropdownItem} 87 onClick={() => handleOptionClick(option)} 88 > 89 {option} 90 </li> 91 ))} 92 </ul>, 93 document.body // Portal renders outside the parent hierarchy 94 ); 42 95 }; 43 96 44 97 return ( 45 <div className={styles.wrapper}> 46 {/* Regular search bar */} 47 {!isExpanded ? ( 48 <div className={styles.searchBar}> 49 <input 50 type="search" 51 className={styles.inputField} 52 placeholder="Search location" 53 aria-label="Search" 54 /> 55 <div className={styles.buttons}> 56 <button type="button" className={styles.iconButton} onClick={searchRoom}> 57 <img src={searchIcon} alt="Search Icon" /> 58 </button> 59 <button type="button" className={styles.iconButton} onClick={toggleExpanded}> 60 <img src={routeIcon} alt="Route Icon" /> 61 </button> 62 </div> 63 </div> 64 ) : ( 65 /* Expanded view for directions */ 66 <div className={styles.directionsContainer}> 67 <div className={styles.directionsInputs}> 68 <input 69 type="text" 70 placeholder="From" 71 aria-label="From" 72 value={from} 73 onChange={(e) => setFrom(e.target.value)} 74 className={styles.inputField} 75 /> 76 <input 77 type="text" 78 placeholder="To" 79 aria-label="To" 80 value={to} 81 onChange={(e) => setTo(e.target.value)} 82 className={styles.inputField} 83 /> 84 </div> 85 <div className={styles.buttons}> 86 <button type="button" className={styles.iconButton} onClick={handleDirectionsSubmit}> 87 <img src={searchIcon} alt="Submit Directions" /> 88 </button> 89 <button type="button" className={styles.iconButton} onClick={toggleExpanded}> 90 <img src={closeIcon} alt="Close Icon" /> 91 </button> 92 </div> 93 </div> 94 )} 95 </div> 98 <div className={styles.wrapper}> 99 {!isExpanded ? ( 100 <div className={styles.searchBar}> 101 <input 102 type="search" 103 className={styles.inputField} 104 placeholder="Search location" 105 aria-label="Search" 106 ref={dropdownRef} // Attach the input to calculate dropdown position 107 onFocus={() => handleInputFocus("from")} 108 onChange={handleInputChange(setFrom)} 109 value={from} 110 /> 111 {renderDropdown()} 112 <div className={styles.buttons}> 113 <button type="button" className={styles.iconButton} onClick={searchRoom}> 114 <img src={searchIcon} alt="Search Icon" /> 115 </button> 116 <button type="button" className={styles.iconButton} onClick={toggleExpanded}> 117 <img src={routeIcon} alt="Route Icon" /> 118 </button> 119 </div> 120 </div> 121 ) : ( 122 <div className={styles.directionsContainer}> 123 <div className={styles.directionsInputs}> 124 <input 125 type="text" 126 placeholder="From" 127 aria-label="From" 128 value={from} 129 onFocus={() => handleInputFocus("from")} 130 onChange={handleInputChange(setFrom)} 131 className={styles.inputField} 132 ref={inputFieldType === "from" ? dropdownRef : null} 133 /> 134 <input 135 type="text" 136 placeholder="To" 137 aria-label="To" 138 value={to} 139 onFocus={() => handleInputFocus("to")} 140 onChange={handleInputChange(setTo)} 141 className={styles.inputField} 142 ref={inputFieldType === "to" ? dropdownRef : null} 143 /> 144 {renderDropdown()} 145 </div> 146 <div className={styles.buttons}> 147 <button 148 type="button" 149 className={styles.iconButton} 150 onClick={() => handleDirectionsSubmit(from, to)} 151 > 152 <img src={searchIcon} alt="Submit Directions" /> 153 </button> 154 <button type="button" className={styles.iconButton} onClick={toggleExpanded}> 155 <img src={closeIcon} alt="Close Icon" /> 156 </button> 157 </div> 158 </div> 159 )} 160 </div> 96 161 ); 97 162 } -
imaps-frontend/src/components/SearchBar/SearchBar.module.css
rd565449 r0c6b92a 1 1 .wrapper { 2 position: relative; 2 3 display: flex; 3 4 justify-content: center; 4 margin: 0px auto; 5 margin: 0 auto; 6 z-index: 105; 5 7 max-width: 600px; 6 8 width: 100%; 9 padding: 10px; 7 10 } 8 11 9 12 .searchBar, 10 13 .directionsContainer { 14 position: relative; 11 15 display: flex; 12 align-items: end; /* Ensure all items remain aligned in the center */13 width: 100%;16 align-items: center; 17 /*width: 100%;*/ 14 18 background-color: white; 15 19 padding: 10px; … … 17 21 border-radius: 30px; 18 22 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 19 flex-wrap: nowrap; /* Prevent items from wrapping to the next line */23 flex-wrap: nowrap; 20 24 } 21 25 … … 28 32 outline: none; 29 33 margin-right: 10px; 30 min-width: 0; /* Ensure the input field remains flexible */ 34 min-width: 0; 35 31 36 } 37 38 .dropdown { 39 position: absolute; /* Position absolute to detach from wrapper flow */ 40 top: 100%; /* Place below the search bar */ 41 left: 0; 42 background-color: white; 43 border: 1px solid #ddd; 44 border-radius: 5px; 45 width: 100%; /* Match the search bar width */ 46 max-height: 200px; 47 overflow-y: auto; 48 z-index: 9999; /* Highest level to ensure it's above everything */ 49 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); 50 } 51 52 53 .dropdownItem { 54 padding: 10px; 55 cursor: pointer; 56 transition: background-color 0.3s ease, color 0.3s ease; 57 } 58 59 .dropdownItem:hover { 60 background-color: #007bff; /* Highlight color */ 61 color: white; /* Ensure text is readable */ 62 } 63 32 64 33 65 .buttons { … … 50 82 font-size: 16px; 51 83 transition: background-color 0.3s ease; 52 width: auto;53 84 } 54 85 … … 62 93 } 63 94 64 /* Expanded view for the direction inputs */65 95 .directionsContainer { 66 flex-direction: column;67 96 gap: 10px; 68 97 } … … 73 102 gap: 10px; 74 103 } 75 76 .directionsInputs .inputField {77 flex: 1;78 }79 80 /* Media query for responsive design */81 @media (max-width: 768px) {82 .wrapper {83 max-width: 100%;84 padding: 10px;85 }86 87 /* Keep buttons and input fields inline */88 .searchBar,89 .directionsContainer {90 flex-direction: row; /* Keep items in a row */91 align-items: center; /* Keep buttons and inputs on the same line */92 flex-wrap: nowrap; /* Prevent items from wrapping */93 padding: 8px;94 }95 96 .inputField {97 width: 100%;98 flex-grow: 1;99 margin-right: 10px;100 }101 102 .buttons {103 width: auto;104 gap: 5px;105 }106 107 .iconButton {108 width: auto;109 padding: 10px;110 }111 } -
imaps-frontend/src/components/SideBar/SideBar.jsx
rd565449 r0c6b92a 24 24 </li> 25 25 <li> 26 <Link to="/Maps"> Maps</Link>26 <Link to="/Maps">Browse Maps</Link> 27 27 </li> 28 28 <li> 29 <Link to="/ Maps/FinkiMaps/Draw">Draw</Link>29 <Link to="/myMaps">My Maps</Link> 30 30 </li> 31 31 </ul> -
imaps-frontend/src/components/SideBar/SideBar.module.css
rd565449 r0c6b92a 90 90 top: 0; 91 91 left: 0; 92 width: 100 %;93 height: 100 %;92 width: 100vw; 93 height: 100vh; 94 94 background-color: rgba(0, 0, 0, 0.4); 95 z-index: 900;95 z-index: 100; 96 96 } -
imaps-frontend/src/index.css
rd565449 r0c6b92a 1 2 1 3 html, 2 4 body, -
imaps-frontend/src/pages/BrowseMaps/Maps.module.css
rd565449 r0c6b92a 1 2 1 3 .container { 2 4 max-width: 1200px; … … 7 9 8 10 h1 { 11 font-family: exo, sans-serif; 9 12 font-size: 2.5rem; 10 color: # 34495e;13 color: #0f71da; 11 14 margin-bottom: 20px; 12 15 } … … 62 65 } 63 66 67 .favorite{ 68 position: absolute; 69 top: 20px; 70 right: 20px; 71 cursor: pointer; 72 } 73 64 74 /* When dragging a tile */ 65 75 .dragging { -
imaps-frontend/src/pages/Draw/Draw.jsx
rd565449 r0c6b92a 1 import { useEffect, useState } from "react"; 2 import { MapBuilder } from "../../scripts/main/MapBuilder.js"; 1 import { useContext, useEffect, useState } from "react"; 3 2 import styles from "./Draw.module.css"; 4 import RoomModal from "../../components/RoomModal/RoomModal.jsx"; 5 import SideBar from "../../components/SideBar/SideBar.jsx"; 6 import EntranceModal from "../../components/EntranceModal/EntranceModal.jsx"; 3 import RoomModal from "../../components/Modals/RoomModal/RoomModal.jsx"; 4 import EntranceModal from "../../components/Modals/EntranceModal/EntranceModal.jsx"; 7 5 import DrawGuide from "../../components/DrawGuide/DrawGuide.jsx"; 8 import RoomTypeModal from "../../components/RoomTypeModal/RoomTypeModal.jsx"; 9 import InfoPinModal from "../../components/InfoPinModal/InfoPinModal.jsx"; 6 import RoomTypeModal from "../../components/Modals/RoomTypeModal/RoomTypeModal.jsx"; 7 import InfoPinModal from "../../components/Modals/InfoPinModal/InfoPinModal.jsx"; 8 import SaveMap from "../../components/SaveMap/SaveMap.jsx"; 9 import Logo from "../../components/Logo/Logo.jsx"; 10 import { Link, useNavigate, useParams, useSearchParams } from "react-router-dom"; 11 import Profile from "../../components/Profile/Profile.jsx"; 10 12 import HttpService from "../../scripts/net/HttpService.js"; 11 import SaveMap from "../../components/SaveMap/SaveMap.jsx"; 12 import logo from "../../assets/logo_icon.png"; 13 import MapTemplateSelector from "../../components/MapTemplateSelector/LoadMap.jsx"; 14 import KeymapPanel from "../../components/KeyMappingsGuidePanel/KeymapPanel.jsx"; 15 import { Link } from "react-router-dom"; 13 import StairsModal from "../../components/Modals/StairsModal/StairsModal.jsx"; 14 import useMapLoader from "./Hooks/useMapLoader.js"; 15 import {FloorSelector} from "./FloorSelector.jsx"; 16 import {useRoomTypesLoader} from "./Hooks/useRoomTypesLoader.js"; 17 import {useAppContext} from "../../components/AppContext/AppContext.jsx"; 18 import config from "../../scripts/net/netconfig.js"; 19 import ShapeRegistry from "../../scripts/util/ShapeRegistry.js"; 16 20 17 21 function Draw() { 18 const [selectedFloor, setSelectedFloor] = useState(1); 19 const [app, setApp] = useState(null); 22 const { mapName } = useParams(); 23 const { username } = useAppContext(); 24 20 25 const [isPopupVisible, setIsPopupVisible] = useState(false); 26 const [errorMessage, setErrorMessage] = useState("Error"); 27 const [hasError, setHasError] = useState(false); 28 const [searchParams, setSearchParams] = useSearchParams(); 29 30 const[roomTypes,setRoomTypes] = useState([]); 31 32 const {app,floors,saveFloor,setFloors} = useMapLoader(mapName,username,searchParams,setSearchParams) 33 const {addRoomType} = useRoomTypesLoader(setRoomTypes,mapName,username); 34 35 const addFloorHandler = async (newFloorNum) => { 36 const httpService = new HttpService(); 37 httpService.setAuthenticated(); 38 39 const payload = { 40 num: newFloorNum, 41 mapName: mapName, 42 }; 43 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 }; 52 53 const deleteFloorHandler = async (floorNum) => { 54 if(floorNum === 0) return 55 56 const httpService = new HttpService(); 57 httpService.setAuthenticated(); 58 59 try { 60 await httpService.delete(`${config.floors.delete}?floorNum=${floorNum}&mapName=${mapName}`); 61 setFloors((prevFloors) => prevFloors.filter(f => f.num !== floorNum)) 62 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 21 73 useEffect(() => { 22 const app = new MapBuilder("container");23 setApp(app);24 // fpsCounterLoop();74 return () => { 75 ShapeRegistry.clear(); 76 } 25 77 }, []); 26 78 27 const handleFloorChange = (event) => {28 setSelectedFloor(event.target.value);29 console.log(`Floor changed to: ${event.target.value}`);30 };31 79 32 const handleRenderClick = () => { 33 setIsPopupVisible(true); 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 }); 34 90 35 setTimeout(() => {36 setIsPopupVisible(false);37 }, 3000);38 };39 91 40 const handleSaveClick = async (mapName) => {41 const resp = await app.saveMap(mapName);42 };43 const handleLoadMapClick = (data) => {44 app.deserializeMap(data);45 92 }; 46 93 47 94 return ( 48 95 <div className={styles.wrapper} id="wrapper"> 49 <SideBar></SideBar> 96 {/* <SideBar></SideBar> */} 97 <Logo></Logo> 50 98 <div id="container" className={styles.cont}></div> 51 99 <div className={styles.panel}> 52 <Link to="/"> 53 <img src={logo} alt="Finki Logo" className={styles.logo} /> 100 <div className={styles.topPanelH}> 101 <Profile position="inline"></Profile> 102 </div> 103 <Link to={`/myMaps/${mapName}/View`} className={styles.titleLink}> 104 <h1 className={styles.title}>{mapName}</h1> 54 105 </Link> 55 <h1 className={styles.title}>Map Builder</h1>56 {/* <div id="fpscont" className={styles.fpscounter}>57 <p id="fpsCounter"></p>58 </div> */}59 106 <div className={styles.guideWrapper}> 60 <DrawGuide 107 <DrawGuide/> 61 108 </div> 62 63 {/* <div className={styles.floorSelector}> 64 <label htmlFor="floorSelect">Select Floor:</label> 65 <select 66 id="floorSelect" 67 value={selectedFloor} 68 onChange={handleFloorChange} 69 className={styles.floorDropdown} 70 > 71 <option value={1}>1st Floor</option> 72 <option value={2}>2nd Floor</option> 73 <option value={3}>3rd Floor</option> 74 <option value={4}>4th Floor</option> 75 </select> 76 </div> */} 77 {/* <h2 className={styles.paragraph}>Objects:</h2> */} 109 <hr/> 110 <br/> 111 {/* {<h2 className={styles.paragraph}>Objects:</h2>} */} 78 112 <ul className={styles.shapeOptions} id="shapeOptions"> 79 113 <li data-info="Entrance" className={`${styles.shapeOption} ${styles.entrance}`}></li> 80 114 <li data-info="Wall" className={`${styles.shapeOption} ${styles.wall}`} id="wall"></li> 81 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> 82 117 </ul> 83 <br /> 84 <RoomTypeModal map={app}></RoomTypeModal> 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> 85 127 86 <div id="render" className={styles.buttonContainer}> 87 <button 88 id="render-button" 89 type="button" 90 className={styles.renderButton} 91 onClick={handleRenderClick} 92 > 93 Render 94 </button> 95 </div> 128 <br/> 129 130 <hr/> 131 <br/> 132 {hasError && <p style={{color: "red", textAlign: "center"}}>{errorMessage}</p>} 96 133 <div className={styles.templateCont}> 97 134 <SaveMap submitHandler={handleSaveClick}></SaveMap> 98 <MapTemplateSelector loadHandler={handleLoadMapClick}></MapTemplateSelector>99 135 </div> 100 136 101 137 <div className={styles.hide}> 102 <RoomModal map={app} ></RoomModal>138 <RoomModal map={app} roomTypes={roomTypes}></RoomModal> 103 139 <EntranceModal map={app}></EntranceModal> 104 140 <InfoPinModal map={app}></InfoPinModal> 141 <StairsModal map={app}></StairsModal> 105 142 </div> 106 143 </div> 107 144 108 145 {isPopupVisible && ( 109 <div className={styles.popup}> 110 <div className={styles.popupContent}> 111 <h2>Map Rendered!</h2> 112 <p>Your map has been successfully rendered.</p> 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> 113 151 </div> 114 </div>115 152 )} 116 153 </div> -
imaps-frontend/src/pages/Draw/Draw.module.css
rd565449 r0c6b92a 4 4 padding: 0; 5 5 height: 100%; 6 overflow : hidden;6 overflow-x: hidden; 7 7 background-color: #141414; 8 8 } 9 9 10 .topPanelH { 11 width: 100%; 12 display: flex; 13 justify-content: center; 14 align-items: center; 15 margin-bottom: 20px; 16 } 17 10 18 .logo { 11 width: 100px; 12 margin: 10px; 13 align-self: center; 19 width: 80px; 14 20 } 15 21 16 22 .title { 17 margin-top: 20px; 18 color: #804047; 19 font-size: 120%; 23 text-align: center; 24 font-size: 2.5em; 25 font-weight: 700; 26 text-transform: uppercase; 27 color: #333333; 28 font-family: "Oswald", sans-serif; 29 margin-bottom: 0; 30 text-decoration: none; 31 } 32 33 .titleLink { 34 text-decoration: none; 35 } 36 .titleLink:hover{ 37 text-decoration: none; 20 38 } 21 39 … … 23 41 color: #cccccc; 24 42 } 43 25 44 .guideWrapper { 26 45 display: flex; … … 28 47 align-items: center; 29 48 margin: 0; 49 width: 100%; 30 50 } 31 51 … … 51 71 display: flex; 52 72 flex-direction: column; 73 align-items: center; /* Center elements horizontally */ 53 74 padding: 20px; 54 75 background-color: #1c1c1c; … … 58 79 margin-right: 10px; 59 80 border-radius: 20px; 81 overflow-x: hidden; 60 82 } 61 83 62 84 .buttonContainer { 63 85 display: flex; 64 justify-content: space-between;86 justify-content: center; 65 87 align-items: center; 66 88 margin-top: 2rem; 67 89 gap: 10px; 68 } 69 70 /* .hide { 71 visibility: hidden; 72 } */ 90 width: 100%; 91 } 73 92 74 93 .renderButton { … … 104 123 list-style: none; 105 124 padding: 0; 106 margin: 0 0;107 display: flex;108 flex-direction: column;125 margin: 0; 126 display: grid; 127 grid-template-columns: repeat(2, 1fr); 109 128 gap: 15px; 129 justify-items: center; 110 130 align-items: center; 111 131 } … … 127 147 } 128 148 129 .templateCont {130 display: flex;131 flex-direction: column;132 }133 134 149 .shapeOption:hover { 135 150 background-color: #94183b; … … 137 152 } 138 153 154 .templateCont { 155 display: flex; 156 flex-direction: column; 157 align-items: center; /* Center section horizontally */ 158 width: 100%; 159 } 160 139 161 .fpscounter { 140 162 border: 1px solid #00b822; 141 163 margin: 20px 0; 164 text-align: center; 165 width: 100%; 142 166 } 143 167 … … 145 169 color: #00740c; 146 170 font-weight: bold; 147 text-align: center;148 171 margin: 0; 149 172 padding: 5px 0; 150 173 } 151 174 152 .floorSelector {153 margin-bottom: 20px;154 display: flex;155 flex-direction: column;156 gap: 8px;157 color: #ffffff;158 }159 160 .floorSelector label {161 font-size: 14px;162 }163 164 .floorDropdown {165 padding: 8px;166 font-size: 14px;167 border-radius: 5px;168 border: 1px solid #ccc;169 background-color: #3a3a3a;170 color: #ffffff;171 cursor: pointer;172 width: 100%;173 transition: border-color 0.3s;174 }175 176 .floorDropdown:focus {177 outline: none;178 border-color: #1e90ff;179 }180 175 181 176 .popup { … … 226 221 background-image: url("./shapeImages/room_icon.png"); 227 222 } 223 .stairs { 224 background-image: url("./shapeImages/stairs_icon.png"); 225 } 226 .floorSection { 227 display: flex; 228 flex-direction: column; 229 align-items: center; 230 gap: 20px; 231 background-color: #2d2d2d; 232 padding: 20px; 233 border-radius: 8px; 234 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); 235 width: 100%; 236 } 237 238 .floorLabel { 239 font-size: 16px; 240 font-weight: bold; 241 color: #ffffff; 242 margin-bottom: 10px; 243 text-align: center; 244 } 245 246 .floorList { 247 display: flex; 248 flex-direction: column; 249 align-items: center; 250 gap: 15px; 251 width: 100%; 252 } 253 254 .floorItems { 255 display: flex; 256 flex-direction: column; 257 align-items: center; 258 gap: 10px; 259 width: 100%; 260 } 261 262 .addFloorButton { 263 background-color: #e67e22; 264 border: none; 265 border-radius: 50%; 266 width: 40px; 267 height: 40px; 268 display: flex; 269 justify-content: center; 270 align-items: center; 271 cursor: pointer; 272 transition: background-color 0.3s, transform 0.2s; 273 } 274 275 .addFloorButton:hover { 276 background-color: #d2691e; 277 transform: scale(1.1); 278 } 279 280 .addFloorButton:active { 281 background-color: #b35418; 282 transform: scale(0.95); 283 } 284 285 .icon { 286 width: 20px; 287 height: 20px; 288 filter: invert(1); 289 } 290 291 .floorButton { 292 padding: 10px 15px; 293 font-size: 14px; 294 color: #ffffff; 295 background-color: #3a3a3a; 296 border: 1px solid #555555; 297 border-radius: 8px; 298 width: 100%; 299 text-align: center; 300 cursor: pointer; 301 transition: background-color 0.3s, transform 0.2s; 302 } 303 304 .floorButton:hover { 305 background-color: #4a4a4a; 306 } 307 308 .floorButton.active { 309 background-color: #1e90ff; 310 color: #ffffff; 311 font-weight: bold; 312 } 313 314 .floorInput { 315 width: 60%; 316 padding: 8px; 317 font-size: 14px; 318 border: 1px solid #ccc; 319 border-radius: 5px; 320 background-color: #3a3a3a; 321 color: #ffffff; 322 text-align: center; 323 transition: border-color 0.3s; 324 } 325 326 .floorInput:focus { 327 outline: none; 328 border-color: #1e90ff; 329 } 330 331 .floorActions { 332 display: flex; 333 gap: 10px; 334 justify-content: center; 335 align-items: center; 336 margin-top: 10px; 337 } 338 339 .errorMessage { 340 color: red; 341 font-size: 12px; 342 margin-top: 10px; 343 text-align: center; 344 } 345 .floorItemWrapper { 346 display: flex; 347 align-items: center; 348 gap: 10px; 349 width: 100%; 350 } 351 352 .floorItem { 353 padding: 10px 15px; 354 font-size: 14px; 355 color: #ffffff; 356 background-color: #3a3a3a; 357 border: 1px solid #555555; 358 border-radius: 8px; 359 text-align: center; 360 cursor: pointer; 361 flex-grow: 1; 362 transition: background-color 0.3s; 363 } 364 365 .floorItem:hover { 366 background-color: #4a4a4a; 367 } 368 369 .activeFloor { 370 background-color: #1e90ff; 371 color: #ffffff; 372 font-weight: bold; 373 } 374 375 .deleteFloorButton { 376 background-color: #ff4d4d; 377 border: none; 378 border-radius: 8px; 379 padding: 10px; 380 color: white; 381 cursor: pointer; 382 transition: background-color 0.3s, transform 0.2s; 383 } 384 385 .deleteFloorButton:hover { 386 background-color: #e60000; 387 transform: scale(1.1); 388 } 389 390 .deleteFloorButton:active { 391 background-color: #cc0000; 392 transform: scale(0.95); 393 } -
imaps-frontend/src/pages/IMaps/IMaps.jsx
rd565449 r0c6b92a 4 4 import styles from "./IMaps.module.css"; 5 5 import Home from "./components/pages/Home"; 6 import Profile from "../../components/Profile/Profile"; 7 import Logo from "../../components/Logo/Logo"; 8 import Cards from "./components/Cards"; 6 9 7 10 function IMaps() { 8 11 return ( 9 <> 10 <div className={styles.home}> 11 <Navbar></Navbar> 12 <Home></Home> 13 </div> 14 </> 12 <div className="homebg"> 13 <Navbar></Navbar> 14 <Home></Home> 15 </div> 15 16 ); 16 17 } -
imaps-frontend/src/pages/IMaps/IMaps.module.css
rd565449 r0c6b92a 5 5 html, 6 6 body { 7 overflow: auto; 8 overflow-x: hidden; 7 margin: 0; 9 8 } -
imaps-frontend/src/pages/IMaps/components/Button.css
rd565449 r0c6b92a 1 @import url(https://fonts.googleapis.com/css?family=Exo:500); 1 2 :root { 2 --primary: # fff;3 --primary: #252627; 3 4 } 4 5 5 6 .btn { 6 7 padding: 8px 20px; 7 8 font-family: exo, sans-serif; 8 9 border-radius: 20px; 9 10 outline: none; … … 14 15 .btn--primary { 15 16 background-color: var(--primary); 16 17 color: #242424; 17 color: #ffffff; 18 18 border: 1px solid var(--primary); 19 19 } … … 24 24 color: #fff; 25 25 padding: 8px 20px; 26 border: 1px solid var(--primary);26 /* border: 1px solid var(--primary); */ 27 27 transition: all 0.3s ease-out; 28 28 } … … 30 30 .btn--medium { 31 31 padding: 8px 20px; 32 font-size: 18px;32 font-size: 22px; 33 33 } 34 34 35 35 .btn--large { 36 36 padding: 12px 26px; 37 font-size: 2 0px;37 font-size: 24px; 38 38 } 39 39 … … 41 41 .btn--medium:hover { 42 42 transition: all 0.3s ease-out; 43 background: #fff ;44 color: # 242424;43 background: #ffffffc2; 44 color: #000000; 45 45 transition: 250ms; 46 46 } -
imaps-frontend/src/pages/IMaps/components/HeroSection.css
rd565449 r0c6b92a 1 @import url(https://fonts.googleapis.com/css?family=Exo:500); 2 1 3 video { 2 4 object-fit: cover; … … 7 9 } 8 10 11 12 13 9 14 .hero-container { 10 background: url("../images/img-home.jpg") center center/cover no-repeat;11 15 height: 100vh; 12 16 width: 100%; … … 15 19 justify-content: center; 16 20 align-items: center; 17 box-shadow: inset 0 0 0 1000px rgba(0, 0, 0, 0.4); 21 18 22 object-fit: contain; 23 24 25 19 26 } 20 27 … … 23 30 font-size: 100px; 24 31 margin-top: -100px; 32 font-family: exo, sans-serif; 33 text-transform: uppercase; 34 text-shadow: 1px 1px 0px #ef404e, 35 1px 2px 0px #ef404e, 36 1px 3px 0px #ef404e, 37 1px 4px 0px #ef404e, 38 1px 5px 0px #ef404e, 39 1px 6px 0px #ef404e, 40 1px 10px 5px rgba(16, 16, 16, 0.5), 41 1px 15px 10px rgba(16, 16, 16, 0.4), 42 1px 20px 30px rgba(16, 16, 16, 0.3), 43 1px 25px 50px rgba(16, 16, 16, 0.2); 25 44 } 26 45 -
imaps-frontend/src/pages/IMaps/components/HeroSection.jsx
rd565449 r0c6b92a 7 7 function HeroSection() { 8 8 return ( 9 <div className="hero-container">10 <h1>Map Your World</h1>11 <p>Create and explore detailed indoor maps.</p>12 <div className="hero-btns">13 <Link to="/Maps/FinkiMaps/Draw">14 <Button className="btns" buttonStyle="btn--outline" buttonSize="btn--large">15 Create maps16 </Button>17 </Link>18 <Link to="/Maps">19 <Button20 className="btns"21 buttonStyle="btn--primary"22 buttonSize="btn--large"23 onClick={console.log("hey")}24 >25 Browse Maps <i className="far fa-play-circle" />26 </Button>27 </Link>9 <div className="hero-container"> 10 <h1>Map Your World</h1> 11 <p>Create and explore detailed indoor maps.</p> 12 <div className="hero-btns"> 13 <Link to="/myMaps"> 14 <Button className="btns" buttonStyle="btn--outline" buttonSize="btn--large"> 15 Create maps 16 </Button> 17 </Link> 18 <Link to="/Maps"> 19 <Button 20 className="btns" 21 buttonStyle="btn--primary" 22 buttonSize="btn--large" 23 > 24 Browse Maps <i className="far fa-play-circle"/> 25 </Button> 26 </Link> 27 </div> 28 28 </div> 29 </div>30 29 ); 31 30 } -
imaps-frontend/src/pages/IMaps/components/Navbar.css
rd565449 r0c6b92a 1 .navbar { 2 background: linear-gradient(90deg, rgb(28, 27, 27) 0%, rgb(26, 23, 23) 100%); 3 height: 80px; 1 .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); 14 color: #ffffff; 15 /*color: #333333;*/ 4 16 display: flex; 5 17 justify-content: center; 6 18 align-items: center; 7 font-size: 1.2rem; 19 padding: 0.8rem 1.5rem; 20 box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); 8 21 position: sticky; 9 22 top: 0; 10 z-index: 999; 11 } 12 13 .pad-bot { 14 /* padding-bottom: 1.5rem; */ 23 z-index: 1000; 24 border-radius: 8px; 25 margin: 5px; 15 26 } 16 27 17 28 .navbar-container { 18 29 display: flex; 19 justify-content: center;30 justify-content: space-between; 20 31 align-items: center; 21 32 width: 100%; 33 max-width: 1200px; 22 34 } 23 35 24 .navbar-logo { 25 color: #fff; 26 justify-self: start; 27 margin-left: 20px; 28 cursor: pointer; 29 text-decoration: none; 30 font-size: 2rem; 36 .navbar-left { 31 37 display: flex; 32 38 align-items: center; 39 gap: 1rem; 33 40 } 34 41 35 .fa-typo3 { 36 margin-left: 0.5rem; 37 font-size: 1.8rem; 42 .navbar-title { 43 font-size: 1.6rem; 44 font-weight: 700; 45 color: #ffffff; 46 margin: 0; 47 font-family: "exo", sans-serif; 38 48 } 39 49 40 .nav-menu { 41 display: grid; 42 grid-template-columns: repeat(4, auto); 43 grid-gap: 10px; 44 list-style: none; 45 text-align: center; 46 width: 70vw; 47 justify-content: end; 48 margin-right: 2rem; 50 .navbar-right { 51 display: flex; 52 align-items: center; 53 gap: 1rem; 49 54 } 50 55 51 .nav-item { 52 height: 80px; 56 .navbar-btn { 57 padding: 0.4rem 1.5rem; 58 font-size: 0.9rem; 59 border: 2px solid transparent; 60 border-radius: 20px; 61 text-decoration: none; 62 color: #ffffff; 63 font-weight: 500; 64 transition: all 0.3s ease-in-out; 65 font-family: "exo", sans-serif; 66 cursor: pointer; 67 margin-left: 4em; 68 margin-right: 1em; 53 69 } 54 70 55 .nav-links { 56 color: #fff; 57 display: flex; 58 align-items: center; 59 text-decoration: none; 60 padding: 0.5rem 1rem; 61 height: 100%; 71 .navbar-login { 72 background-color: #252627; 73 border: 2px solid #3f3232; 62 74 } 63 75 64 .nav-links:hover { 65 border-bottom: 4px solid #fff; 66 transition: all 0.2s ease-out; 76 .navbar-login:hover { 77 background-color: #ffffff; 78 color: #212121; 79 border: 1px solid rgba(19, 18, 18, 0.82); 67 80 } 68 81 69 .fa-bars { 70 color: #fff; 82 .navbar-signup { 83 background-color: #252627; 84 color: #ffffff; 85 border: 2px solid #343a40; 71 86 } 72 87 73 .nav-links-mobile { 74 display: none; 88 .navbar-signup:hover { 89 background-color: #ffffff; 90 color: #343a40; 91 border: 2px solid #343a40; 75 92 } 76 77 .menu-icon {78 display: none;79 }80 81 @media screen and (max-width: 960px) {82 .NavbarItems {83 position: relative;84 }85 86 .nav-menu {87 display: flex;88 flex-direction: column;89 width: 100%;90 height: 90vh;91 position: absolute;92 top: 80px;93 left: -100%;94 opacity: 1;95 transition: all 0.5s ease;96 }97 98 .nav-menu.active {99 background: #242222;100 left: 0;101 opacity: 1;102 transition: all 0.5s ease;103 z-index: 1;104 }105 106 .nav-links {107 text-align: center;108 padding: 2rem;109 width: 100%;110 display: table;111 }112 113 .nav-links:hover {114 background-color: #fff;115 color: #242424;116 border-radius: 0;117 }118 119 .navbar-logo {120 position: absolute;121 top: 0;122 left: 0;123 transform: translate(25%, 50%);124 }125 126 .menu-icon {127 display: block;128 position: absolute;129 top: 0;130 right: 0;131 transform: translate(-100%, 60%);132 font-size: 1.8rem;133 cursor: pointer;134 }135 136 .fa-times {137 color: #fff;138 font-size: 2rem;139 }140 141 .nav-links-mobile {142 display: block;143 text-align: center;144 margin: 2rem auto;145 border-radius: 4px;146 width: 80%;147 text-decoration: none;148 font-size: 1.5rem;149 background-color: transparent;150 color: #fff;151 padding: 14px 20px;152 border: 1px solid #fff;153 transition: all 0.3s ease-out;154 }155 156 .nav-links-mobile:hover {157 background: #fff;158 color: #242424;159 transition: 250ms;160 }161 } -
imaps-frontend/src/pages/IMaps/components/Navbar.jsx
rd565449 r0c6b92a 1 import React, { useState, useEffect } from "react"; 2 import { Button } from "./Button"; 1 import React, {useContext, useState} from "react"; 2 import { Link } from "react-router-dom"; 3 import logo_icon from "../../../assets/logo_icon.png"; 4 //import { AuthContext } from "../../../components/AuthContext/AuthContext"; 5 import Logo from "../../../components/Logo/Logo.jsx"; 6 import Profile from "../../../components/Profile/Profile.jsx"; 7 import {useAppContext} from "../../../components/AppContext/AppContext.jsx"; 3 8 import "./Navbar.css"; 4 import { Link } from "react-router-dom";5 import logo from "../../../assets/logo_icon.png";6 9 7 10 function Navbar() { … … 12 15 const closeMobileMenu = () => setClick(false); 13 16 14 const showButton = () => { 15 if (window.innerWidth <= 960) { 16 setButton(false); 17 } else { 18 setButton(true); 19 } 20 }; 17 const { isAuthenticated } = useAppContext(); 21 18 22 useEffect(() => { 23 showButton(); 24 }, []); 19 return ( 20 <nav className="modern-navbar"> 21 <div className="navbar-container"> 22 {/* Left Section - Logo and Title */} 23 <div className="navbar-left"> 24 <Logo position="relative"/> 25 <h1 className="navbar-title">iMaps</h1> 26 </div> 25 27 26 window.addEventListener("resize", showButton); 27 28 return ( 29 <> 30 <nav className="navbar"> 31 <div className="navbar-container"> 32 <a href="#" className="navbar-logo pad-bot" onClick={closeMobileMenu}> 33 iMaps 34 </a> 35 <div className="menu-icon" onClick={handleClick}> 36 <i className={click ? "fas fa-times" : "fas fa-bars"} /> 37 </div> 38 <ul className={click ? "nav-menu active" : "nav-menu"}> 39 <Link to="/Signup"> 40 <li className="nav-item"> 41 <a href="#" className="nav-links" onClick={closeMobileMenu}> 42 SignUp 43 </a> 44 </li> 45 </Link> 46 </ul> 47 <div className="pad-bot"> 48 <Link to="/Login">{button && <Button buttonStyle="btn--outline">LOG IN</Button>}</Link> 49 </div> 50 </div> 51 </nav> 52 </> 53 ); 28 {/* Right Section - Login/Signup or Profile */} 29 <div className="navbar-right"> 30 {isAuthenticated ? ( 31 <Profile position="relative"/> 32 ) : ( 33 <> 34 <Link to="/Login" className="navbar-btn navbar-login"> 35 Log In 36 </Link> 37 <Link to="/Signup" className="navbar-btn navbar-signup"> 38 Sign Up 39 </Link> 40 </> 41 )} 42 </div> 43 </div> 44 </nav> 45 ); 54 46 } 55 47 -
imaps-frontend/src/pages/IMaps/components/pages/Home.jsx
rd565449 r0c6b92a 1 1 import React from "react"; 2 import "./Home.css"; 3 import Cards from "../Cards"; 4 import HeroSection from "../HeroSection"; 5 import Footer from "../Footer"; 2 import "./Home.scss"; 3 import { Link } from "react-router-dom"; 4 import { Button } from "../Button.jsx"; 5 import Cards from "../Cards.jsx"; 6 import Footer from "../Footer.jsx"; 7 import sl from "../../../../assets/bg-home-light-gray.png" 6 8 7 9 function Home() { 8 10 return ( 9 <> 10 <div className="home"> 11 <HeroSection /> 12 {/* <Cards /> */} 13 <Footer /> 11 <div className="home"> 12 <div className="hero-container"> 13 <h1>Map Your World</h1> 14 <h2 className="description">Create and explore detailed indoor maps.</h2> 15 <div className="hero-btns"> 16 <Link to="/myMaps"> 17 <Button className="btns" buttonSize="btn--large"> 18 Create Maps 19 </Button> 20 </Link> 21 <Link to="/Maps"> 22 <Button className="btns" buttonSize="btn--large"> 23 Browse Maps <i className="far fa-play-circle" /> 24 </Button> 25 </Link> 26 </div> 14 27 </div> 15 </> 28 <Cards /> 29 <Footer></Footer> 30 </div> 16 31 ); 17 32 } -
imaps-frontend/src/pages/Login/Login.jsx
rd565449 r0c6b92a 1 import React, { useState} from "react";2 import { Link, useNavigate} from "react-router-dom";1 import React, {useContext, useState} from "react"; 2 import {Link, useLocation, useNavigate} from "react-router-dom"; 3 3 import styles from "./Login.module.css"; 4 4 import illustration from "../../assets/illustration_img.png"; 5 import Logo from "../../components/Logo/Logo.jsx"; 6 import HttpService from "../../scripts/net/HttpService.js"; 7 import {useAppContext} from "../../components/AppContext/AppContext.jsx"; 8 import config from "../../scripts/net/netconfig.js"; 5 9 6 const LoginPage = ({onLogin}) => { 7 const [username, setUsername] = useState(""); 8 const [password, setPassword] = useState(""); 9 const [error, setError] = useState(null); 10 const navigate = useNavigate(); 10 const LoginPage = () => { 11 const [formUsername, setFormUsername] = useState(""); 12 const [password, setPassword] = useState(""); 13 const [error, setError] = useState(null); 14 const navigate = useNavigate(); 15 const location = useLocation(); 11 16 12 const payload = { 13 username: username, 14 password: password 15 }; 17 const {setUsername, setIsAuthenticated} = useAppContext(); 16 18 17 const handleLogin = (e) => { 18 e.preventDefault(); 19 const {targetPath} = location.state || {targetPath: {pathname: "/"}}; 19 20 20 fetch("http://localhost:8080/api/auth/login", { 21 method: "POST", 22 headers: { 23 "Content-Type": "application/json", 24 }, 25 body: JSON.stringify(payload), 26 }) 27 .then((response) => { 28 if (!response.ok) { 29 throw new Error("Login failed: resp = " + response.statusText); 30 } 31 return response.json(); 32 }) 33 .then((data) => { 34 if (data.token) { 35 navigate("/Maps/FinkiMaps/Draw"); 36 onLogin(data.token) 37 } else { 38 setError("Invalid username or password."); 39 } 40 }) 41 .catch((error) => { 42 console.error("Login failed", error); 43 setError("Login failed. Please try again."); 44 }); 45 46 }; 21 const payload = { 22 username: formUsername, 23 password: password, 24 }; 47 25 48 return ( 49 <div className={styles.wrapper}> 50 <div className={styles.illustration}> 51 <img src={illustration} alt="illustration" /> 52 </div> 53 <div className={styles.form}> 54 <div className={styles.heading}>LOGIN</div> 55 <form onSubmit={handleLogin}> 56 <div> 57 <label htmlFor="username">Username</label> 58 <input 59 type="text" 60 id="name" 61 placeholder="Enter your username" 62 onChange={(e) => setUsername(e.target.value)} 63 value={username} 64 required 65 /> 66 </div> 67 <div> 68 <label htmlFor="password">Password</label> 69 <input 70 type="password" 71 id="password" 72 placeholder="Enter your password" 73 onChange={(e) => setPassword(e.target.value)} 74 value={password} 75 required 76 /> 77 </div> 78 {error && <p className={styles.error}>{error}</p>} 79 <button type="submit">Submit</button> 80 </form> 81 <p> 82 Don't have an account? <Link to="/Signup"> Sign Up </Link> 83 </p> 84 </div> 85 </div> 86 ); 26 const handleLogin = async () => { 27 const httpService = new HttpService(); 28 return httpService.post(config.auth.login, payload) 29 30 }; 31 32 33 const login = async (e) => { 34 e.preventDefault(); 35 36 handleLogin() 37 .then(resp => { 38 if (resp.token) { 39 navigate(targetPath) 40 localStorage.setItem("token", resp.token); 41 setUsername(resp.username); 42 setIsAuthenticated(true); 43 console.log("ROLES",resp.roles) 44 } else { 45 setError("Invalid username or password."); 46 } 47 }).catch(reason => { 48 console.error("Login failed", reason); 49 setError("Login failed. Please try again.") 50 }) 51 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 // }); 77 }; 78 79 return ( 80 <div className={styles.wrapper}> 81 <Logo></Logo> 82 <div className={styles.illustration}> 83 <img src={illustration} alt="illustration"/> 84 </div> 85 <div className={styles.form}> 86 <div className={styles.heading}>LOGIN</div> 87 <form onSubmit={login}> 88 <div> 89 <label htmlFor="username">Username</label> 90 <input 91 type="text" 92 id="name" 93 placeholder="Enter your username" 94 onChange={(e) => setFormUsername(e.target.value)} 95 value={formUsername} 96 required 97 /> 98 </div> 99 <div> 100 <label htmlFor="password">Password</label> 101 <input 102 type="password" 103 id="password" 104 placeholder="Enter your password" 105 onChange={(e) => setPassword(e.target.value)} 106 value={password} 107 required 108 /> 109 </div> 110 {error && <p className={styles.error}>{error}</p>} 111 <button type="submit">Submit</button> 112 </form> 113 <p> 114 Don't have an account? <Link to="/Signup"> Sign Up </Link> 115 </p> 116 </div> 117 </div> 118 ); 87 119 }; 88 120 -
imaps-frontend/src/pages/Signup/Signup.jsx
rd565449 r0c6b92a 3 3 import illustration from "../../assets/illustration_img.png"; 4 4 import styles from "./Signup.module.css"; 5 import Logo from "../../components/Logo/Logo"; 5 6 6 7 export default function Signup() { … … 54 55 return ( 55 56 <div className={styles.wrapper}> 57 <Logo></Logo> 56 58 <div className={styles.illustration}> 57 59 <img src={illustration} alt="illustration" /> -
imaps-frontend/src/scripts/base/MapShape.js
rd565449 r0c6b92a 6 6 throw new Error("Cannot instantiate abstract class BaseShape directly."); 7 7 } 8 9 console.log(config.x,"FFF"); 8 10 super(config); 9 11 this.layer = layer; … … 12 14 this.snappable = snap; 13 15 this._info = {}; 14 this. modalEventName = "";16 this.eventName = ""; 15 17 this.infoText = null; 18 this.floorNum; 16 19 17 20 this.shadowForStrokeEnabled(false); 18 21 this.on("mouseover", () => (document.body.style.cursor = "pointer")); 19 22 this.on("mouseout", () => (document.body.style.cursor = "default")); 20 // this.on("dblclick", (e) => { 21 // this.moveToTop(); 22 // this.getLayer() 23 // .find("Transformer") 24 // .forEach((t) => t.moveToTop()); 25 // }); 23 this.on("click", (e) => { 24 if(e.evt.altKey){ 25 this.moveToTop(); 26 this.getLayer() 27 .find("Transformer") 28 .forEach((t) => t.moveToTop()); 29 } 30 31 }); 26 32 27 33 if (snap) { 28 34 this.on("dragend", this.snapToGrid.bind(this)); 29 this.on('dblclick', this.snapToGrid.bind(this)) 35 this.on('dblclick', this.snapToGrid.bind(this)); 30 36 } 31 37 … … 36 42 }); 37 43 38 this.on('dragend', () => { 44 45 this.on('dragmove', () => { 39 46 if (this.infoText) { 40 47 this.updateTextPosition(); … … 76 83 } 77 84 85 86 destroy() { 87 super.destroy(); 88 if (this.infoText !== null) { 89 this.infoText.remove() 90 console.log("cleared text") 91 } 92 } 93 94 setInfo(infoObj){ 95 this.info = infoObj; 96 } 97 98 load(){ 99 console.log("Abstract function") 100 } 101 78 102 _sceneFunc(context) { 79 103 let width = this.width(); … … 93 117 } 94 118 119 updateText(shapeName){ 120 this.infoText.text = shapeName; 121 console.log("Updated text to : " + shapeName) 122 } 123 95 124 snapToGrid() { 96 125 this.position({ … … 98 127 y: Math.round(this.y() / this.blockSize) * this.blockSize, 99 128 }); 129 100 130 } 101 131 -
imaps-frontend/src/scripts/main/MapBuilder.js
rd565449 r0c6b92a 2 2 import Konva from "konva"; 3 3 import HttpService from "../net/HttpService.js"; 4 import { zoomStage } from "../util/zoomStage.js"; 4 import {zoomStage} from "../util/zoomStage.js"; 5 import {addEventHandling} from "../util/addEventHandling.js"; 6 import MapNode from "../base/MapNode.js"; 7 import {json} from "react-router-dom"; 8 import log from "eslint-plugin-react/lib/util/log.js"; 9 import ShapeRegistry from "../util/ShapeRegistry.js"; 10 import shapeRegistry from "../util/ShapeRegistry.js"; 11 import triggerMapSave from "../util/triggerMapSave.js"; 5 12 6 13 export class MapBuilder { 7 constructor(containerId) { 8 this.container = document.getElementById(containerId); 9 this.stage = new Konva.Stage({ 10 container: containerId, 11 width: this.container.clientWidth, 12 height: this.container.clientHeight, 13 }); 14 15 // TODO AKO DRAGNIT NEKOJ OD POCETOK NA STAGE POZICIIVE KE SA ZEZNAT 16 // TODO jwt vo cookie 17 // TODO placed shape i mouseMoveHandler da ne callback ( da ne vrakjat funkcija) 18 19 this.gridLayer = new Konva.Layer(); 20 this.mainLayer = new Konva.Layer(); 21 this.dragLayer = new Konva.Layer(); 22 this.infoPinLayer = new Konva.Layer(); 23 this.textLayer = new Konva.Layer(); 24 this.gridLayer.listening(false); 25 26 this.originalWidth = this.container.clientWidth; 27 this.originalHeight = this.container.clientHeight; 28 29 this.shapes = []; 30 this.blockSize = 10; 31 this.efficientDrawingMode = false; 32 this.roomTypes = []; 33 34 this.gridLine = new Konva.Line({ 35 points: [], 36 stroke: "grey", 37 strokeWidth: 1, 38 opacity: 0.3, 39 }); 40 41 this.gridLine.cache(); 42 43 this.mainTransformer = new Konva.Transformer({ 44 centeredScaling: false, 45 rotationSnaps: [0, 90, 180, 270], 46 anchorSize: 5, 47 padding: 2, 48 anchorFill: "#ef7539", 49 borderStroke: "black", 50 anchorStroke: "black", 51 cornerRadius: 20, 52 anchorCornerRadius: 10, 53 anchorDragBoundFunc: this.transformerSnapFunc(), 54 }); 55 56 this.selectionRectangle = new Konva.Rect({ 57 fill: "rgba(200,0,255,0.5)", 58 visible: false, 59 listening: false, 60 zIndex: 100, 61 }); 62 63 this.x1 = 0; 64 this.y1 = 0; 65 this.x2 = 0; 66 this.y2 = 0; 67 68 this.selecting = false; 69 70 this.initialize(); 71 } 72 73 initialize() { 74 this.drawGrid(); 75 this.mainLayer.add(this.mainTransformer); 76 this.mainLayer.add(this.selectionRectangle); 77 this.stage.add(this.gridLayer); 78 this.stage.add(this.dragLayer); 79 this.stage.add(this.mainLayer); 80 this.stage.add(this.infoPinLayer); 81 this.stage.add(this.textLayer); 82 this.setupEventListeners(); 83 } 84 85 setupEventListeners() { 86 document.getElementById("shapeOptions").addEventListener("click", this.selectShape.bind(this)); 87 document.getElementById("render-button").addEventListener("click", this.render.bind(this)); 88 window.addEventListener("keydown", this.handleExitSelection.bind(this)); 89 window.addEventListener("keydown", this.handleDelete.bind(this)); 90 window.addEventListener("keydown", this.rotateShapesBy90Deg.bind(this)); 91 window.addEventListener("keydown", this.toggleEfficientDrawingMode.bind(this)); 92 window.addEventListener("resize", this.handleResize.bind(this)); 93 this.stage.on("mousedown touchstart", this.handleMouseDown.bind(this)); 94 this.stage.on("mousemove touchmove", this.handleMouseMove.bind(this)); 95 this.stage.on("mouseup touchend", this.handleMouseUp.bind(this)); 96 this.stage.on("click tap", this.handleStageClick.bind(this)); 97 this.stage.on("contextmenu", this.placeInfoPin.bind(this)); 98 this.stage.on("dragmove", this.dragStage.bind(this)); 99 this.stage.on("wheel", this.zoom.bind(this)); 100 } 101 102 detachKeyPressEventListeners() { 103 window.removeEventListener("keydown", this.handleExitSelection.bind(this)); 104 window.removeEventListener("keydown", this.handleDelete.bind(this)); 105 window.removeEventListener("keydown", this.rotateShapesBy90Deg.bind(this)); 106 window.removeEventListener("keydown", this.toggleEfficientDrawingMode.bind(this)); 107 } 108 attachKeyPressEventListeners() { 109 window.addEventListener("keydown", this.handleExitSelection.bind(this)); 110 window.addEventListener("keydown", this.handleDelete.bind(this)); 111 window.addEventListener("keydown", this.rotateShapesBy90Deg.bind(this)); 112 window.addEventListener("keydown", this.toggleEfficientDrawingMode.bind(this)); 113 } 114 115 dragStage(e) { 116 if (!e.evt.shiftKey) return; 117 this.drawGrid(); 118 } 119 120 transformerSnapFunc() { 121 return (oldPos, newPos) => { 122 const snapDistance = 8; 123 124 if (this.mainTransformer.getActiveAnchor() === "rotater") { 125 return newPos; 126 } 127 128 const distance = Math.sqrt(Math.pow(newPos.x - oldPos.x, 2) + Math.pow(newPos.y - oldPos.y, 2)); 129 130 if (distance > snapDistance) { 131 return newPos; 132 } 133 134 const nextX = Math.round(newPos.x / this.blockSize) * this.blockSize; 135 const diffX = Math.abs(newPos.x - nextX); 136 137 const nextY = Math.round(newPos.y / this.blockSize) * this.blockSize; 138 const diffY = Math.abs(newPos.y - nextY); 139 140 const snapToX = diffX < snapDistance; 141 const snapToY = diffY < snapDistance; 142 143 if (snapToX && !snapToY) { 14 constructor(containerId,floorNum,mapName) { 15 this.container = document.getElementById(containerId); 16 this.stage = new Konva.Stage({ 17 container: containerId, 18 width: this.container.clientWidth, 19 height: this.container.clientHeight, 20 }); 21 22 // TODO AKO DRAGNIT NEKOJ OD POCETOK NA STAGE POZICIIVE KE SA ZEZNAT 23 // TODO jwt vo cookie 24 // TODO placed shape i mouseMoveHandler da ne callback ( da ne vrakjat funkcija) 25 // TODO text na top layer sekogas 26 27 this._floorNum = floorNum; 28 this.mapName = mapName; 29 30 this.gridLayer = new Konva.Layer(); 31 this.mainLayer = new Konva.Layer(); 32 this.dragLayer = new Konva.Layer(); 33 this.infoPinLayer = new Konva.Layer(); 34 this.prioLayer = new Konva.Layer(); 35 this.textLayer = new Konva.Layer(); 36 this.gridLayer.listening(false); 37 38 39 this.othStairs = []; 40 41 this.blockSize = 10; 42 this.efficientDrawingMode = false; 43 this.roomTypes = []; 44 45 this.gridLine = new Konva.Line({ 46 points: [], 47 stroke: "grey", 48 strokeWidth: 1, 49 opacity: 0.3, 50 }); 51 52 this.gridLine.cache(); 53 54 this.mainTransformer = new Konva.Transformer({ 55 centeredScaling: false, 56 rotationSnaps: [0, 90, 180, 270], 57 anchorSize: 5, 58 padding: 2, 59 anchorFill: "#f6031f", 60 borderStroke: "black", 61 anchorStroke: "black", 62 cornerRadius: 20, 63 anchorCornerRadius: 10, 64 anchorDragBoundFunc: this.transformerSnapFunc(), 65 }); 66 67 this.selectionRectangle = new Konva.Rect({ 68 fill: "rgba(56,194,245,0.5)", 69 visible: false, 70 listening: false, 71 zIndex: 100, 72 }); 73 74 this.x1 = 0; 75 this.y1 = 0; 76 this.x2 = 0; 77 this.y2 = 0; 78 79 this.selecting = false; 80 81 this.initialize(); 82 } 83 84 initialize() { 85 this.drawGrid(); 86 this.mainLayer.add(this.mainTransformer); 87 this.mainLayer.add(this.selectionRectangle); 88 this.stage.add(this.gridLayer); 89 this.stage.add(this.dragLayer); 90 this.stage.add(this.mainLayer); 91 this.stage.add(this.infoPinLayer); 92 this.stage.add(this.textLayer); 93 this.setupEventListeners(); 94 } 95 96 setupEventListeners() { 97 document.getElementById("shapeOptions").addEventListener("click", this.selectShape.bind(this)); 98 window.addEventListener("keydown", this.handleExitSelection.bind(this)); 99 window.addEventListener("keydown", this.handleDelete.bind(this)); 100 window.addEventListener("keydown", this.rotateShapesBy90Deg.bind(this)); 101 window.addEventListener("keydown", this.toggleEfficientDrawingMode.bind(this)); 102 window.addEventListener("resize", this.handleResize.bind(this)); 103 104 this.boundEscapeEventListener = this.handleExitSelection.bind(this); 105 this.boundDeleteEventListener = this.handleDelete.bind(this); 106 this.boundRotateShapeEventListener = this.rotateShapesBy90Deg.bind(this) 107 this.boundEfficientDrawingModeEventListener = this.toggleEfficientDrawingMode.bind(this); 108 109 //this.attachKeyPressEventListeners(); 110 111 this.stage.on("mousedown touchstart", this.handleMouseDown.bind(this)); 112 this.stage.on("mousemove touchmove", this.handleMouseMove.bind(this)); 113 this.stage.on("mouseup touchend", this.handleMouseUp.bind(this)); 114 this.stage.on("click tap", this.handleStageClick.bind(this)); 115 this.stage.on("contextmenu", this.placeInfoPin.bind(this)); 116 this.stage.on("dragmove", this.dragStage.bind(this)); 117 this.stage.on("wheel", this.zoom.bind(this)); 118 } 119 120 detachKeyPressEventListeners() { 121 window.removeEventListener("keydown", this.boundEscapeEventListener); 122 window.removeEventListener("keydown", this.boundDeleteEventListener); 123 window.removeEventListener("keydown", this.boundRotateShapeEventListener); 124 window.removeEventListener("keydown", this.boundEfficientDrawingModeEventListener); 125 } 126 127 attachKeyPressEventListeners() { 128 window.addEventListener("keydown", this.boundEscapeEventListener); 129 window.addEventListener("keydown", this.boundDeleteEventListener); 130 window.addEventListener("keydown", this.boundRotateShapeEventListener); 131 window.addEventListener("keydown", this.boundEfficientDrawingModeEventListener); 132 } 133 134 dragStage(e) { 135 if (!e.evt.shiftKey) return; 136 this.drawGrid(); 137 } 138 139 transformerSnapFunc() { 140 return (oldPos, newPos) => { 141 const snapDistance = 8; 142 143 if (this.mainTransformer.getActiveAnchor() === "rotater") { 144 return newPos; 145 } 146 147 const distance = Math.sqrt(Math.pow(newPos.x - oldPos.x, 2) + Math.pow(newPos.y - oldPos.y, 2)); 148 149 if (distance > snapDistance) { 150 return newPos; 151 } 152 153 const nextX = Math.round(newPos.x / this.blockSize) * this.blockSize; 154 const diffX = Math.abs(newPos.x - nextX); 155 156 const nextY = Math.round(newPos.y / this.blockSize) * this.blockSize; 157 const diffY = Math.abs(newPos.y - nextY); 158 159 const snapToX = diffX < snapDistance; 160 const snapToY = diffY < snapDistance; 161 162 if (snapToX && !snapToY) { 163 return { 164 x: nextX, 165 y: oldPos.y, 166 }; 167 } else if (!snapToX && snapToY) { 168 return { 169 x: oldPos.x, 170 y: nextY, 171 }; 172 } else if (snapToX && snapToY) { 173 return { 174 x: nextX, 175 y: nextY, 176 }; 177 } 178 179 return newPos; 180 }; 181 } 182 183 handleResize() { 184 this.stage.width(this.container.offsetWidth); 185 this.stage.height(this.container.offsetHeight); 186 this.drawGrid(); 187 } 188 189 zoom(e) { 190 zoomStage(e, this.stage, true); 191 this.drawGrid(); 192 } 193 194 get floorNum(){ 195 return this._floorNum; 196 } 197 198 set floorNum(val){ 199 this._floorNum = val; 200 } 201 202 drawGrid() { 203 this.gridLayer.destroyChildren(); 204 205 let width = this.stage.width(); 206 let height = this.stage.height(); 207 208 //presmetka od globalen koordinaten sistem vo lokalen na canvasot 209 let transform = this.stage.getAbsoluteTransform().copy().invert(); 210 let topLeft = transform.point({ 211 x: 0, 212 y: 0, 213 }); 214 215 let bottomRight = transform.point({ 216 x: width, 217 y: height, 218 }); 219 220 let startX = Math.floor(topLeft.x / this.blockSize) * this.blockSize; 221 let startY = Math.floor(topLeft.y / this.blockSize) * this.blockSize; 222 223 let endX = Math.ceil(bottomRight.x / this.blockSize) * this.blockSize; 224 let endY = Math.ceil(bottomRight.y / this.blockSize) * this.blockSize; 225 226 for (let x = startX; x <= endX; x += this.blockSize) { 227 let line = this.gridLine.clone({ 228 points: [x + 0.5, topLeft.y - this.blockSize, x + 0.5, bottomRight.y + this.blockSize], 229 }); 230 231 line.transformsEnabled("position"); 232 line.perfectDrawEnabled(false); 233 line.shadowForStrokeEnabled(false); 234 235 this.gridLayer.add(line); 236 } 237 238 for (let y = startY; y <= endY; y += this.blockSize) { 239 let line = this.gridLine.clone({ 240 points: [topLeft.x - this.blockSize, y + 0.5, bottomRight.x + this.blockSize, y + 0.5], 241 }); 242 243 line.perfectDrawEnabled(false); 244 line.shadowForStrokeEnabled(false); 245 line.transformsEnabled("position"); 246 this.gridLayer.add(line); 247 } 248 249 this.mainLayer.moveToTop(); 250 this.infoPinLayer.moveToTop(); 251 252 this.gridLayer.batchDraw(); 253 } 254 255 placeInfoPin(e) { 256 e.evt.preventDefault(); 257 let mousePos = this.stage.getRelativePointerPosition(); 258 const attrs = { 259 type: "InfoPin", 260 position: mousePos, 261 blockSize: this.blockSize, 262 layer: this.mainLayer, 263 rotation: 0, 264 scaleX: 1, 265 scaleY: 1, 266 increment: true, 267 floorNum: this.floorNum 268 }; 269 let infoPin = Factory.createShape("InfoPin", attrs); 270 addEventHandling(infoPin, this, "dblclick"); 271 //this.shapes.push(infoPin); 272 ShapeRegistry.add(infoPin) 273 this.mainLayer.add(infoPin); 274 infoPin.displayName(this.textLayer); 275 triggerMapSave() 276 277 console.log(infoPin.name()); 278 } 279 280 toggleEfficientDrawingMode(e) { 281 if (e.key === "e" || e.key === "E") { 282 this.efficientDrawingMode = !this.efficientDrawingMode; 283 console.log("EFFICIENT DRAWING MODE is: ", this.efficientDrawingMode); 284 285 if (!this.efficientDrawingMode) { 286 this.stopDrawing(); 287 } 288 } 289 } 290 291 placeShape() { 292 const mousePos = this.stage.getRelativePointerPosition(); 293 const attrs = { 294 position: mousePos, 295 width: this.blockSize, 296 height: this.blockSize, 297 layer: this.mainLayer, 298 rotation: this.hoverObj.rotation(), 299 scaleX: 1, 300 scaleY: 1, 301 increment: true, 302 snap: true, 303 fromLoad: false, 304 blockSize: this.blockSize, 305 floorNum: this.floorNum 306 }; 307 308 const placedObj = Factory.createShape(this.hoverObj.type, attrs); 309 if (!placedObj) return; 310 311 console.info("ATTRS FNUM",attrs.floorNum) 312 313 this.mainLayer.add(placedObj); 314 //this.shapes.push(placedObj); 315 console.log("VO PLACED SHAEPS WALL ZITI SE: " + placedObj.className); 316 ShapeRegistry.add(placedObj); 317 addEventHandling(placedObj, this, "dblclick"); 318 this.mainLayer.draw(); 319 320 // site ovie func da se vo edna funkcija vo shape. 321 322 placedObj.displayName(this.textLayer); 323 placedObj.snapToGrid(); 324 325 triggerMapSave(); 326 327 if (!this.efficientDrawingMode) { 328 this.stopDrawing(); 329 } 330 } 331 332 stopDrawing() { 333 this.mainTransformer.nodes([]); 334 if (this.hoverObj != null) this.hoverObj.remove(); 335 this.dragLayer.removeChildren(); 336 this.stage.off("mousemove", this.boundMouseMoveHandler); 337 this.stage.off("click", this.boundPlaceShapeHandler); 338 } 339 340 mouseMoveHandler() { 341 const mousePos = this.stage.getRelativePointerPosition(); 342 this.hoverObj.position({x: mousePos.x, y: mousePos.y}); 343 this.hoverObj.visible(true); 344 } 345 346 startDrawing(shapeType) { 347 const attrs = { 348 position: {x: 0, y: 0}, 349 width: this.blockSize, 350 height: this.blockSize, 351 layer: this.mainLayer, 352 rotation: 0, 353 scaleX: 1, 354 scaleY: 1, 355 increment: false, 356 snap: true, 357 fromLoad: false, 358 blockSize: this.blockSize 359 }; 360 this.hoverObj = Factory.createShape(shapeType, attrs); 361 362 console.log("HOVBER OBK:", this.hoverObj) 363 364 this.hoverObj.visible(false); 365 this.dragLayer.add(this.hoverObj); 366 this.dragLayer.moveToTop(); 367 this.boundMouseMoveHandler = this.mouseMoveHandler.bind(this); 368 this.boundPlaceShapeHandler = this.placeShape.bind(this); 369 370 this.stage.on("mousemove", this.boundMouseMoveHandler); 371 this.stage.on("click", this.boundPlaceShapeHandler); 372 } 373 374 selectShape(e) { 375 if (e.target.tagName === "LI") { 376 const shapeType = e.target.getAttribute("data-info"); 377 this.startDrawing(shapeType); 378 this.mainTransformer.nodes([]); 379 } 380 } 381 382 rotateShapesBy90Deg(e) { 383 if (e.key === "r" || e.key === "R") { 384 if (this.hoverObj) { 385 this.hoverObj.rotate(90); 386 } 387 this.mainTransformer.nodes().forEach((node) => { 388 node.rotate(90); 389 }); 390 } 391 } 392 393 handleDelete(e) { 394 if (e.key === "Delete") { 395 this.mainTransformer.nodes().forEach((node) => { 396 node.remove(); 397 node.destroy(); 398 ShapeRegistry.delete(node); 399 triggerMapSave(); 400 }); 401 this.mainTransformer.nodes([]); 402 this.mainLayer.batchDraw(); 403 } 404 } 405 406 handleExitSelection(e) { 407 if (e.key === "Escape") { 408 this.mainTransformer.nodes([]); 409 this.stopDrawing(); 410 } 411 } 412 413 handleMouseDown(e) { 414 this.stage.draggable(e.evt.shiftKey); 415 416 if (e.target !== this.stage) { 417 return; 418 } 419 420 e.evt.preventDefault(); 421 this.x1 = this.stage.getRelativePointerPosition().x; 422 this.y1 = this.stage.getRelativePointerPosition().y; 423 this.x2 = this.stage.getRelativePointerPosition().x; 424 this.y2 = this.stage.getRelativePointerPosition().y; 425 426 this.selectionRectangle.width(0); 427 this.selectionRectangle.height(0); 428 this.selecting = true; 429 } 430 431 handleMouseMove(e) { 432 if (!this.selecting) { 433 return; 434 } 435 e.evt.preventDefault(); 436 this.x2 = this.stage.getRelativePointerPosition().x; 437 this.y2 = this.stage.getRelativePointerPosition().y; 438 439 this.selectionRectangle.setAttrs({ 440 visible: true, 441 x: Math.min(this.x1, this.x2), 442 y: Math.min(this.y1, this.y2), 443 width: Math.abs(this.x2 - this.x1), 444 height: Math.abs(this.y2 - this.y1), 445 }); 446 } 447 448 handleMouseUp(e) { 449 this.selecting = false; 450 this.stage.draggable(false); 451 452 if (!this.selectionRectangle.visible()) { 453 return; 454 } 455 456 e.evt.preventDefault(); 457 this.selectionRectangle.visible(false); 458 const shapes = this.stage.find(".mapObj"); 459 const box = this.selectionRectangle.getClientRect(); 460 const selected = shapes.filter((shape) => Konva.Util.haveIntersection(box, shape.getClientRect())); 461 this.mainTransformer.nodes(selected); 462 console.log(this.mainTransformer.nodes()); 463 } 464 465 saveShapeDetails() { 466 // this.shapes.forEach(shape => { 467 // shape.saveShapeDetails(); 468 // console.log(shape.info); 469 // }); 470 ShapeRegistry.saveDetails(); 471 console.log("thisflornum",this.floorNum) 472 return { 473 shapes: ShapeRegistry.getShapes(this.floorNum), 474 roomTypes: JSON.stringify(this.roomTypes), 475 mapName: this.mapName, 476 floorNum: this.floorNum 477 } 478 } 479 480 getPayload(){ 481 this.saveShapeDetails(); 144 482 return { 145 x: nextX, 146 y: oldPos.y, 147 }; 148 } else if (!snapToX && snapToY) { 149 return { 150 x: oldPos.x, 151 y: nextY, 152 }; 153 } else if (snapToX && snapToY) { 154 return { 155 x: nextX, 156 y: nextY, 157 }; 158 } 159 160 return newPos; 161 }; 162 } 163 164 handleResize() { 165 this.stage.width(this.container.offsetWidth); 166 this.stage.height(this.container.offsetHeight); 167 this.drawGrid(); 168 } 169 170 zoom(e) { 171 zoomStage(e,this.stage); 172 this.drawGrid(); 173 } 174 175 drawGrid() { 176 this.gridLayer.destroyChildren(); 177 178 let width = this.stage.width(); 179 let height = this.stage.height(); 180 181 //presmetka od globalen koordinaten sistem vo lokalen na canvasot 182 let transform = this.stage.getAbsoluteTransform().copy().invert(); 183 let topLeft = transform.point({ 184 x: 0, 185 y: 0, 186 }); 187 188 let bottomRight = transform.point({ 189 x: width, 190 y: height, 191 }); 192 193 let startX = Math.floor(topLeft.x / this.blockSize) * this.blockSize; 194 let startY = Math.floor(topLeft.y / this.blockSize) * this.blockSize; 195 196 let endX = Math.ceil(bottomRight.x / this.blockSize) * this.blockSize; 197 let endY = Math.ceil(bottomRight.y / this.blockSize) * this.blockSize; 198 199 for (let x = startX; x <= endX; x += this.blockSize) { 200 let line = this.gridLine.clone({ 201 points: [x + 0.5, topLeft.y - this.blockSize, x + 0.5, bottomRight.y + this.blockSize], 202 }); 203 204 line.transformsEnabled("position"); 205 line.perfectDrawEnabled(false); 206 line.shadowForStrokeEnabled(false); 207 208 this.gridLayer.add(line); 209 } 210 211 for (let y = startY; y <= endY; y += this.blockSize) { 212 let line = this.gridLine.clone({ 213 points: [topLeft.x - this.blockSize, y + 0.5, bottomRight.x + this.blockSize, y + 0.5], 214 }); 215 216 line.perfectDrawEnabled(false); 217 line.shadowForStrokeEnabled(false); 218 line.transformsEnabled("position"); 219 this.gridLayer.add(line); 220 } 221 222 this.mainLayer.moveToTop(); 223 this.infoPinLayer.moveToTop(); 224 225 this.gridLayer.batchDraw(); 226 } 227 228 placeInfoPin(e) { 229 e.evt.preventDefault(); 230 let mousePos = this.stage.getRelativePointerPosition(); 231 let infoPin = Factory.createShape("InfoPin", mousePos, this.blockSize, this.mainLayer, 0,1,1,true); 232 this.addModalHandling(infoPin); 233 this.shapes.push(infoPin); 234 this.mainLayer.add(infoPin) 235 infoPin.displayName(this.textLayer); 236 console.log(infoPin.name()); 237 } 238 239 toggleEfficientDrawingMode(e) { 240 if (e.key === "e" || e.key === "E") { 241 this.efficientDrawingMode = !this.efficientDrawingMode; 242 console.log("EFFICIENT DRAWING MODE is: ", this.efficientDrawingMode); 243 244 if (!this.efficientDrawingMode) { 245 this.stopDrawing(); 246 } 247 } 248 } 249 250 placeShape() { 251 const mousePos = this.stage.getRelativePointerPosition(); 252 const placedObj = Factory.createShape( 253 this.hoverObj.type, 254 mousePos, 255 this.blockSize, 256 this.mainLayer, 257 this.hoverObj.rotation(), 258 1, 259 1, 260 true 261 ); 262 263 if (!placedObj) return; 264 265 this.mainLayer.add(placedObj); 266 this.shapes.push(placedObj); 267 this.addModalHandling(placedObj); 268 this.mainLayer.draw(); 269 placedObj.displayName(this.textLayer); 270 placedObj.snapToGrid(); 271 272 if (!this.efficientDrawingMode) { 273 this.stopDrawing(); 274 } 275 } 276 277 stopDrawing() { 278 this.mainTransformer.nodes([]); 279 this.hoverObj.remove(); 280 this.dragLayer.removeChildren(); 281 this.stage.off("mousemove", this.boundMouseMoveHandler); 282 this.stage.off("click", this.boundPlaceShapeHandler); 283 } 284 285 mouseMoveHandler() { 286 const mousePos = this.stage.getRelativePointerPosition(); 287 this.hoverObj.position({ x: mousePos.x, y: mousePos.y }); 288 this.hoverObj.visible(true); 289 } 290 291 startDrawing(shapeType) { 292 let pos = { x: 0, y: 0 }; 293 this.hoverObj = Factory.createShape(shapeType, pos, this.blockSize, this.dragLayer, 0); 294 295 this.hoverObj.visible(false); 296 this.dragLayer.add(this.hoverObj); 297 this.dragLayer.moveToTop(); 298 this.boundMouseMoveHandler = this.mouseMoveHandler.bind(this); 299 this.boundPlaceShapeHandler = this.placeShape.bind(this); 300 301 this.stage.on("mousemove", this.boundMouseMoveHandler); 302 this.stage.on("click", this.boundPlaceShapeHandler); 303 } 304 305 selectShape(e) { 306 if (e.target.tagName === "LI") { 307 const shapeType = e.target.getAttribute("data-info"); 308 this.startDrawing(shapeType); 309 this.mainTransformer.nodes([]); 310 } 311 } 312 313 addModalHandling(shape) { 314 shape.on("dblclick", () => { 315 const eventName = shape.modalEventName; 316 if (eventName) { 317 const data = { 318 room: shape, 319 map: this, 320 }; 321 const event = new CustomEvent(eventName, { detail: data }); 322 window.dispatchEvent(event); 323 } 324 }); 325 } 326 327 rotateShapesBy90Deg(e) { 328 if (e.key === "r" || e.key === "R") { 329 if (this.hoverObj) { 330 this.hoverObj.rotate(90); 331 } 332 this.mainTransformer.nodes().forEach((node) => { 333 node.rotate(90); 334 }); 335 } 336 } 337 338 handleDelete(e) { 339 if (e.key === "Delete") { 340 this.mainTransformer.nodes().forEach((node) => { 341 node.remove(); 342 this.shapes.splice(this.shapes.indexOf(node), 1); 343 }); 344 this.mainTransformer.nodes([]); 345 this.mainLayer.batchDraw(); 346 } 347 } 348 349 handleExitSelection(e) { 350 if (e.key === "Escape") { 351 this.mainTransformer.nodes([]); 352 this.stopDrawing(); 353 } 354 } 355 356 handleMouseDown(e) { 357 this.stage.draggable(e.evt.shiftKey); 358 359 if (e.target !== this.stage) { 360 return; 361 } 362 363 e.evt.preventDefault(); 364 this.x1 = this.stage.getRelativePointerPosition().x; 365 this.y1 = this.stage.getRelativePointerPosition().y; 366 this.x2 = this.stage.getRelativePointerPosition().x; 367 this.y2 = this.stage.getRelativePointerPosition().y; 368 369 this.selectionRectangle.width(0); 370 this.selectionRectangle.height(0); 371 this.selecting = true; 372 } 373 374 handleMouseMove(e) { 375 if (!this.selecting) { 376 return; 377 } 378 e.evt.preventDefault(); 379 this.x2 = this.stage.getRelativePointerPosition().x; 380 this.y2 = this.stage.getRelativePointerPosition().y; 381 382 this.selectionRectangle.setAttrs({ 383 visible: true, 384 x: Math.min(this.x1, this.x2), 385 y: Math.min(this.y1, this.y2), 386 width: Math.abs(this.x2 - this.x1), 387 height: Math.abs(this.y2 - this.y1), 388 }); 389 } 390 391 handleMouseUp(e) { 392 this.selecting = false; 393 this.stage.draggable(false); 394 395 if (!this.selectionRectangle.visible()) { 396 return; 397 } 398 399 e.evt.preventDefault(); 400 this.selectionRectangle.visible(false); 401 const shapes = this.stage.find(".mapObj"); 402 const box = this.selectionRectangle.getClientRect(); 403 const selected = shapes.filter((shape) => Konva.Util.haveIntersection(box, shape.getClientRect())); 404 this.mainTransformer.nodes(selected); 405 console.log(this.mainTransformer.nodes()); 406 } 407 408 saveShapeDetails() { 409 this.shapes.forEach((room) => { 410 room.saveShapeDetails(); 411 console.log(room.info); 412 }); 413 } 414 415 async render() { 416 this.saveShapeDetails(); 417 const httpService = new HttpService("http://localhost:8080/api/protected", true); 418 try { 419 const response = await httpService.post("/render", this.shapes); 420 console.log(response); 421 } catch (err) { 422 console.log("ERROR --> Could not render map --->", err); 423 } 424 } 425 426 async saveMap(mapName) { 427 this.saveShapeDetails(); 428 const httpService = new HttpService("http://localhost:8080/api/protected/maps", true); 429 try { 430 const response = await httpService.put(`/save?mapName=${mapName}`, this.shapes); 431 console.log(response, "resp in builder"); 432 } catch (err) { 433 console.log("ERROR --> Could not Save map --->", err); 434 } 435 } 436 437 handleStageClick(e) { 438 if (this.selectionRectangle.visible()) { 439 return; 440 } 441 442 if (e.target === this.stage) { 443 this.mainTransformer.nodes([]); 444 return; 445 } 446 447 if (!e.target.hasName("mapObj")) { 448 return; 449 } 450 451 const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey; 452 const isSelected = this.mainTransformer.nodes().indexOf(e.target) >= 0; 453 454 if (!metaPressed && !isSelected) { 455 this.mainTransformer.nodes([e.target]); 456 console.log("Sel 1"); 457 } else if (metaPressed && isSelected) { 458 const nodes = this.mainTransformer.nodes().slice(); 459 nodes.splice(nodes.indexOf(e.target), 1); 460 this.mainTransformer.nodes(nodes); 461 } else if (metaPressed && !isSelected) { 462 const nodes = this.mainTransformer.nodes().concat([e.target]); 463 this.mainTransformer.nodes(nodes); 464 } 465 } 466 467 addRoomType(roomType) { 468 this.roomTypes.push(roomType); 469 } 470 471 getRoomTypes() { 472 return this.roomTypes; 473 } 474 475 getRooms() { 476 return this.getShapeInfoByType("Room"); 477 } 478 479 getPins() { 480 return this.getShapeInfoByType("InfoPin"); 481 } 482 483 getEntrances() { 484 return this.getShapeInfoByType("Entrance"); 485 } 486 487 getConnections() { 488 const pins = this.getShapeInfoByType("InfoPin"); 489 const entrances = this.getShapeInfoByType("Entrance"); 490 return [...pins, ...entrances]; 491 } 492 493 getShapeInfoByType(type) { 494 return this.shapes.filter((shape) => shape.className === type).map((shape) => shape.info); 495 } 496 497 updateConnections() { 498 console.log("Update"); 499 500 this.shapes.forEach((shape) => { 501 if (shape.className === "InfoPin" || shape.className === "Entrance") { 502 shape.info.selectedPins.forEach((connectedShapeName) => { 503 const connectedShape = this.shapes.find((s) => s.info.name === connectedShapeName); 504 if ( 505 connectedShape && 506 (connectedShape.className === "InfoPin" || connectedShape.className === "Entrance") 507 ) { 508 if (!connectedShape.info.selectedPins.includes(shape.info.name)) { 509 connectedShape.info.selectedPins.push(shape.info.name); 510 } 511 } 512 }); 513 } 514 }); 515 } 516 517 removeConnection(from, to) { 518 this.shapes 519 .filter((s) => s.info.name === from || s.info.name === to) 520 .forEach((s) => { 521 s.info.selectedPins = s.info.selectedPins.filter((pin) => pin !== from && pin !== to); 522 }); 523 console.log("Remove"); 524 } 525 526 updateRoomNames() { 527 this.textLayer.removeChildren(); 528 this.shapes.forEach((shape) => { 529 shape.displayName(this.textLayer); 530 }); 531 this.textLayer.children.forEach((child) => console.log(child)); 532 } 533 534 clearMap() { 535 this.mainLayer.removeChildren(); 536 this.shapes = []; 537 this.hoverObj = null; 538 } 539 540 deserializeMap(data) { 541 console.log("DESERIALIZING: ", data); 542 this.clearMap(); 543 let dsrData = JSON.parse(data); 544 dsrData.forEach((child) => { 545 const shape = JSON.parse(child); 546 const loadedShape = Factory.createShape( 547 shape.className, 548 { x: shape.attrs.x, y: shape.attrs.y }, 549 this.blockSize, 550 this.mainLayer, 551 shape.attrs.rotation, 552 shape.attrs.scaleX, 553 shape.attrs.scaleY 554 ); 555 loadedShape.loadInfo(shape.attrs); 556 this.shapes.push(loadedShape); 557 this.addModalHandling(loadedShape); 558 this.mainLayer.add(loadedShape); 559 }); 560 this.mainTransformer.nodes([]); 561 this.mainLayer.add(this.mainTransformer); 562 this.mainLayer.add(this.selectionRectangle); 563 564 this.shapes.forEach((shape) => shape.displayName(this.textLayer)); 565 } 483 shapes: ShapeRegistry.getShapes(this.floorNum), 484 roomTypes: JSON.stringify(this.roomTypes), 485 mapName: this.mapName, 486 floorNum: this.floorNum 487 } 488 } 489 490 491 handleStageClick(e) { 492 if (this.selectionRectangle.visible()) { 493 return; 494 } 495 496 if (e.target === this.stage) { 497 this.mainTransformer.nodes([]); 498 return; 499 } 500 501 if (!e.target.hasName("mapObj")) { 502 return; 503 } 504 505 const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey; 506 const isSelected = this.mainTransformer.nodes().indexOf(e.target) >= 0; 507 508 if (!metaPressed && !isSelected) { 509 this.mainTransformer.nodes([e.target]); 510 console.log("Sel 1"); 511 } else if (metaPressed && isSelected) { 512 const nodes = this.mainTransformer.nodes().slice(); 513 nodes.splice(nodes.indexOf(e.target), 1); 514 this.mainTransformer.nodes(nodes); 515 } else if (metaPressed && !isSelected) { 516 const nodes = this.mainTransformer.nodes().concat([e.target]); 517 this.mainTransformer.nodes(nodes); 518 } 519 } 520 521 addRoomType(type) { 522 this.roomTypes.push(type); 523 } 524 525 removeRoomType(targetType) { 526 this.roomTypes = this.roomTypes.filter(type => type !== targetType); 527 } 528 529 getRoomTypes() { 530 return this.roomTypes; 531 } 532 533 getRooms() { 534 return this.getShapeInfoByType("Room"); 535 } 536 537 getPins() { 538 return this.getShapeInfoByType("InfoPin"); 539 } 540 541 getEntrances() { 542 return this.getShapeInfoByType("Entrance"); 543 } 544 545 546 547 getShapeInfoByType(type) { 548 return ShapeRegistry.getShapes(this.floorNum).filter((shape) => shape.className === type).map((shape) => shape.info); 549 } 550 551 552 drawConnection(node1Name, node2Name) { 553 554 ShapeRegistry.drawConnection(node1Name,node2Name); 555 } 556 557 getNodeByName(name) { 558 return ShapeRegistry.getShapes(this.floorNum).filter(shape => shape instanceof MapNode && shape.info.name === name)[0]; 559 } 560 561 removeConnection(from, to) { 562 ShapeRegistry.removeConnection(from,to); 563 } 564 565 updateRoomNames() { 566 this.textLayer.removeChildren(); 567 ShapeRegistry.getShapes(this.floorNum).forEach((shape) => { 568 shape.displayName(this.textLayer); 569 }); 570 571 } 572 573 isMainEntranceSelected() { 574 console.log(this.getEntrances().forEach((en) => console.log(en.isMainEntrance, "asdsad"))); 575 576 let hasMainEntrance = false; 577 578 this.getEntrances().forEach((entrance) => { 579 if (entrance.isMainEntrance === true) hasMainEntrance = true; 580 }); 581 582 return hasMainEntrance; 583 584 } 585 586 clearMap() { 587 this.mainLayer.removeChildren(); 588 this.hoverObj = null; 589 } 590 591 592 // ova klasa i map display da nasledbat od glavna klasa 593 594 loadNewFloor(floor) { 595 596 this._floorNum = floor?.num; 597 let data = floor?.mapData; 598 599 if (data == null || data === "") return; 600 601 this.deserializeMap(data); 602 shapeRegistry.getShapes(this.floorNum).forEach((shape) => { 603 this.mainLayer.add(shape); 604 }); 605 606 607 } 608 609 //nov 610 deserializeMap(data) { 611 console.log("DESERIALIZING: ", data); 612 ShapeRegistry.clear(this.floorNum); 613 614 if (data != null) { 615 const dsrData = JSON.parse(data); 616 //load shapes 617 dsrData.forEach((shape) => { 618 const attrs = { 619 position: {x: shape.attrs.x, y: shape.attrs.y}, 620 width: shape.attrs.width, 621 height: shape.attrs.height, 622 layer: this.mainLayer, 623 blockSize: this.blockSize, 624 rotation: shape.attrs.rotation, 625 scaleX: shape.attrs.scaleX, 626 scaleY: shape.attrs.scaleY, 627 increment: false, 628 snap: true, 629 fromLoad: true, 630 floorNum: this.floorNum 631 }; 632 633 const loadedShape = Factory.createShape(shape.className, attrs); 634 loadedShape.loadInfo(shape.attrs); 635 ShapeRegistry.add(loadedShape); 636 // na destroy trebit events da sa trgnat 637 addEventHandling(loadedShape, this, "dblclick"); 638 }); 639 let nodes = ShapeRegistry.getShapes(this.floorNum).filter((shape) => shape.className === "InfoPin" || shape.className === "Entrance" || shape.className === "Stairs"); 640 nodes.forEach((pin) => { 641 let connectedPins = pin.info.selectedPins; 642 if (connectedPins) { 643 connectedPins.forEach((slPin) => { 644 console.log("CONN node1: " + pin + "conn node2: " + slPin) 645 this.drawConnection(pin.info.name, slPin); 646 }); 647 } 648 }); 649 } 650 651 this.mainTransformer.nodes([]); 652 this.mainLayer.add(this.mainTransformer); 653 this.mainLayer.add(this.selectionRectangle); 654 655 ShapeRegistry.getShapes(this.floorNum).forEach((shape) => shape.displayName(this.textLayer)); 656 } 657 566 658 } -
imaps-frontend/src/scripts/main/MapDisplay.js
rd565449 r0c6b92a 3 3 import HttpService from "../net/HttpService.js"; 4 4 import {zoomStage} from "../util/zoomStage.js"; 5 import {addEventHandling} from "../util/addEventHandling.js"; 6 import triggerNavigate from "../util/triggerNavigate.js"; 7 import config from "../net/netconfig.js"; 8 5 9 export class MapDisplay { 6 constructor(containerId) { 7 this.container = document.getElementById(containerId); 8 this.containerId = containerId; 9 this.stage = new Konva.Stage({ 10 container: containerId, 11 width: window.innerWidth, 12 height: window.innerHeight, 13 draggable: true, 14 }); 15 16 this.shapes = []; 17 this.loaded = false; 18 this.mainLayer = new Konva.Layer(); 19 this.routeLayer = new Konva.Layer(); 20 this.textLayer = new Konva.Layer(); 21 this.stage.add(this.mainLayer); 22 this.stage.add(this.routeLayer); 23 this.stage.add(this.textLayer); 24 this.route = new Konva.Line(); 25 26 this.stage.on("resize", () => { 27 this.stage.width = window.innerWidth; 28 this.stage.height = window.innerHeight; 29 }); 30 31 this.stage.on("wheel",(e) => { 32 zoomStage(e,this.stage); 33 }) 34 35 36 } 37 38 deserializeMap(data) { 39 data.forEach((child) => { 40 const shape = JSON.parse(child); 41 if (shape.className !== "InfoPin") { 42 const renderedShape = Factory.createRenderedShape( 43 shape.className, 44 shape.attrs 45 ); 46 this.shapes.push(renderedShape); 47 } 48 }); 49 } 50 51 displayRoomNames(){ 52 console.log("VLEZE") 53 this.shapes.forEach(shape => { 54 shape.displayName(this.textLayer); 55 }) 56 57 this.textLayer.children.forEach(child => console.log(child,"DECAAA")); 58 } 59 60 async loadMap() { 61 const httpService = new HttpService(); 62 const mapData = await httpService.get("/public/mapData"); 63 console.log("DESERIALIZED --->",mapData); 64 this.deserializeMap(mapData); 65 this.shapes.forEach((shape) => { 66 this.mainLayer.add(shape); 67 }); 68 this.displayRoomNames(); 69 } 70 71 drawRoute(path) { 72 this.routeLayer.removeChildren(); 73 console.log("====PATH===="); 74 path.forEach((point) => console.log(point.x, point.y)); 75 76 const pointsArray = path.flatMap((point) => [point.x, point.y]); 77 78 console.log(pointsArray, "POINTS"); 79 80 let buff = []; 81 let count = 0; 82 let index = 0; 83 84 const drawNextSegment = () => { 85 if (index >= pointsArray.length) return; 86 87 buff.push(pointsArray[index]); 88 count++; 89 90 if (count % 4 === 0) { 91 const line = new Konva.Arrow({ 92 points: buff, 93 stroke: "#e91332", 94 strokeWidth: 2.5, 95 dash: [5, 4], 96 lineCap: 'round', 97 lineJoin: 'round', 98 pointerLength: 7, 99 pointerWidth: 7, 100 fill:'red', 101 }); 102 103 this.routeLayer.add(line); 104 this.routeLayer.draw(); 105 106 console.log(buff, "BUFFER"); 107 buff = []; 108 index -= 2; 10 constructor(containerId, floorNum) { 11 this.container = document.getElementById(containerId); 12 this.containerId = containerId; 13 this.stage = new Konva.Stage({ 14 container: containerId, 15 width: window.innerWidth, 16 height: window.innerHeight, 17 draggable: true, 18 }); 19 20 this.shapes = []; 21 this.roomTypes = []; 22 this.loaded = false; 23 this.mainLayer = new Konva.Layer(); 24 this.routeLayer = new Konva.Layer(); 25 this.textLayer = new Konva.Layer(); 26 this.stage.add(this.mainLayer); 27 this.stage.add(this.routeLayer); 28 this.stage.add(this.textLayer); 29 30 this.floorNum = floorNum; 31 32 this.navArrow = new Konva.Arrow({ 33 stroke: "#bb0000", 34 strokeWidth: 3, 35 dash: [12, 7], 36 lineCap: "round", 37 tension: 10, 38 pointerLength: 2, 39 pointerWidth: 3, 40 }); 41 42 this.navArrow.cache(); 43 44 this.stage.on("resize", () => { 45 this.stage.width = window.innerWidth; 46 this.stage.height = window.innerHeight; 47 }); 48 49 this.stage.on("wheel", (e) => { 50 zoomStage(e, this.stage); 51 }); 52 } 53 54 clearMap() { 55 this.mainLayer.removeChildren(); 56 this.shapes.forEach(shape => shape.clearText()) 57 this.shapes = []; 58 } 59 60 deserializeMap(data) { 61 this.clearMap(); 62 63 let dsrData = JSON.parse(data); 64 dsrData.forEach((shape) => { 65 if (shape.className !== "InfoPin") { 66 const renderedShape = Factory.createRenderedShape(shape.className, shape.attrs); 67 addEventHandling(renderedShape, this, "click"); 68 this.shapes.push(renderedShape); 69 } 70 }); 71 } 72 73 displayRoomNames() { 74 this.shapes.forEach((shape) => { 75 shape.displayName(this.textLayer); 76 }); 77 78 } 79 80 81 82 loadMapN(floorData) { 83 if (floorData == null || floorData === "") return; 84 85 this.deserializeMap(floorData); 86 this.shapes.forEach((shape) => { 87 this.mainLayer.add(shape); 88 }); 89 this.displayRoomNames(); 90 this.initializeRoomTypes(); 91 92 } 93 94 clearRoute() { 95 this.routeLayer.removeChildren(); 96 } 97 98 99 drawRouteNEW(nodes, offset = 0) { 100 101 this.clearRoute(); 102 console.log("====PATH===="); 103 nodes.forEach((node) => console.log("NODE", node)); 104 105 let idx = offset; 106 let buff = [nodes[idx].coordinates.x, nodes[idx].coordinates.y]; 107 108 109 ++idx; 110 111 console.log("INIT BUFFER", buff); 112 console.log("INIT IDX", idx); 113 114 const drawNextSegment = () => { 115 116 if (idx >= nodes.length){ 117 return; 118 } 119 120 const currentNode = nodes[idx - 1]; 121 const nextNode = nodes[idx]; 122 123 if (nextNode.floorNumber !== currentNode.floorNumber) { 124 triggerNavigate(nodes, idx, nextNode.floorNumber, nextNode); 125 return; 126 } 127 128 const startX = currentNode.coordinates.x; 129 const startY = currentNode.coordinates.y; 130 const endX = nextNode.coordinates.x; 131 const endY = nextNode.coordinates.y; 132 133 const numSegments = 12; 134 135 const deltaX = (endX - startX) / numSegments; 136 const deltaY = (endY - startY) / numSegments; 137 138 const drawSegment = (i) => { 139 const segmentX = startX + deltaX * i; 140 const segmentY = startY + deltaY * i; 141 142 buff.push(segmentX, segmentY); 143 144 let line = this.navArrow.clone({ points: [...buff] }); 145 this.routeLayer.add(line); 146 this.routeLayer.draw(); 147 148 buff = [segmentX, segmentY]; 149 }; 150 151 let segmentIdx = 1; 152 const interval = setInterval(() => { 153 drawSegment(segmentIdx); 154 segmentIdx++; 155 156 if (segmentIdx > numSegments) { 157 clearInterval(interval); 158 idx++; 159 setTimeout(drawNextSegment, 150); 160 } 161 }, 50); 162 }; 163 164 drawNextSegment(); 165 } 166 167 168 169 initializeRoomTypes() { 170 this.roomTypes = this.shapes 171 .filter((shape) => shape.class === "Room" && shape.info.type !== "") 172 .map((shape) => shape.info.type); 173 } 174 175 getRoomTypes() { 176 return this.roomTypes; 177 } 178 179 180 getShapeByName(name){ 181 return this.shapes.find(shape => shape.info.name === name) 182 } 183 184 getShapeByType(type) { 185 return this.shapes.filter((shape) => shape.class === type) 186 } 187 188 toggleSearchRoom() { 189 this.toggleSearch = !this.toggleSearch; 190 } 191 192 //ova e loso ne trebit vaka 193 highlightShape(roomName) { 194 let foundShape = this.shapes.filter((shape) => shape.info.name === roomName)[0]; 195 foundShape.highlight(); 196 } 197 198 getMainEntrance() { 199 return this.shapes.filter(shape => shape.class === "Entrance").filter(el => el.info.isMainEntrance === true)[0]; 200 } 201 202 setFilter(filter) { 203 let rooms = this.getShapeByType("Room") 204 if (filter === "All") { 205 rooms.forEach((shape) => { 206 shape.unHighlight() 207 }) 208 } else { 209 rooms.filter((shape) => shape.info.type === filter).forEach((shape) => { 210 shape.highlight() 211 }) 212 rooms.filter((shape) => shape.info.type !== filter).forEach((shape) => { 213 shape.unHighlight() 214 }) 109 215 } 110 216 111 index++; 112 113 setTimeout(drawNextSegment, 25); 114 }; 115 116 drawNextSegment(); 217 } 117 218 } 118 119 120 search() {121 console.log("VLEZE VO SEARCH");122 }123 } -
imaps-frontend/src/scripts/net/HttpService.js
rd565449 r0c6b92a 3 3 4 4 class HttpService { 5 constructor(URL = config.apiBaseUrl, auth = false) { 6 this.baseURL = URL; 5 constructor(auth = false) { 7 6 this.auth = auth; 8 7 } … … 33 32 } 34 33 35 const response = await fetch(`${ this.baseURL}${endpoint}`, options);34 const response = await fetch(`${endpoint}`, options); 36 35 37 36 if (!response.ok) { -
imaps-frontend/src/scripts/net/netconfig.js
rd565449 r0c6b92a 1 export const API_BASE_URL = "http://localhost:8080/api"; 2 export const API_PROTECTED = `${API_BASE_URL}/protected` 3 export const API_PUBLIC = `${API_BASE_URL}/public` 4 export const API_AUTH = `${API_BASE_URL}/auth` 5 1 6 2 7 const config = { 3 apiBaseUrl: "http://localhost:8080/api", 8 my_maps: { 9 display: `${API_PROTECTED}/my-maps`, 10 save: `${API_PROTECTED}/my-maps/save`, 11 add: `${API_PROTECTED}/my-maps/create`, 12 load: `${API_PROTECTED}/my-maps/load`, 13 delete: `${API_PROTECTED}/my-maps/delete`, 14 publish: `${API_PROTECTED}/publish/add`, 15 publish_get: `${API_PROTECTED}/publish/get`, 16 }, 17 room_types: { 18 display: (auth) => { 19 return auth ? `${API_PROTECTED}/room-types` : `${API_PUBLIC}/room-types` 20 }, 21 add: `${API_PROTECTED}/room-types/add`, 4 22 5 endpoints: { 6 login: "/auth/login", 7 register: "/auth/register", 8 verify: "/auth/verify", 9 render: "/protected/render", 10 publicMapData: "/public/mapData" 23 }, 24 floors: { 25 add: `${API_PROTECTED}/floors/add`, 26 load: `${API_PROTECTED}/my-maps/load`, 27 delete: `${API_PROTECTED}/floors/delete`, 28 }, 29 view_maps: { 30 display: `${API_PUBLIC}/maps`, 31 load: (auth) => { 32 return auth ? `${API_PROTECTED}/load-map` : `${API_PUBLIC}/load-map` 33 }, 34 navigate: `${API_PUBLIC}/navigate`, 35 add_favourite: `${API_PROTECTED}/favourites/add`, 36 }, 37 favourites: { 38 display: `${API_PROTECTED}/favourites`, 39 add: `${API_PROTECTED}/favourites/add`, 40 delete: `${API_PROTECTED}/favourites/delete`, 41 }, 42 auth:{ 43 login: `${API_AUTH}/login`, 44 register: `${API_AUTH}/register`, 45 verify: `${API_AUTH}/verify` 46 47 }, 48 admin:{ 49 display: `${API_BASE_URL}/admin`, 50 load_pr: `${API_BASE_URL}/admin/load-pr`, 51 approve_pr: `${API_BASE_URL}/admin/pr/approve`, 52 deny_pr: `${API_BASE_URL}/admin/pr/deny` 11 53 } 12 54 }; 13 55 56 14 57 export default config; -
imaps-frontend/src/scripts/rendered_shapes/RenderedEntrance.js
rd565449 r0c6b92a 1 import RenderedMapShape from "./RenderedMapShape.js"; 1 import RenderedMapShape from "../base/RenderedMapShape.js"; 2 import {_registerNode} from "konva/lib/Global"; 3 import RenderedRoom from "./RenderedRoom.js"; 2 4 3 export default class RenderedEntrance extends RenderedMapShape {4 constructor(attrs,scaleX,scaleY){5 6 7 8 9 10 fill: '#1c3cff',11 stroke: 'black',12 13 14 15 cornerRadius:3,16 zIndex: 017 5 export default class RenderedEntrance extends RenderedMapShape { 6 constructor(attrs, scaleX, scaleY) { 7 super({ 8 x: attrs.x, 9 y: attrs.y, 10 width: attrs.width * scaleX, 11 height: attrs.height * scaleY, 12 fill: "#7fef83", 13 stroke: "black", 14 strokeWidth: 1, 15 draggable: false, 16 rotation: attrs.rotation, 17 cornerRadius: 3, 18 zIndex: 0, 19 }); 18 20 19 this.info.name = attrs.obj_name; 20 this.info.description = attrs.description; 21 22 23 this.on("mouseenter",() => { 24 console.log("HOVER ROOM IN", this.x()); 25 console.log(this.info.name,"NAME"); 26 this.stroke('purple'); 27 }) 28 this.on("mouseleave", () => { 29 console.log("HOVER ROOM OUT"); 30 this.opacity(1); 31 this.stroke('black') 21 this.info.name = attrs.obj_name; 22 this.info.description = attrs.description; 23 this.info.isMainEntrance = attrs.is_main_entrance; 32 24 33 })25 this.floorNum = attrs.floor_num 34 26 35 this.initText() 36 } 27 this.class = "Entrance"; 28 29 console.log("ATTRS VIEW: " + attrs) 30 31 this.on("mouseenter", () => { 32 this.stroke("purple"); 33 }); 34 this.on("mouseleave", () => { 35 this.opacity(1); 36 this.stroke("black"); 37 }); 38 39 //this.initText(); 40 } 37 41 } 42 43 RenderedEntrance.prototype.className = "RenderedEntrance"; 44 _registerNode(RenderedEntrance); -
imaps-frontend/src/scripts/rendered_shapes/RenderedRoom.js
rd565449 r0c6b92a 1 import RenderedMapShape from "./RenderedMapShape.js"; 1 import RenderedMapShape from "../base/RenderedMapShape.js"; 2 import {_registerNode} from "konva/lib/Global"; 2 3 3 export default class RenderedRoom extends RenderedMapShape{ 4 constructor(attrs,scaleX,scaleY){ 5 super({ 6 x: attrs.x, 7 y: attrs.y, 8 width: attrs.width * scaleX, 9 height: attrs.height * scaleY, 10 fill: 'white', 11 stroke: 'black', 12 strokeWidth: 1, 13 draggable: false, 14 rotation: attrs.rotation, 15 cornerRadius:3, 16 zIndex: 0 17 }); 4 export default class RenderedRoom extends RenderedMapShape { 5 constructor(attrs, scaleX, scaleY) { 6 super({ 7 x: attrs.x, 8 y: attrs.y, 9 width: attrs.width * scaleX, 10 height: attrs.height * scaleY, 11 fill: "#A2D9FF", 12 stroke: "black", 13 strokeWidth: 1, 14 draggable: false, 15 rotation: attrs.rotation, 16 cornerRadius: 3 17 }); 18 18 19 this.info.name = attrs.obj_name; 20 this.info.description = attrs.description; 19 console.info("FNUM RENDER:",attrs.floor_num) 21 20 22 this.textOffsetX = -50 23 24 25 this.on("mouseenter",() => { 26 console.log("HOVER ROOM IN", this.x()); 27 console.log(this.info.name,"NAME"); 28 this.opacity(0.7); 29 this.fill("pink"); 30 }) 31 this.on("mouseleave", () => { 32 console.log("HOVER ROOM OUT"); 33 this.opacity(1); 34 this.fill("white"); 35 }) 21 this.floorNum = attrs.floor_num 36 22 37 this.initText() 38 } 23 this.info.name = attrs.obj_name; 24 this.info.type = attrs.room_type; 25 this.info.description = attrs.description; 26 this.class = "Room"; 27 this.textOffsetX = -50; 39 28 40 29 this.eventName = "openRoomInfoPanel" 30 31 this.on("mouseenter", () => { 32 console.log(this.info.name, "NAME"); 33 this.fill("#65c3f8"); 34 }); 35 this.on("mouseleave", () => { 36 this.fill("#A2D9FF"); 37 }); 38 39 40 41 // searched(){ 42 // this.fill("#b92d39") 43 // } 44 // unsearched(){ 45 // this.fill("#A2D9FF"); 46 // } 47 48 // console.log("ATTRS: " + attrs); 49 50 this.initText(); 51 } 52 highlight(){ 53 this.fill("rgba(29,238,78,0.49)"); 54 this.strokeWidth(2) 55 } 56 unHighlight(){ 57 this.fill("#A2D9FF"); 58 this.strokeWidth(1); 59 } 41 60 } 61 RenderedRoom.prototype.className = "RenderedRoom"; 62 _registerNode(RenderedRoom); -
imaps-frontend/src/scripts/rendered_shapes/RenderedWall.js
rd565449 r0c6b92a 1 import RenderedMapShape from "./RenderedMapShape.js"; 1 import RenderedMapShape from "../base/RenderedMapShape.js"; 2 import {_registerNode} from "konva/lib/Global"; 2 3 3 4 export default class RenderedWall extends RenderedMapShape{ … … 8 9 width: attrs.width * scaleX, 9 10 height: attrs.height * scaleY, 10 fill: ' grey',11 fill: '#4B4B4B', 11 12 stroke: 'black', 12 13 strokeWidth: 1, … … 18 19 } 19 20 } 21 22 RenderedWall.prototype.className = "RenderedWall"; 23 _registerNode(RenderedWall); -
imaps-frontend/src/scripts/shapes/Entrance.js
rd565449 r0c6b92a 1 import Konva from "konva"; 2 import MapShape from "./MapShape"; 3 import { _registerNode } from "konva/lib/Global"; 4 export default class Entrance extends MapShape { 5 constructor(mousePos, blockSize, layer, rotation, snap,id,scaleX = 1, scaleY = 1) { 6 super( 7 { 8 x: mousePos.x, 9 y: mousePos.y, 10 width: blockSize * scaleX, 11 height: blockSize * 2 * scaleY, 12 fill: "#0051ff", 13 stroke: "grey", 14 strokeWidth: 1, 15 opacity: 0.7, 16 name: "mapObj", 17 draggable: true, 18 rotation: rotation, 19 zIndex: 1, 20 }, 21 layer, 22 blockSize, 23 snap 24 ); 25 this.type = "Entrance"; 26 this.modalEventName = "openEntranceModalEvent"; 1 import {_registerNode} from "konva/lib/Global"; 2 import MapNode from "../base/MapNode.js"; 3 import {node} from "prop-types"; 27 4 28 this.id = id; 5 export default class Entrance extends MapNode { 29 6 30 this._info = { 31 name: `Entrance ${id}`, 32 connectedRoom: "", 33 description: "", 34 isMainEntrance: false, 35 selectedPin: "", 36 selectedPins: [], 37 }; 7 constructor(attrs, id) { 38 8 39 this.initText(); 40 } 9 if (!attrs.fromLoad) { 10 attrs.height *= 2; 11 } 12 super( 13 { 14 x: attrs.position.x, 15 y: attrs.position.y, 16 width: attrs.width, 17 height: attrs.height, 18 fill: "rgb(126,238,167)", 19 stroke: "#252627", 20 strokeWidth: 1, 21 opacity: 0.9, 22 name: "mapObj", 23 draggable: true, 24 rotation: attrs.rotation, 25 zIndex: 1, 26 }, 27 attrs.layer, 28 attrs.blockSize, 29 attrs.snap 30 ); 31 this.type = "Entrance"; 32 this.eventName = "openEntranceModalEvent"; 33 this.floorNum = attrs.floorNum 41 34 42 loadInfo(attrs) { 43 this.info.name = attrs.obj_name; 44 this.info.connectedRoom = attrs.connected_room; 45 this.info.description = attrs.description; 46 this.info.isMainEntrance = attrs.is_main_entrance; 47 this.info.selectedPins = attrs.connected_pins; 48 } 35 this.id = id; 49 36 50 saveShapeDetails() { 51 this.setAttr("connected_pins", this.info.selectedPins); 52 this.setAttr("obj_name", this.info.name); 53 this.setAttr("description", this.info.description); 54 this.setAttr("is_main_entrance", this.info.isMainEntrance); 55 this.setAttr("connected_room", this.info.connectedRoom); 56 } 37 this._info = { 38 name: `Entrance${id} [${this.floorNum}F]`, 39 connectedRoom: "", 40 description: "", 41 isMainEntrance: false, 42 selectedPin: "", 43 selectedPins: [], 44 }; 45 46 this.initText(); 47 this.moveToTop(); 48 } 49 50 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 } 58 59 saveShapeDetails() { 60 console.info("fnum entrance",this.attrs.floorNum) 61 62 this.setAttr("connected_pins", this.info.selectedPins); 63 this.setAttr("obj_name", this.info.name); 64 this.setAttr("description", this.info.description); 65 this.setAttr("is_main_entrance", this.info.isMainEntrance); 66 this.setAttr("connected_room", this.info.connectedRoom); 67 this.setAttr("floor_num",this.floorNum); 68 } 69 70 connect(node, draw = true) { 71 if(this.floorNum !== node.floorNum) return; 72 73 super.connect(node) 74 } 75 76 setInfo(infoObj) { 77 console.log("SA VIKNA SETINFO") 78 this.info = infoObj; 79 if(this.info.connectedRoom == null || this.info.connectedRoom === "" ){ 80 this.strokeWidth(2); 81 this.stroke("#a10114") 82 }else{ 83 this.strokeWidth(1) 84 this.stroke("black") 85 } 86 } 57 87 } 58 88 -
imaps-frontend/src/scripts/shapes/InfoPin.js
rd565449 r0c6b92a 1 import Konva from "konva"; 2 import MapShape from "./MapShape"; 3 import Factory from "../util/Factory"; 1 4 2 import { _registerNode } from "konva/lib/Global"; 5 export default class InfoPin extends MapShape { 6 constructor(mousePos, blockSize, layer, snappable,id) { 3 import MapNode from "../base/MapNode.js"; 4 import {node} from "prop-types"; 5 import draw from "../../pages/Draw/Draw.jsx"; 6 export default class InfoPin extends MapNode { 7 constructor(attrs,id) { 8 attrs.snap = false; 7 9 super( 8 10 { 9 x: mousePos.x,10 y: mousePos.y,11 radiusX: blockSize * 0.5,12 radiusY: blockSize * 0.7,13 tailHeight: blockSize * 1.2,14 fill: "# d70113",11 x: attrs.position.x, 12 y: attrs.position.y, 13 radiusX: attrs.blockSize * 0.5, 14 radiusY: attrs.blockSize * 0.7, 15 tailHeight: attrs.blockSize * 1.2, 16 fill: "#f60000", 15 17 stroke: "#1b1b1b", 16 strokeWidth: 0.2,18 strokeWidth: 1, 17 19 draggable: true, 18 20 name: "mapObj", 19 21 }, 20 layer,21 blockSize,22 snappable22 attrs.layer, 23 attrs.blockSize, 24 attrs.snap 23 25 ); 24 26 25 27 this.id = id; 28 this.eventName = "openPinModalEvent"; 29 this.floorNum = attrs.floorNum; 26 30 27 this.modalEventName = "openPinModalEvent";28 31 this.type = "InfoPin"; 29 32 this._info = { 30 name: `Pin ${id}`,33 name: `Pin${id} [${this.floorNum}F]`, 31 34 selectedPins: [], 32 35 description: "", … … 34 37 35 38 this.on("mouseover", () => { 36 this.fill(" yellow");39 this.fill("#FFD700"); 37 40 }); 38 41 this.on("mouseout", () => { 39 this.fill(" red");42 this.fill("#f60000"); 40 43 }); 44 41 45 42 46 this.initText(); 43 47 } 44 48 _sceneFunc(context, shape) { 45 const { radiusX, radiusY, tailHeight } = this.attrs; 49 const { radiusX, radiusY, tailHeight } = this.attrs; // attrs od konva 46 50 47 51 context.beginPath(); … … 59 63 } 60 64 65 61 66 loadInfo(attrs) { 62 67 this.info.name = attrs.obj_name; 63 68 this.info.selectedPins = attrs.connected_pins; 64 69 this.info.description = attrs.description; 70 this.floorNum = attrs.floor_num 65 71 } 66 72 … … 69 75 this.setAttr("connected_pins", this.info.selectedPins); 70 76 this.setAttr("description", this.info.description); 77 this.setAttr("floor_num",this.floorNum) 71 78 console.log(this.info, "vnatre vo info"); 79 } 80 81 connect(node, draw = true) { 82 if(this.floorNum !== node.floorNum) return; 83 super.connect(node) 72 84 } 73 85 } -
imaps-frontend/src/scripts/shapes/Room.js
rd565449 r0c6b92a 1 1 import Konva from "konva"; 2 import MapShape from ". /MapShape";2 import MapShape from "../base/MapShape.js"; 3 3 import { _registerNode } from "konva/lib/Global"; 4 4 export default class Room extends MapShape { 5 constructor(mousePos, blockSize, layer, rotation, snap, id, scaleX = 1, scaleY = 1){ 5 constructor(attrs,id){ 6 7 if(!attrs.fromLoad){ 8 attrs.width *= 12; 9 attrs.height *= 8; 10 } 11 12 console.log(attrs.position,"hehe") 6 13 super( 7 14 { 8 x: mousePos.x,9 y: mousePos.y,10 width: blockSize * 8 *scaleX,11 height: blockSize * 4 *scaleY,12 fill: " #DDE0F8",15 x: attrs.position.x, 16 y: attrs.position.y, 17 width: attrs.width * attrs.scaleX, 18 height: attrs.height * attrs.scaleY, 19 fill: "rgb(86,168,253)", 13 20 stroke: "grey", 14 21 strokeWidth: 1, 15 22 name: "mapObj", 16 rotation: rotation,23 rotation: attrs.rotation, 17 24 draggable: true, 18 25 }, 19 layer,20 blockSize,21 snap26 attrs.layer, 27 attrs.blockSize, 28 attrs.snap 22 29 ); 23 30 31 this.floorNum = attrs.floorNum; 32 24 33 this._info = { 25 name: `Room ${id}`,34 name: `Room${id} [${this.floorNum}F]`, 26 35 type: "", 27 36 description: "", … … 29 38 30 39 this.type = "Room"; 31 this. modalEventName = "openRoomModalEvent";40 this.eventName = "openRoomModalEvent"; 32 41 this.id = id; 33 42 … … 39 48 this.info.type = attrs.room_type; 40 49 this.info.description = attrs.description; 50 this.floorNum = attrs.floor_num; 41 51 } 42 52 … … 45 55 this.setAttr("room_type", this.info.type); 46 56 this.setAttr("description", this.info.description); 57 this.setAttr("floor_num",this.floorNum); 47 58 } 48 59 } -
imaps-frontend/src/scripts/shapes/Stairs.js
rd565449 r0c6b92a 1 1 import Konva from "konva"; 2 import { MapShape } from "./MapShape"; 2 import MapNode from "../base/MapNode.js"; 3 import {_registerNode} from "konva/lib/Global.js"; 4 import Room from "./Room.js"; 5 export default class Stairs extends MapNode{ 6 constructor(attrs,id) { 3 7 8 if(!attrs.fromLoad){ 9 attrs.width *= 4; 10 } 11 12 super({ 13 x: attrs.position.x, 14 y: attrs.position.y, 15 width: attrs.width * attrs.scaleX, 16 height: attrs.height * attrs.scaleY, 17 fill: "rgb(225,213,124)", 18 stroke: "rgb(16,15,15)", 19 strokeWidth: 1, 20 name: "mapObj", 21 rotation: attrs.rotation, 22 draggable: true 23 }, 24 attrs.layer, 25 attrs.blockSize, 26 attrs.snap 27 ); 28 29 this.floorNum = attrs.floorNum; 30 31 this.type = "Stairs" 32 this._info = { 33 name: `Stairs${id} [${this.floorNum}F]`, 34 description: "", 35 selectedPins: [] 36 }; 37 38 this.id = id; 39 this.eventName = "openStairsModalEvent"; 40 this.initText(); 41 } 42 43 44 _sceneFunc(context, shape) { 45 const { width, height} = this.attrs; 46 47 let steps = 5; 48 context.beginPath() 49 for(let i = 0; i < steps; i++){ 50 context.rect((-this.blockSize) * i,(this.blockSize * 0.6) * i,width * 0.86,height/2) 51 context.fillStrokeShape(shape); 52 } 53 context.closePath() 54 } 55 56 loadInfo(attrs) { 57 this.info.name = attrs.obj_name; 58 this.info.description = attrs.description; 59 this.info.selectedPins = attrs.connected_pins; 60 this.floorNum = attrs.floor_num; 61 } 62 63 saveShapeDetails() { 64 this.setAttr("connected_pins", this.info.selectedPins); 65 this.setAttr("obj_name", this.info.name); 66 this.setAttr("description", this.info.description); 67 this.setAttr("floor_num",this.floorNum) 68 } 69 connect(node,draw = true) { 70 let canDraw = this.floorNum === node.floorNum; 71 super.connect(node,canDraw); 72 } 73 74 } 75 76 Stairs.prototype.className = "Stairs"; 77 _registerNode(Stairs); 4 78 //TODO -
imaps-frontend/src/scripts/shapes/Wall.js
rd565449 r0c6b92a 1 1 import Konva from "konva"; 2 import MapShape from ". /MapShape";2 import MapShape from "../base/MapShape.js"; 3 3 import { _registerNode } from 'konva/lib/Global'; 4 4 export default class Wall extends MapShape { 5 constructor(mousePos, blockSize, layer, rotation,snap, draggable = true,scaleX = 1, scaleY = 1){ 5 constructor(attrs){ 6 if(!attrs.fromLoad){ 7 attrs.height *= 8; 8 } 9 10 6 11 super( 7 12 { 8 x: mousePos.x,9 y: mousePos.y,10 width: blockSize *scaleX,11 height: blockSize * 8 *scaleY,12 fill: "# DDE0F8",13 x: attrs.position.x, 14 y: attrs.position.y, 15 width: attrs.width * attrs.scaleX, 16 height: attrs.height * attrs.scaleY, 17 fill: "#d3d3d3", 13 18 stroke: "grey", 14 19 strokeWidth: 1, 15 20 name: "mapObj", 16 draggable: draggable,17 rotation: rotation,21 draggable: true, 22 rotation: attrs.rotation, 18 23 zIndex: 0, 19 24 }, 20 layer,21 blockSize,22 snap,25 attrs.layer, 26 attrs.blockSize, 27 attrs.snap, 23 28 ); 24 25 29 this.type = "Wall"; 30 31 this.floorNum = attrs.floorNum; 26 32 } 27 } 33 34 loadInfo(attrs) { 35 this.floorNum = attrs.floor_num; 36 } 37 38 saveShapeDetails() { 39 this.setAttr("floor_num",this.floorNum) 40 } 41 } 28 42 29 43 Wall.prototype.className = 'Wall' -
imaps-frontend/src/scripts/util/Factory.js
rd565449 r0c6b92a 6 6 import RenderedEntrance from "../rendered_shapes/RenderedEntrance"; 7 7 import RenderedWall from "../rendered_shapes/RenderedWall.js"; 8 import Stairs from "../shapes/Stairs.js"; 9 import RenderedStairs from "../rendered_shapes/RenderedStairs.js"; 10 import {updateShapeCount, getShapeCount} from "./ShapeCount.js"; 8 11 export default class Factory { 12 // BUG KO KE NAPRES REFRESH SA RESETVAT COUNTS! 9 13 10 static infoPinCount = 0; 11 static wallCount = 0; 12 static entranceCount = 0; 13 static roomCount = 0; 14 static createShape(shapeType,attrs) { 15 console.log(attrs,"attrs in factory") 16 console.log("position in factory: " + attrs.position.x) 14 17 15 static createShape(shapeType, position, blockSize, layer, rotation,scaleX = 1, scaleY = 1, increment = false) { 18 let scaleX = (attrs.scaleX ? parseFloat(attrs.scaleX) : 1); 19 let scaleY = (attrs.scaleY ? parseFloat(attrs.scaleY) : 1); 20 21 attrs.scaleX = scaleX; 22 attrs.scaleY = scaleY; 23 16 24 switch (shapeType) { 17 25 case "Entrance": 18 if( increment) this.entranceCount++;19 return new Entrance( position, blockSize, layer, rotation,true,this.entranceCount,scaleX,scaleY);26 if(attrs.increment) updateShapeCount("entrance") 27 return new Entrance(attrs,getShapeCount("entrance")); 20 28 case "Room": 21 if(increment) this.roomCount++; 22 return new Room(position, blockSize, layer, rotation,true, this.roomCount,scaleX,scaleY); 23 case "Wall": 24 return new Wall(position, blockSize, layer, rotation,true,scaleX,scaleY); 25 case "InfoPin": 26 if(increment) this.infoPinCount++; 27 return new InfoPin(position, blockSize, layer, false,this.infoPinCount); 29 if(attrs.increment) updateShapeCount("room"); 30 return new Room(attrs,getShapeCount("room")); 31 case "Wall": 32 return new Wall(attrs); 33 case "InfoPin": 34 if(attrs.increment) updateShapeCount("infoPin") 35 return new InfoPin(attrs,getShapeCount("infoPin")); 36 case "Stairs": 37 if(attrs.increment) updateShapeCount("stairs") 38 return new Stairs(attrs,getShapeCount("stairs")) 28 39 default: 29 40 throw new Error("Invalid shape type: " + shapeType); … … 41 52 case "Wall": 42 53 return new RenderedWall(attrs,scaleX,scaleY); 54 case "Stairs": 55 return new RenderedStairs(attrs,scaleX,scaleY) 43 56 default: 44 57 throw new Error("Invalid shape type." + shapeType); -
imaps-frontend/src/scripts/util/zoomStage.js
rd565449 r0c6b92a 1 export const zoomStage = (e,stage ) => {2 if ( !e.evt.shiftKey) return;1 export const zoomStage = (e,stage,shift=false) => { 2 if (shift && !e.evt.shiftKey) return; 3 3 4 4 e.evt.preventDefault();
Note:
See TracChangeset
for help on using the changeset viewer.