diff --git a/app/.env b/app/.env index ff2cfb0..daf92f1 100644 --- a/app/.env +++ b/app/.env @@ -7,6 +7,8 @@ REACT_APP_SERVER_SOCKET_API_BASE_URL=185.100.212.76:8000 # Base URL for the server REST API, used for HTTP requests to the backend server. REACT_APP_SERVER_REST_API_BASE_URL=185.100.212.76:5000 +REACT_APP_SERVER_REST_API_LOCAL_BASE_URL=192.168.0.102:5000 + # Base URL for the server marketplace API. # REACT_APP_SERVER_MARKETPLACE_URL=185.100.212.76:50011 REACT_APP_SERVER_MARKETPLACE_URL=192.168.0.111:3501 diff --git a/app/package-lock.json b/app/package-lock.json index 9a7d772..a820896 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -26,6 +26,7 @@ "@types/react-dom": "^18.3.0", "@use-gesture/react": "^10.3.1", "chart.js": "^4.4.8", + "chartjs-plugin-annotation": "^3.1.0", "glob": "^11.0.0", "gsap": "^3.12.5", "leva": "^0.10.0", @@ -8499,6 +8500,15 @@ "pnpm": ">=8" } }, + "node_modules/chartjs-plugin-annotation": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-annotation/-/chartjs-plugin-annotation-3.1.0.tgz", + "integrity": "sha512-EkAed6/ycXD/7n0ShrlT1T2Hm3acnbFhgkIEJLa0X+M6S16x0zwj1Fv4suv/2bwayCT3jGPdAtI9uLcAMToaQQ==", + "license": "MIT", + "peerDependencies": { + "chart.js": ">=4.0.0" + } + }, "node_modules/check-more-types": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", diff --git a/app/package.json b/app/package.json index 541d3a1..66e3b39 100644 --- a/app/package.json +++ b/app/package.json @@ -21,6 +21,7 @@ "@types/react-dom": "^18.3.0", "@use-gesture/react": "^10.3.1", "chart.js": "^4.4.8", + "chartjs-plugin-annotation": "^3.1.0", "glob": "^11.0.0", "gsap": "^3.12.5", "leva": "^0.10.0", diff --git a/app/public/index.html b/app/public/index.html index 785ee0f..d05ca9d 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -1,9 +1,12 @@ - + - + Dwinzo (beta) - +
diff --git a/app/src/components/icons/DashboardIcon.tsx b/app/src/components/icons/DashboardIcon.tsx index 83c997b..f1f4d7d 100644 --- a/app/src/components/icons/DashboardIcon.tsx +++ b/app/src/components/icons/DashboardIcon.tsx @@ -1,255 +1,255 @@ -export function NotificationIcon() { - return ( - - - - - ); -} - -export function HomeIcon() { - return ( - - - - ); -} - -export function ProjectsIcon() { - return ( - - - - ); -} - -export function TutorialsIcon() { - return ( - - - - - - - - - - - - - - - - - - - - ); -} - -export function DocumentationIcon() { - return ( - - - - - ); -} - -export function HelpIcon() { - return ( - - - - - - - - - - - ); -} - -export function LogoutIcon() { - return ( - - - - - - ); -} +export function NotificationIcon() { + return ( + + + + + ); +} + +export function HomeIcon() { + return ( + + + + ); +} + +export function ProjectsIcon() { + return ( + + + + ); +} + +export function TutorialsIcon() { + return ( + + + + + + + + + + + + + + + + + + + + ); +} + +export function DocumentationIcon() { + return ( + + + + + ); +} + +export function HelpIcon() { + return ( + + + + + + + + + + + ); +} + +export function LogoutIcon() { + return ( + + + + + + ); +} diff --git a/app/src/components/layout/Dashboard/DashboardCard.tsx b/app/src/components/layout/Dashboard/DashboardCard.tsx index 0e5df0d..d58bfb6 100644 --- a/app/src/components/layout/Dashboard/DashboardCard.tsx +++ b/app/src/components/layout/Dashboard/DashboardCard.tsx @@ -1,25 +1,25 @@ -import React from "react"; -import { KebabIcon } from "../../icons/ExportCommonIcons"; -import img from "../../../assets/image/image.png" - -const DashboardCard:React.FC = () => { - return ( -
-
- -
-
-
-
Untitled
-
24-12-2025
-
-
-
V
- -
-
-
- ); -}; - -export default DashboardCard; +import React from "react"; +import { KebabIcon } from "../../icons/ExportCommonIcons"; +import img from "../../../assets/image/image.png" + +const DashboardCard:React.FC = () => { + return ( +
+
+ +
+
+
+
Untitled
+
24-12-2025
+
+
+
V
+ +
+
+
+ ); +}; + +export default DashboardCard; diff --git a/app/src/components/layout/Dashboard/DashboardHome.tsx b/app/src/components/layout/Dashboard/DashboardHome.tsx index 9d6672c..38e7bad 100644 --- a/app/src/components/layout/Dashboard/DashboardHome.tsx +++ b/app/src/components/layout/Dashboard/DashboardHome.tsx @@ -1,21 +1,21 @@ -import React from "react"; -import DashboardCard from "./DashboardCard"; -import DashboardNavBar from "./DashboardNavBar"; -import MarketPlaceBanner from "./MarketPlaceBanner"; - -const DashboardHome: React.FC = () => { - return ( -
- - -
-
Recents
-
- -
-
-
- ); -}; - -export default DashboardHome; +import React from "react"; +import DashboardCard from "./DashboardCard"; +import DashboardNavBar from "./DashboardNavBar"; +import MarketPlaceBanner from "./MarketPlaceBanner"; + +const DashboardHome: React.FC = () => { + return ( +
+ + +
+
Recents
+
+ +
+
+
+ ); +}; + +export default DashboardHome; diff --git a/app/src/components/layout/Dashboard/DashboardNavBar.tsx b/app/src/components/layout/Dashboard/DashboardNavBar.tsx index add0656..3da1a61 100644 --- a/app/src/components/layout/Dashboard/DashboardNavBar.tsx +++ b/app/src/components/layout/Dashboard/DashboardNavBar.tsx @@ -1,21 +1,21 @@ -import React from "react"; -import { CartIcon } from "../../icons/ExportModuleIcons"; -import Search from "../../ui/inputs/Search"; - -interface DashboardNavBarProps { - page: React.ReactNode; -} - -const DashboardNavBar: React.FC = ({ page }) => { - return ( -
-
{page}
-
- Market Place -
- {}} /> -
- ); -}; - -export default DashboardNavBar; +import React from "react"; +import { CartIcon } from "../../icons/ExportModuleIcons"; +import Search from "../../ui/inputs/Search"; + +interface DashboardNavBarProps { + page: React.ReactNode; +} + +const DashboardNavBar: React.FC = ({ page }) => { + return ( +
+
{page}
+
+ Market Place +
+ {}} /> +
+ ); +}; + +export default DashboardNavBar; diff --git a/app/src/components/layout/Dashboard/MarketPlaceBanner.tsx b/app/src/components/layout/Dashboard/MarketPlaceBanner.tsx index b0d151a..b89bfb2 100644 --- a/app/src/components/layout/Dashboard/MarketPlaceBanner.tsx +++ b/app/src/components/layout/Dashboard/MarketPlaceBanner.tsx @@ -1,44 +1,44 @@ -import React from "react"; -import banner from "../../../assets/image/banner.png"; - -const MarketPlaceBanner = () => { - return ( -
- {/* market place banner */} - -
- NEW -
FALL -
COLLECTION -
-
Unlock Creativity with Premium 3D Assets!
-
- - - - -
-
Explore Now
-
- ); -}; - -export default MarketPlaceBanner; +import React from "react"; +import banner from "../../../assets/image/banner.png"; + +const MarketPlaceBanner = () => { + return ( +
+ {/* market place banner */} + +
+ NEW +
FALL +
COLLECTION +
+
Unlock Creativity with Premium 3D Assets!
+
+ + + + +
+
Explore Now
+
+ ); +}; + +export default MarketPlaceBanner; diff --git a/app/src/components/layout/Dashboard/SidePannel.tsx b/app/src/components/layout/Dashboard/SidePannel.tsx index 00e3e14..d44c72c 100644 --- a/app/src/components/layout/Dashboard/SidePannel.tsx +++ b/app/src/components/layout/Dashboard/SidePannel.tsx @@ -1,69 +1,69 @@ -import React from "react"; -import { - DocumentationIcon, - HelpIcon, - HomeIcon, - LogoutIcon, - NotificationIcon, - ProjectsIcon, - TutorialsIcon, -} from "../../icons/DashboardIcon"; -import { SettingsIcon, TrashIcon } from "../../icons/ExportCommonIcons"; - -const SidePannel: React.FC = () => { - const userName = localStorage.getItem("userName") || "Anonymous"; - return ( -
-
-
-
{userName[0]}
-
{userName}
-
-
- -
-
-
+ New project
-
-
-
- - Home -
-
- - Projects -
-
- - Trash -
-
- - Tutorials -
-
- - Documentation -
-
-
-
- - Settings -
-
- - Log out -
-
- - Help & Feedback -
-
-
-
- ); -}; - -export default SidePannel; +import React from "react"; +import { + DocumentationIcon, + HelpIcon, + HomeIcon, + LogoutIcon, + NotificationIcon, + ProjectsIcon, + TutorialsIcon, +} from "../../icons/DashboardIcon"; +import { SettingsIcon, TrashIcon } from "../../icons/ExportCommonIcons"; + +const SidePannel: React.FC = () => { + const userName = localStorage.getItem("userName") || "Anonymous"; + return ( +
+
+
+
{userName[0]}
+
{userName}
+
+
+ +
+
+
+ New project
+
+
+
+ + Home +
+
+ + Projects +
+
+ + Trash +
+
+ + Tutorials +
+
+ + Documentation +
+
+
+
+ + Settings +
+
+ + Log out +
+
+ + Help & Feedback +
+
+
+
+ ); +}; + +export default SidePannel; diff --git a/app/src/components/layout/sidebarLeft/Assets.tsx b/app/src/components/layout/sidebarLeft/Assets.tsx index 6005546..3e2510e 100644 --- a/app/src/components/layout/sidebarLeft/Assets.tsx +++ b/app/src/components/layout/sidebarLeft/Assets.tsx @@ -10,6 +10,7 @@ import arch from "../../../assets/gltf-glb/arch.glb"; import door from "../../../assets/gltf-glb/door.glb"; import window from "../../../assets/gltf-glb/window.glb"; import { fetchAssets } from "../../../services/marketplace/fetchAssets"; +import { useSelectedItem } from "../../../store/store"; interface AssetProp { filename: string; thumbnail?: string; @@ -24,6 +25,7 @@ interface AssetProp { CreatedBy?: String; } const Assets: React.FC = () => { + const { setSelectedItem } = useSelectedItem(); const [searchValue, setSearchValue] = useState(""); const [selectedCategory, setSelectedCategory] = useState(null); const [categoryAssets, setCategoryAssets] = useState([]); @@ -32,9 +34,17 @@ const Assets: React.FC = () => { const handleSearchChange = (value: string) => { setSearchValue(value); setSelectedCategory(null); - const filteredModels = filtereredAssets?.filter((model) => - model.filename.toLowerCase().includes(value.toLowerCase()) - ); + const searchTerm = value.toLowerCase(); // Convert input to lowercase + const filteredModels = filtereredAssets?.filter((model) => { + if (!model?.tags || !model?.filename) return false; + if (searchTerm.startsWith(":") && searchTerm.length > 1) { + const tagSearchTerm = searchTerm.slice(1); + return model.tags.toLowerCase().includes(tagSearchTerm); + } else if (!searchTerm.startsWith(":")) { + return model.filename.toLowerCase().includes(searchTerm); + } + return false; + }); setCategoryAssets(filteredModels); }; @@ -105,12 +115,11 @@ const Assets: React.FC = () => { } else { try { const res = await getCategoryAsset(asset); - setCategoryAssets(res || []); // Ensure it's always an array - setFiltereredAssets(res || []); + setCategoryAssets(res); + setFiltereredAssets(res); } catch (error) {} } }; - return (
@@ -149,36 +158,38 @@ const Assets: React.FC = () => { {/* Back Button */}
{ - setSelectedCategory(null); - setCategoryAssets([]); - }} + onClick={() => setSelectedCategory(null)} > ← Back

{selectedCategory}

- {searchValue || - (categoryAssets && - categoryAssets?.map((asset: any, index: number) => ( -
- {asset.filename} + {categoryAssets && + categoryAssets?.map((asset: any, index: number) => ( +
+ {asset.filename} + setSelectedItem({ + name: asset.filename, + id: asset.modelfileID, + }) + } + /> -
- {asset.filename - .split("_") - .map( - (word: any) => - word.charAt(0).toUpperCase() + word.slice(1) - ) - .join(" ")} -
+
+ {asset.filename + .split("_") + .map( + (word: any) => + word.charAt(0).toUpperCase() + word.slice(1) + ) + .join(" ")}
- )))} +
+ ))}
) : ( diff --git a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx b/app/src/components/layout/sidebarLeft/visualization/Templates.tsx index ef710f7..4b13429 100644 --- a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/Templates.tsx @@ -1,25 +1,76 @@ +import { useEffect } from "react"; +import { useDroppedObjectsStore } from "../../../../store/useDroppedObjectsStore"; import useTemplateStore from "../../../../store/useTemplateStore"; import { useSelectedZoneStore } from "../../../../store/useZoneStore"; +import { getTemplateData } from "../../../../services/realTimeVisulization/zoneData/getTemplate"; +import { deleteTemplateApi } from "../../../../services/realTimeVisulization/zoneData/deleteTemplate"; +import { loadTempleteApi } from "../../../../services/realTimeVisulization/zoneData/loadTemplate"; const Templates = () => { const { templates, removeTemplate } = useTemplateStore(); - const { setSelectedZone } = useSelectedZoneStore(); + const { setTemplates } = useTemplateStore(); + const { setSelectedZone, selectedZone } = useSelectedZoneStore(); - const handleDeleteTemplate = (id: string) => { - removeTemplate(id); + useEffect(() => { + async function templateData() { + try { + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + let response = await getTemplateData(organization); + setTemplates(response); + } catch (error) { + console.error("Error fetching template data:", error); + } + } + + templateData(); + }, []); + + const handleDeleteTemplate = async (id: string) => { + try { + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + + let response = await deleteTemplateApi(id, organization); + + if (response.message === "Template deleted successfully") { + removeTemplate(id); + } + } catch (error) { + console.error("Error deleting template:", error); + } }; - const handleLoadTemplate = (template: any) => { - setSelectedZone((prev) => ({ - ...prev, - panelOrder: template.panelOrder, - activeSides: Array.from( - new Set([...prev.activeSides, ...template.panelOrder]) - ), - widgets: template.widgets, - })); + const handleLoadTemplate = async (template: any) => { + try { + if (selectedZone.zoneName === "") return; + + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + + let response = await loadTempleteApi(template.id, selectedZone.zoneId, organization); + + if (response.message === "Template placed in Zone") { + setSelectedZone({ + panelOrder: template.panelOrder, + activeSides: Array.from(new Set(template.panelOrder)), // No merging with previous `activeSides` + widgets: template.widgets, + }); + + useDroppedObjectsStore.getState().setZone(selectedZone.zoneName, selectedZone.zoneId); + + if (Array.isArray(template.floatingWidget)) { + template.floatingWidget.forEach((val: any) => { + useDroppedObjectsStore.getState().addObject(selectedZone.zoneName, val); + }); + } + } + } catch (error) { + console.error("Error loading template:", error); + } }; + return (
{ transition: "box-shadow 0.3s ease", }} > - {template.snapshot && ( + {template?.snapshot && (
{" "} {/* 16:9 aspect ratio */} @@ -122,3 +173,4 @@ const Templates = () => { }; export default Templates; + diff --git a/app/src/components/layout/sidebarRight/Header.tsx b/app/src/components/layout/sidebarRight/Header.tsx index 1da29a2..e51bf9d 100644 --- a/app/src/components/layout/sidebarRight/Header.tsx +++ b/app/src/components/layout/sidebarRight/Header.tsx @@ -13,7 +13,7 @@ const Header: React.FC = () => { const guestUsers: ActiveUser[] = activeUsers.filter( (user: ActiveUser) => user.userName !== userName ); - + const [userManagement, setUserManagement] = useState(false); return ( @@ -31,9 +31,9 @@ const Header: React.FC = () => { > Share
-
+ {/*
-
+
*/}
@@ -52,12 +52,7 @@ const Header: React.FC = () => { ))}
-
- {userName[0]} -
+
{userName[0]}
diff --git a/app/src/components/layout/sidebarRight/customInput/PositionInputs.tsx b/app/src/components/layout/sidebarRight/customInput/PositionInputs.tsx index 881e225..01b4ab3 100644 --- a/app/src/components/layout/sidebarRight/customInput/PositionInputs.tsx +++ b/app/src/components/layout/sidebarRight/customInput/PositionInputs.tsx @@ -1,23 +1,32 @@ import React from "react"; +import { EyeDroperIcon } from "../../../icons/ExportCommonIcons"; interface PositionInputProps { + label?: string; // Optional label for the input onChange: (value: string) => void; // Callback for value change placeholder?: string; // Optional placeholder type?: string; // Input type (e.g., text, number, email) value1?: number; value2?: number; + disabled?: boolean; // Optional disabled property + isEyedrop?: boolean; // Optional eyedrop property + handleEyeDropClick?: () => void; // Optional function for eye drop click } const PositionInput: React.FC = ({ onChange, + label = "Position", // Default label placeholder = "Enter value", // Default placeholder type = "number", // Default type value1 = "number", value2 = "number", + disabled = false, // Default disabled value + isEyedrop = false, // Default isEyedrop value + handleEyeDropClick = () => { }, // Default function for eye drop click }) => { return (
-
Position
+
{label}
X :
@@ -26,7 +35,8 @@ const PositionInput: React.FC = ({ type={type} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} - value={value2} + value={value1} + disabled={disabled} // Apply disabled prop />
@@ -36,10 +46,16 @@ const PositionInput: React.FC = ({ type={type} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} - value={value1} + value={value2} + disabled={disabled} // Apply disabled prop />
+ {isEyedrop && ( +
+ +
+ )}
); }; diff --git a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx index f80fca0..bd10613 100644 --- a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx @@ -9,9 +9,13 @@ import RenameInput from "../../../ui/inputs/RenameInput"; import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; import LabledDropdown from "../../../ui/inputs/LabledDropdown"; import { handleResize } from "../../../../functions/handleResizePannel"; -import { useSelectedActionSphere, useSelectedPath, useSimulationPaths } from "../../../../store/store"; -import * as THREE from 'three'; -import * as Types from '../../../../types/world/worldTypes'; +import { + useSelectedActionSphere, + useSelectedPath, + useSimulationPaths, +} from "../../../../store/store"; +import * as THREE from "three"; +import * as Types from "../../../../types/world/worldTypes"; import InputToggle from "../../../ui/inputs/InputToggle"; const ConveyorMechanics: React.FC = () => { @@ -25,7 +29,9 @@ const ConveyorMechanics: React.FC = () => { const selectedPoint = useMemo(() => { if (!selectedActionSphere) return null; return simulationPaths - .filter((path): path is Types.ConveyorEventsSchema => path.type === "Conveyor") + .filter( + (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" + ) .flatMap((path) => path.points) .find((point) => point.uuid === selectedActionSphere.point.uuid); }, [selectedActionSphere, simulationPaths]); @@ -43,11 +49,11 @@ const ConveyorMechanics: React.FC = () => { const newAction = { uuid: THREE.MathUtils.generateUUID(), name: `Action ${actionIndex + 1}`, - type: 'Inherit', - material: 'Inherit', - delay: 'Inherit', - spawnInterval: 'Inherit', - isUsed: false + type: "Inherit", + material: "Inherit", + delay: "Inherit", + spawnInterval: "Inherit", + isUsed: false, }; return { ...point, actions: [...point.actions, newAction] }; @@ -68,13 +74,18 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { ...point, actions: point.actions.filter(action => action.uuid !== uuid) } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + actions: point.actions.filter( + (action) => action.uuid !== uuid + ), + } + : point + ), + } : path ); @@ -87,26 +98,33 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid - ? { - ...action, - type: actionType, - material: actionType === 'Spawn' || actionType === 'Swap' ? 'Inherit' : action.material, - delay: actionType === 'Delay' ? 'Inherit' : action.delay, - spawnInterval: actionType === 'Spawn' ? 'Inherit' : action.spawnInterval - } - : action - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid + ? { + ...action, + type: actionType, + material: + actionType === "Spawn" || actionType === "Swap" + ? "Inherit" + : action.material, + delay: + actionType === "Delay" ? "Inherit" : action.delay, + spawnInterval: + actionType === "Spawn" + ? "Inherit" + : action.spawnInterval, + } + : action + ), + } + : point + ), + } : path ); @@ -115,15 +133,17 @@ const ConveyorMechanics: React.FC = () => { // Update the selected item to reflect changes if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { const updatedAction = updatedPaths - .filter((path): path is Types.ConveyorEventsSchema => path.type === "Conveyor") - .flatMap(path => path.points) - .find(p => p.uuid === selectedActionSphere.point.uuid) - ?.actions.find(a => a.uuid === uuid); + .filter( + (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" + ) + .flatMap((path) => path.points) + .find((p) => p.uuid === selectedActionSphere.point.uuid) + ?.actions.find((a) => a.uuid === uuid); if (updatedAction) { setSelectedItem({ type: "action", - item: updatedAction + item: updatedAction, }); } } @@ -136,21 +156,21 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid && - (action.type === 'Spawn' || action.type === 'Swap') - ? { ...action, material } - : action - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid && + (action.type === "Spawn" || action.type === "Swap") + ? { ...action, material } + : action + ), + } + : point + ), + } : path ); @@ -162,8 +182,8 @@ const ConveyorMechanics: React.FC = () => { ...selectedItem, item: { ...selectedItem.item, - material - } + material, + }, }); } }; @@ -174,42 +194,47 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid ? { ...action, delay } : action - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid ? { ...action, delay } : action + ), + } + : point + ), + } : path ); setSimulationPaths(updatedPaths); }; - const handleSpawnIntervalChange = (uuid: string, spawnInterval: number | string) => { + const handleSpawnIntervalChange = ( + uuid: string, + spawnInterval: number | string + ) => { if (!selectedActionSphere) return; const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid ? { ...action, spawnInterval } : action - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid + ? { ...action, spawnInterval } + : action + ), + } + : point + ), + } : path ); @@ -233,23 +258,23 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => { - if (point.uuid === selectedActionSphere.point.uuid) { - const triggerIndex = point.triggers.length; - const newTrigger = { - uuid: THREE.MathUtils.generateUUID(), - name: `Trigger ${triggerIndex + 1}`, - type: '', - bufferTime: 0, - isUsed: false - }; + ...path, + points: path.points.map((point) => { + if (point.uuid === selectedActionSphere.point.uuid) { + const triggerIndex = point.triggers.length; + const newTrigger = { + uuid: THREE.MathUtils.generateUUID(), + name: `Trigger ${triggerIndex + 1}`, + type: "", + bufferTime: 0, + isUsed: false, + }; - return { ...point, triggers: [...point.triggers, newTrigger] }; - } - return point; - }), - } + return { ...point, triggers: [...point.triggers, newTrigger] }; + } + return point; + }), + } : path ); @@ -262,13 +287,18 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { ...point, triggers: point.triggers.filter(trigger => trigger.uuid !== uuid) } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + triggers: point.triggers.filter( + (trigger) => trigger.uuid !== uuid + ), + } + : point + ), + } : path ); @@ -281,18 +311,20 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - triggers: point.triggers.map((trigger) => - trigger.uuid === uuid ? { ...trigger, type: triggerType } : trigger - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + triggers: point.triggers.map((trigger) => + trigger.uuid === uuid + ? { ...trigger, type: triggerType } + : trigger + ), + } + : point + ), + } : path ); @@ -309,26 +341,25 @@ const ConveyorMechanics: React.FC = () => { } }; - // Update the toggle handlers to immediately update the selected item const handleActionToggle = (uuid: string) => { if (!selectedActionSphere) return; const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - actions: point.actions.map((action) => ({ - ...action, - isUsed: action.uuid === uuid ? !action.isUsed : false, - })), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + actions: point.actions.map((action) => ({ + ...action, + isUsed: action.uuid === uuid ? !action.isUsed : false, + })), + } + : point + ), + } : path ); @@ -340,8 +371,8 @@ const ConveyorMechanics: React.FC = () => { ...selectedItem, item: { ...selectedItem.item, - isUsed: !selectedItem.item.isUsed - } + isUsed: !selectedItem.item.isUsed, + }, }); } }; @@ -353,19 +384,19 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - triggers: point.triggers.map((trigger) => ({ - ...trigger, - isUsed: trigger.uuid === uuid ? !trigger.isUsed : false, - })), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + triggers: point.triggers.map((trigger) => ({ + ...trigger, + isUsed: trigger.uuid === uuid ? !trigger.isUsed : false, + })), + } + : point + ), + } : path ); @@ -377,8 +408,8 @@ const ConveyorMechanics: React.FC = () => { ...selectedItem, item: { ...selectedItem.item, - isUsed: !selectedItem.item.isUsed - } + isUsed: !selectedItem.item.isUsed, + }, }); } }; @@ -389,18 +420,20 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - triggers: point.triggers.map((trigger) => - trigger.uuid === uuid ? { ...trigger, bufferTime } : trigger - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + triggers: point.triggers.map((trigger) => + trigger.uuid === uuid + ? { ...trigger, bufferTime } + : trigger + ), + } + : point + ), + } : path ); @@ -412,13 +445,16 @@ const ConveyorMechanics: React.FC = () => { ...selectedItem, item: { ...selectedItem.item, - bufferTime - } + bufferTime, + }, }); } }; - const [selectedItem, setSelectedItem] = useState<{ type: "action" | "trigger"; item: any; } | null>(null); + const [selectedItem, setSelectedItem] = useState<{ + type: "action" | "trigger"; + item: any; + } | null>(null); useEffect(() => { setSelectedItem(null); @@ -426,21 +462,20 @@ const ConveyorMechanics: React.FC = () => { return (
- {!selectedPath && + {!selectedPath && (
{selectedActionSphere?.path?.modelName || "point name not found"}
- } - - {selectedPath && + )} + {selectedPath && (
{selectedPath.path.modelName || "path name not found"}
- } + )}
- {!selectedPath && + {!selectedPath && ( <>
@@ -458,16 +493,20 @@ const ConveyorMechanics: React.FC = () => { {selectedPoint?.actions.map((action) => (
setSelectedItem({ type: "action", item: action })} + onClick={() => + setSelectedItem({ type: "action", item: action }) + } > +
{ {selectedPoint?.triggers.map((trigger) => (
setSelectedItem({ type: "trigger", item: trigger })} + onClick={() => + setSelectedItem({ type: "trigger", item: trigger }) + } > +
{
- } + )}
{selectedItem && ( @@ -553,48 +596,69 @@ const ConveyorMechanics: React.FC = () => { handleActionSelect(selectedItem.item.uuid, option)} + onSelect={(option) => + handleActionSelect(selectedItem.item.uuid, option) + } /> {/* Only show material dropdown for Spawn/Swap actions */} - {(selectedItem.item.type === 'Spawn' || selectedItem.item.type === 'Swap') && ( + {(selectedItem.item.type === "Spawn" || + selectedItem.item.type === "Swap") && ( handleMaterialSelect(selectedItem.item.uuid, option)} + onSelect={(option) => + handleMaterialSelect(selectedItem.item.uuid, option) + } /> )} {/* Only show delay input for Delay actions */} - {selectedItem.item.type === 'Delay' && ( + {selectedItem.item.type === "Delay" && ( { const numValue = parseInt(value); handleDelayChange( selectedItem.item.uuid, - !value ? 'Inherit' : numValue + !value ? "Inherit" : numValue ); }} /> )} {/* Only show spawn interval for Spawn actions */} - {selectedItem.item.type === 'Spawn' && ( + {selectedItem.item.type === "Spawn" && ( { - handleSpawnIntervalChange(selectedItem.item.uuid, (value === "") ? "Inherit" : parseInt(value)); + handleSpawnIntervalChange( + selectedItem.item.uuid, + value === "" ? "Inherit" : parseInt(value) + ); }} /> - )} )} @@ -609,9 +673,13 @@ const ConveyorMechanics: React.FC = () => { /> handleTriggerSelect(selectedItem.item.uuid, option)} + onSelect={(option) => + handleTriggerSelect(selectedItem.item.uuid, option) + } /> {selectedItem.item.type === "Buffer" && ( @@ -619,23 +687,34 @@ const ConveyorMechanics: React.FC = () => { label="Buffer Time" value={selectedItem.item.bufferTime.toString()} onChange={(value) => { - handleTriggerBufferTimeChange(selectedItem.item.uuid, parseInt(value)); + handleTriggerBufferTimeChange( + selectedItem.item.uuid, + parseInt(value) + ); }} /> )} )} - )} {selectedPath && !selectedItem && ( -
+
handleSpeedChange((value === "") ? "Inherit" : parseInt(value))} + value={ + selectedPath.path.speed === "Inherit" + ? "" + : selectedPath.path.speed.toString() + } + onChange={(value) => + handleSpeedChange(value === "" ? "Inherit" : parseInt(value)) + } />
)} @@ -657,4 +736,4 @@ const ConveyorMechanics: React.FC = () => { ); }; -export default ConveyorMechanics; \ No newline at end of file +export default ConveyorMechanics; diff --git a/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx index c611a13..bf0b112 100644 --- a/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx @@ -1,13 +1,16 @@ import React, { useRef, useMemo } from "react"; import { InfoIcon } from "../../../icons/ExportCommonIcons"; import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; -import { useSelectedActionSphere, useSimulationPaths } from "../../../../store/store"; +import { useEditingPoint, useEyeDropMode, usePreviewPosition, useSelectedActionSphere, useSimulationPaths } from "../../../../store/store"; import * as Types from '../../../../types/world/worldTypes'; -import LabledDropdown from "../../../ui/inputs/LabledDropdown"; +import PositionInput from "../customInput/PositionInputs"; const VehicleMechanics: React.FC = () => { const { selectedActionSphere } = useSelectedActionSphere(); const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { eyeDropMode, setEyeDropMode } = useEyeDropMode(); + const { editingPoint, setEditingPoint } = useEditingPoint(); + const { previewPosition, setPreviewPosition } = usePreviewPosition(); const propertiesContainerRef = useRef(null); @@ -59,12 +62,10 @@ const VehicleMechanics: React.FC = () => { setSimulationPaths(updatedPaths); }, [selectedActionSphere?.point?.uuid, simulationPaths, setSimulationPaths]); - const handleStartPointChange = React.useCallback((uuid: string) => { - handleActionUpdate({ start: uuid }); + const handleStartPointChange = React.useCallback((position: { x: number, y: number }) => { }, [handleActionUpdate]); - const handleEndPointChange = React.useCallback((uuid: string) => { - handleActionUpdate({ end: uuid }); + const handleEndPointChange = React.useCallback((position: { x: number, y: number }) => { }, [handleActionUpdate]); const handleHitCountChange = React.useCallback((hitCount: number) => { @@ -94,6 +95,16 @@ const VehicleMechanics: React.FC = () => { setSimulationPaths(updatedPaths); }, [selectedActionSphere?.point?.uuid, simulationPaths, setSimulationPaths]); + const handleStartEyeDropClick = () => { + setEditingPoint('start'); + setEyeDropMode(true); + }; + + const handleEndEyeDropClick = () => { + setEditingPoint('end'); + setEyeDropMode(true); + }; + return (
@@ -106,20 +117,49 @@ const VehicleMechanics: React.FC = () => { {selectedPoint && ( <> - { }} + disabled={true} + value1={ + editingPoint === 'start' && previewPosition + ? parseFloat(previewPosition.x.toFixed(4)) + : selectedPoint.actions.start && 'x' in selectedPoint.actions.start + ? parseFloat(selectedPoint.actions.start.x.toFixed(4)) + : 0 + } + value2={ + editingPoint === 'start' && previewPosition + ? parseFloat(previewPosition.y.toFixed(4)) + : selectedPoint.actions.start && 'y' in selectedPoint.actions.start + ? parseFloat(selectedPoint.actions.start.y.toFixed(4)) + : 0 + } + + isEyedrop={true} + handleEyeDropClick={handleStartEyeDropClick} /> - { }} + disabled={true} + value1={ + editingPoint === 'end' && previewPosition + ? parseFloat(previewPosition.x.toFixed(4)) + : selectedPoint.actions.end && 'x' in selectedPoint.actions.end + ? parseFloat(selectedPoint.actions.end.x.toFixed(4)) + : 0 + } + value2={ + editingPoint === 'end' && previewPosition + ? parseFloat(previewPosition.y.toFixed(4)) + : selectedPoint.actions.end && 'y' in selectedPoint.actions.end + ? parseFloat(selectedPoint.actions.end.y.toFixed(4)) + : 0 + } + isEyedrop={true} + handleEyeDropClick={handleEndEyeDropClick} /> { {/* Name */}
{selectedFloorItem.userData.name}
-
- {}} value1={selectedFloorItem.position.x.toFixed(5)} diff --git a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx index 9234cc5..9e8b37e 100644 --- a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx @@ -20,17 +20,16 @@ const ZoneProperties: React.FC = () => { try { const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; - + let zonesdata = { zoneId: selectedZone.zoneId, viewPortposition: zonePosition, viewPortCenter: zoneTarget }; - + let response = await zoneCameraUpdate(zonesdata, organization); console.log('response: ', response); - - + setEdit(false); } catch (error) { console.error("Error in handleSetView:", error); diff --git a/app/src/components/layout/sidebarRight/visualization/IotInputCards/FlotingWidgetInput.tsx b/app/src/components/layout/sidebarRight/visualization/IotInputCards/FlotingWidgetInput.tsx new file mode 100644 index 0000000..5938da9 --- /dev/null +++ b/app/src/components/layout/sidebarRight/visualization/IotInputCards/FlotingWidgetInput.tsx @@ -0,0 +1,177 @@ +import React, { useEffect, useState } from "react"; +import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown"; +import { AddIcon } from "../../../../icons/ExportCommonIcons"; +import RegularDropDown from "../../../../ui/inputs/RegularDropDown"; +import useChartStore from "../../../../../store/useChartStore"; +import { useSelectedZoneStore } from "../../../../../store/useZoneStore"; +import { useWidgetStore } from "../../../../../store/useWidgetStore"; +import axios from "axios"; +import RenameInput from "../../../../ui/inputs/RenameInput"; + +type Props = {}; + +const FlotingWidgetInput = (props: Props) => { + const [widgetName, setWidgetName] = useState('Widget'); + const { setMeasurements, updateDuration, updateName } = useChartStore(); + const [duration, setDuration] = useState('1h') + const [dropDowndata, setDropDownData] = useState({}); + const [selections, setSelections] = useState>({}); + const { selectedZone } = useSelectedZoneStore(); + const { selectedChartId } = useWidgetStore(); + const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL; + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0] + + useEffect(() => { + const fetchZoneData = async () => { + try { + const response = await axios.get(`http://${iotApiUrl}/getinput`); + if (response.status === 200) { + // console.log("dropdown data:", response.data); + setDropDownData(response.data); + } else { + console.log("Unexpected response:", response); + } + } catch (error) { + console.error("There was an error!", error); + } + }; + fetchZoneData(); + }, []); + + useEffect(() => { + const fetchSavedInputes = async () => { + if (selectedChartId.id !== "") { + try { + const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${selectedChartId.id}/${organization}`); + if (response.status === 200) { + setSelections(response.data.Data.measurements) + setDuration(response.data.Data.duration) + setWidgetName(response.data.widgetName) + } else { + console.log("Unexpected response:", response); + } + } catch (error) { + console.error("There was an error!", error); + } + } + } + + fetchSavedInputes(); + + }, [selectedChartId.id]); + + // Sync Zustand state when component mounts + useEffect(() => { + setMeasurements(selections); + updateDuration(duration); + updateName(widgetName); + }, [selections, duration, widgetName]); + + + const sendInputes = async (inputMeasurement: any, inputDuration: any, inputName: any) => { + try { + const response = await axios.post(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/floatwidget/save`, { + organization: organization, + zoneId: selectedZone.zoneId, + widget: { + id: selectedChartId.id, + panel: selectedChartId.panel, + widgetName: inputName, + Data: { + measurements: inputMeasurement, + duration: inputDuration + } + } + } as any); + if (response.status === 200) { + return true + } else { + console.log("Unexpected response:", response); + return false + } + } catch (error) { + console.error("There was an error!", error); + return false + } + } + + const handleSelect = async (inputKey: string, selectedData: { name: string; fields: string } | null) => { + + // async() => { + const newSelections = { ...selections }; + if (selectedData === null) { + delete newSelections[inputKey]; + } else { + newSelections[inputKey] = selectedData; + } + // setMeasurements(newSelections); // Update Zustand store + console.log(newSelections); + if (await sendInputes(newSelections, duration, widgetName)) { + setSelections(newSelections); + } + // sendInputes(newSelections, duration); // Send data to server + // return newSelections; + // }; + }; + + const handleSelectDuration = async (option: string) => { + if (await sendInputes(selections, option, widgetName)) { + setDuration(option); + } + // setDuration(option); + }; + + const handleNameChange = async (name:any) => { + console.log('name change requested',name); + + if (await sendInputes(selections, duration, name)) { + setWidgetName(name); + } + } + + return ( + <> +
+
+
Title
+ +
+ {[...Array(6)].map((_, index) => { + const inputKey = `input${index + 1}`; + return ( +
+
Input {index + 1}
+
+ handleSelect(inputKey, selectedData)} + onUnselect={() => handleSelect(inputKey, null)} + selectedValue={selections[inputKey]} // Load from Zustand + /> +
+ +
+
+
+ ); + })} +
+
+
+
Duration
+
+ +
+
+
+ + ); +}; + +export default FlotingWidgetInput; diff --git a/app/src/components/layout/sidebarRight/visualization/IotInputCards/LineGrapInput.tsx b/app/src/components/layout/sidebarRight/visualization/IotInputCards/LineGrapInput.tsx index baf1fcd..32a5590 100644 --- a/app/src/components/layout/sidebarRight/visualization/IotInputCards/LineGrapInput.tsx +++ b/app/src/components/layout/sidebarRight/visualization/IotInputCards/LineGrapInput.tsx @@ -58,11 +58,11 @@ // name: string; // fields: string; // } - + // interface InputData { // [key: string]: Measurement; // } - + // const extractMeasurements = (input: InputData): Measurement[] => { // return Object.values(input); // }; @@ -71,7 +71,7 @@ // const measurementsData = extractMeasurements(selections); // setMeasurements(measurementsData); // }, [selections]); - + // return ( // <> @@ -125,20 +125,22 @@ import useChartStore from "../../../../../store/useChartStore"; import { useSelectedZoneStore } from "../../../../../store/useZoneStore"; import { useWidgetStore } from "../../../../../store/useWidgetStore"; import axios from "axios"; +import RenameInput from "../../../../ui/inputs/RenameInput"; type Props = {}; const LineGrapInput = (props: Props) => { - const { setMeasurements, updateDuration } = useChartStore(); + const [widgetName, setWidgetName] = useState('Widget'); + const { setMeasurements, updateDuration, updateName } = useChartStore(); const [duration, setDuration] = useState('1h') const [dropDowndata, setDropDownData] = useState({}); - const [selections, setSelections] = useState>({}); + const [selections, setSelections] = useState>({}); const { selectedZone } = useSelectedZoneStore(); const { selectedChartId } = useWidgetStore(); const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL; const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0] - + useEffect(() => { const fetchZoneData = async () => { try { @@ -157,13 +159,14 @@ const LineGrapInput = (props: Props) => { }, []); useEffect(() => { - const fetchSavedInputes = async() => { + const fetchSavedInputes = async () => { if (selectedChartId.id !== "") { try { const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${selectedChartId.id}/${organization}`); if (response.status === 200) { setSelections(response.data.Data.measurements) setDuration(response.data.Data.duration) + setWidgetName(response.data.widgetName) } else { console.log("Unexpected response:", response); } @@ -181,17 +184,19 @@ const LineGrapInput = (props: Props) => { useEffect(() => { setMeasurements(selections); updateDuration(duration); - }, [selections, duration]); + updateName(widgetName); + }, [selections, duration, widgetName]); - const sendInputes = async(inputMeasurement: any, inputDuration: any) => { + const sendInputes = async (inputMeasurement: any, inputDuration: any, inputName: any) => { try { - const response = await axios.post(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget/save`,{ + const response = await axios.post(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget/save`, { organization: organization, zoneId: selectedZone.zoneId, - widget:{ + widget: { id: selectedChartId.id, panel: selectedChartId.panel, + widgetName: inputName, Data: { measurements: inputMeasurement, duration: inputDuration @@ -210,35 +215,47 @@ const LineGrapInput = (props: Props) => { } } - const handleSelect = async(inputKey: string, selectedData: { name: string; fields: string } | null) => { - - // async() => { - const newSelections = { ...selections }; - if (selectedData === null) { - delete newSelections[inputKey]; - } else { - newSelections[inputKey] = selectedData; - } - // setMeasurements(newSelections); // Update Zustand store - console.log(newSelections); - if ( await sendInputes(newSelections, duration)) { - setSelections(newSelections); - } - // sendInputes(newSelections, duration); // Send data to server - // return newSelections; + const handleSelect = async (inputKey: string, selectedData: { name: string; fields: string } | null) => { + + // async() => { + const newSelections = { ...selections }; + if (selectedData === null) { + delete newSelections[inputKey]; + } else { + newSelections[inputKey] = selectedData; + } + // setMeasurements(newSelections); // Update Zustand store + console.log(newSelections); + if (await sendInputes(newSelections, duration, widgetName)) { + setSelections(newSelections); + } + // sendInputes(newSelections, duration); // Send data to server + // return newSelections; // }; }; - const handleSelectDuration = async(option: string) => { - if ( await sendInputes(selections, option)) { + const handleSelectDuration = async (option: string) => { + if (await sendInputes(selections, option, widgetName)) { setDuration(option); } // setDuration(option); }; + const handleNameChange = async (name:any) => { + console.log('name change requested',name); + + if (await sendInputes(selections, duration, name)) { + setWidgetName(name); + } + } + return ( <>
+
+
Title
+ +
{[...Array(6)].map((_, index) => { const inputKey = `input${index + 1}`; return ( diff --git a/app/src/components/layout/sidebarRight/visualization/data/Data.tsx b/app/src/components/layout/sidebarRight/visualization/data/Data.tsx index 71847c7..2c9b5c6 100644 --- a/app/src/components/layout/sidebarRight/visualization/data/Data.tsx +++ b/app/src/components/layout/sidebarRight/visualization/data/Data.tsx @@ -3,6 +3,7 @@ import { useWidgetStore } from "../../../../../store/useWidgetStore"; import { AddIcon, RemoveIcon } from "../../../../icons/ExportCommonIcons"; import MultiLevelDropDown from "../../../../ui/inputs/MultiLevelDropDown"; import LineGrapInput from "../IotInputCards/LineGrapInput"; +import RenameInput from "../../../../ui/inputs/RenameInput"; // Define the data structure for demonstration purposes const DATA_STRUCTURE = { @@ -107,27 +108,36 @@ const Data = () => { [selectedChartId.id]: currentChartData.map((group) => group.id === groupId ? { - ...group, - children: group.children.filter( - (child) => child.id !== childId - ), - } + ...group, + children: group.children.filter( + (child) => child.id !== childId + ), + } : group ), }; }); }; + console.log("selectedChartId", selectedChartId); return (
- {selectedChartId?.title && ( + {/* {selectedChartId?.title && (
{selectedChartId?.title}
- )} + )} */} + + + {/* */} + {/* Render groups dynamically */} { - chartDataGroups[selectedChartId?.id] && + chartDataGroups[selectedChartId?.id] && + <> +
2D Widget Input
+ + } - + {/* Info Box */}
i diff --git a/app/src/components/ui/FileMenu.tsx b/app/src/components/ui/FileMenu.tsx index 475d01f..a7c876d 100644 --- a/app/src/components/ui/FileMenu.tsx +++ b/app/src/components/ui/FileMenu.tsx @@ -1,12 +1,24 @@ -import React from "react"; +import React, { useState } from "react"; import RenameInput from "./inputs/RenameInput"; +import { ArrowIcon } from "../icons/ExportCommonIcons"; +import MenuBar from "./menu/menu"; const FileMenu: React.FC = () => { + const [openMenu, setOpenMenu] = useState(false); return (
+
{ + setOpenMenu(!openMenu); + }} + > + + {openMenu && } +
); }; diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index 9e7dd1b..71ebfe3 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -14,7 +14,7 @@ import { ZoneIcon, } from "../icons/ExportToolsIcons"; import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons"; -import useModuleStore from "../../store/useModuleStore"; +import useModuleStore, { useThreeDStore } from "../../store/useModuleStore"; import { handleSaveTemplate } from "../../modules/visualization/handleSaveTemplate"; import { usePlayButtonStore } from "../../store/usePlayButtonStore"; import useTemplateStore from "../../store/useTemplateStore"; @@ -32,12 +32,13 @@ import { useTransformMode, } from "../../store/store"; import useToggleStore from "../../store/useUIToggleStore"; +import { use3DWidget, useFloatingWidget } from "../../store/useDroppedObjectsStore"; const Tools: React.FC = () => { const { templates } = useTemplateStore(); const [activeSubTool, setActiveSubTool] = useState("cursor"); - const [toggleThreeD, setToggleThreeD] = useState(true); - const { toggleUI, setToggleUI } = useToggleStore(); + const { toggleThreeD, setToggleThreeD } = useThreeDStore(); + const { setToggleUI } = useToggleStore(); const dropdownRef = useRef(null); const [openDrop, setOpenDrop] = useState(false); @@ -46,6 +47,8 @@ const Tools: React.FC = () => { const { isPlaying, setIsPlaying } = usePlayButtonStore(); const { addTemplate } = useTemplateStore(); const { selectedZone } = useSelectedZoneStore(); + const { floatingWidget } = useFloatingWidget() + const { widgets3D } = use3DWidget() // wall options const { toggleView, setToggleView } = useToggleView(); @@ -68,7 +71,7 @@ const Tools: React.FC = () => { : true ); }, []); - useEffect(() => {}, [activeModule]); + useEffect(() => { }, [activeModule]); useEffect(() => { setActiveTool(activeSubTool); setActiveSubTool(activeSubTool); @@ -210,9 +213,8 @@ const Tools: React.FC = () => {
{activeSubTool == "cursor" && (
{ setActiveTool("cursor"); }} @@ -222,9 +224,8 @@ const Tools: React.FC = () => { )} {activeSubTool == "free-hand" && (
{ setActiveTool("free-hand"); }} @@ -234,9 +235,8 @@ const Tools: React.FC = () => { )} {activeSubTool == "delete" && (
{ setActiveTool("delete"); }} @@ -308,9 +308,8 @@ const Tools: React.FC = () => {
{ setActiveTool("draw-wall"); }} @@ -319,9 +318,8 @@ const Tools: React.FC = () => {
{ setActiveTool("draw-zone"); }} @@ -330,9 +328,8 @@ const Tools: React.FC = () => {
{ setActiveTool("draw-aisle"); }} @@ -341,9 +338,8 @@ const Tools: React.FC = () => {
{ setActiveTool("draw-floor"); }} @@ -359,9 +355,8 @@ const Tools: React.FC = () => {
{ setActiveTool("measure"); }} @@ -377,9 +372,8 @@ const Tools: React.FC = () => {
{ setActiveTool("pen"); }} @@ -395,13 +389,17 @@ const Tools: React.FC = () => {
+ onClick={() => { + handleSaveTemplate({ addTemplate, + floatingWidget, + widgets3D, selectedZone, templates, }) } + } >
@@ -411,9 +409,8 @@ const Tools: React.FC = () => {
{ setActiveTool("comment"); }} @@ -422,9 +419,8 @@ const Tools: React.FC = () => {
{toggleThreeD && (
{ setIsPlaying(!isPlaying); }} @@ -433,20 +429,23 @@ const Tools: React.FC = () => {
)}
-
-
-
- 2d -
-
- 3d -
-
+ {activeModule === "builder" && ( + <> +
+
+
+ 2d +
+
+ 3d +
+
+ + )}
) : ( diff --git a/app/src/components/ui/componets/AddButtons.tsx b/app/src/components/ui/componets/AddButtons.tsx index 4f3f9b7..e0ca342 100644 --- a/app/src/components/ui/componets/AddButtons.tsx +++ b/app/src/components/ui/componets/AddButtons.tsx @@ -119,7 +119,6 @@ const AddButtons: React.FC = ({ }; // Delete the selectedZone state - setSelectedZone(updatedZone); } else { const updatePanelData = async () => { diff --git a/app/src/components/ui/componets/DisplayZone.tsx b/app/src/components/ui/componets/DisplayZone.tsx index 7689e51..3889139 100644 --- a/app/src/components/ui/componets/DisplayZone.tsx +++ b/app/src/components/ui/componets/DisplayZone.tsx @@ -2,9 +2,10 @@ import React, { useEffect, useRef, useState, useCallback } from "react"; import { Widget } from "../../../store/useWidgetStore"; import { MoveArrowLeft, MoveArrowRight } from "../../icons/SimulationIcons"; import { InfoIcon } from "../../icons/ExportCommonIcons"; -import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore"; +import { useDroppedObjectsStore, useFloatingWidget } from "../../../store/useDroppedObjectsStore"; import { getSelect2dZoneData } from "../../../services/realTimeVisulization/zoneData/getSelect2dZoneData"; import { getFloatingZoneData } from "../../../services/realTimeVisulization/zoneData/getFloatingData"; +import { get3dWidgetZoneData } from "../../../services/realTimeVisulization/zoneData/get3dWidgetData"; // Define the type for `Side` type Side = "top" | "bottom" | "left" | "right"; @@ -72,6 +73,7 @@ const DisplayZone: React.FC = ({ // State to track overflow visibility const [showLeftArrow, setShowLeftArrow] = useState(false); const [showRightArrow, setShowRightArrow] = useState(false); + const { floatingWidget, setFloatingWidget } = useFloatingWidget() // Function to calculate overflow state const updateOverflowState = useCallback(() => { @@ -150,14 +152,16 @@ const DisplayZone: React.FC = ({ async function handleSelect2dZoneData(zoneId: string, zoneName: string) { try { if (selectedZone?.zoneId === zoneId) { - console.log("Zone is already selected:", zoneName); + return; } const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; // Fetch data from backend let response = await getSelect2dZoneData(zoneId, organization); + console.log('response: ', response); let res = await getFloatingZoneData(zoneId, organization); + setFloatingWidget(res) // Set the selected zone in the store useDroppedObjectsStore.getState().setZone(zoneName, zoneId); if (Array.isArray(res)) { @@ -177,8 +181,8 @@ const DisplayZone: React.FC = ({ zoneViewPortPosition: response.viewPortposition || {}, }); } catch (error) { - console.log('error: ', error); - + + } } @@ -186,9 +190,8 @@ const DisplayZone: React.FC = ({ return (
{/* Left Arrow */} {showLeftArrow && ( diff --git a/app/src/components/ui/componets/DistanceLine.tsx b/app/src/components/ui/componets/DistanceLine.tsx new file mode 100644 index 0000000..8dec7b1 --- /dev/null +++ b/app/src/components/ui/componets/DistanceLine.tsx @@ -0,0 +1,93 @@ +import React from "react"; + +interface DistanceLinesProps { + obj: { + position: { + top?: number | "auto"; + left?: number | "auto"; + right?: number | "auto"; + bottom?: number | "auto"; + }; + }; + activeEdges: { + vertical: "top" | "bottom"; + horizontal: "left" | "right"; + } | null; +} + +const DistanceLines: React.FC = ({ obj, activeEdges }) => { + if (!activeEdges) return null; + + return ( + <> + {activeEdges.vertical === "top" && typeof obj.position.top === "number" && ( +
+ {obj.position.top.toFixed()}px +
+ )} + + {activeEdges.vertical === "bottom" && + typeof obj.position.bottom === "number" && ( +
+ {obj.position.bottom.toFixed()}px +
+ )} + + {activeEdges.horizontal === "left" && + typeof obj.position.left === "number" && ( +
+ {obj.position.left.toFixed()}px +
+ )} + + {activeEdges.horizontal === "right" && + typeof obj.position.right === "number" && ( +
+ {obj.position.right.toFixed()}px +
+ )} + + ); +}; + +export default DistanceLines; \ No newline at end of file diff --git a/app/src/components/ui/componets/DistanceLines.tsx b/app/src/components/ui/componets/DistanceLines.tsx new file mode 100644 index 0000000..07cf202 --- /dev/null +++ b/app/src/components/ui/componets/DistanceLines.tsx @@ -0,0 +1,93 @@ +import React from "react"; + +interface DistanceLinesProps { + obj: { + position: { + top?: number | "auto"; + left?: number | "auto"; + right?: number | "auto"; + bottom?: number | "auto"; + }; + }; + activeEdges: { + vertical: "top" | "bottom"; + horizontal: "left" | "right"; + } | null; +} + +const DistanceLines: React.FC = ({ obj, activeEdges }) => { + if (!activeEdges) return null; + + return ( + <> + {activeEdges.vertical === "top" && typeof obj.position.top === "number" && ( +
+ {obj.position.top}px +
+ )} + + {activeEdges.vertical === "bottom" && + typeof obj.position.bottom === "number" && ( +
+ {obj.position.bottom}px +
+ )} + + {activeEdges.horizontal === "left" && + typeof obj.position.left === "number" && ( +
+ {obj.position.left}px +
+ )} + + {activeEdges.horizontal === "right" && + typeof obj.position.right === "number" && ( +
+ {obj.position.right}px +
+ )} + + ); +}; + +export default DistanceLines; \ No newline at end of file diff --git a/app/src/components/ui/componets/DraggableWidget.tsx b/app/src/components/ui/componets/DraggableWidget.tsx index 14916b0..db8c408 100644 --- a/app/src/components/ui/componets/DraggableWidget.tsx +++ b/app/src/components/ui/componets/DraggableWidget.tsx @@ -306,34 +306,20 @@ export const DraggableWidget = ({ )} {widget.type === "doughnut" && ( )} {widget.type === "polarArea" && ( )}
diff --git a/app/src/components/ui/componets/Dropped3dWidget.tsx b/app/src/components/ui/componets/Dropped3dWidget.tsx index 10dd343..94c5e45 100644 --- a/app/src/components/ui/componets/Dropped3dWidget.tsx +++ b/app/src/components/ui/componets/Dropped3dWidget.tsx @@ -10,79 +10,138 @@ import ProductionCapacity from "../../layout/3D-cards/cards/ProductionCapacity"; import ReturnOfInvestment from "../../layout/3D-cards/cards/ReturnOfInvestment"; import StateWorking from "../../layout/3D-cards/cards/StateWorking"; import { useSelectedZoneStore } from "../../../store/useZoneStore"; +import { generateUniqueId } from "../../../functions/generateUniqueId"; +import { adding3dWidgets } from "../../../services/realTimeVisulization/zoneData/add3dWidget"; +import { get3dWidgetZoneData } from "../../../services/realTimeVisulization/zoneData/get3dWidgetData"; +import { use3DWidget } from "../../../store/useDroppedObjectsStore"; export default function Dropped3dWidgets() { - const { widgetSelect, setWidgetSelect } = useAsset3dWidget(); + const { widgetSelect } = useAsset3dWidget(); const { activeModule } = useModuleStore(); const { raycaster, gl, scene }: ThreeState = useThree(); - const { selectedZone } = useSelectedZoneStore(); // Get currently selected zone - const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption() - // 🔥 Store widget positions per zone - const [zoneWidgets, setZoneWidgets] = useState // Widget type -> Positions array - >>({}); + const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption(); + const { selectedZone } = useSelectedZoneStore(); // Get the currently active zone + // 🔥 Store widget data (id, type, position) based on the selected zone + const [zoneWidgetData, setZoneWidgetData] = useState< + Record + >({}); + const { setWidgets3D } = use3DWidget() + useEffect(() => { + if (activeModule !== "visualization") return + if (selectedZone.zoneName === "") return; + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + + async function get3dWidgetData() { + let result = await get3dWidgetZoneData(selectedZone.zoneId, organization); + setWidgets3D(result) + // Ensure the extracted data has id, type, and position correctly mapped + const formattedWidgets = result.map((widget: any) => ({ + id: widget.id, + type: widget.type, + position: widget.position, + })); + + setZoneWidgetData((prev) => ({ + ...prev, + [selectedZone.zoneId]: formattedWidgets, + })); + } + + get3dWidgetData(); + + }, [selectedZone.zoneId,activeModule]); + // useEffect(() => { + // // ✅ Set data only for the selected zone, keeping existing state structure + // setZoneWidgetData((prev) => ({ + // ...prev, + // [selectedZone.zoneId]: [ + // { + // "id": "1743322674626-50mucpb1c", + // "type": "ui-Widget 1", + // "position": [120.94655021768133, 4.142360029666558, 124.39283546121099] + // }, + // { + // "id": "1743322682086-je2h9x33v", + // "type": "ui-Widget 2", + // "position": [131.28751045879255, 0.009999999999970264, 133.92059801984362] + // } + // ] + // })); + // }, [selectedZone.zoneId]); // ✅ Only update when the zone changes useEffect(() => { - if (widgetSubOption === "Floating") return - // if (activeModule !== "visualization") return; + if (activeModule !== "visualization") return; + if (widgetSubOption === "Floating") return; + if (selectedZone.zoneName === "") return const canvasElement = gl.domElement; - const onDrop = (event: DragEvent) => { + const onDrop = async (event: DragEvent) => { event.preventDefault(); // Prevent default browser behavior - if (widgetSubOption === "3D") { - if (selectedZone.zoneName === "") return - if (!widgetSelect?.startsWith("ui")) return; - const group1 = scene.getObjectByName("itemsGroup"); - if (!group1) return; - const Assets = group1.children - .map((val) => scene.getObjectByProperty("uuid", val.uuid)) - .filter(Boolean) as THREE.Object3D[]; - const intersects = raycaster.intersectObjects(scene.children, true).filter( - (intersect) => - !intersect.object.name.includes("Roof") && - !intersect.object.name.includes("agv-collider") && - !intersect.object.name.includes("MeasurementReference") && - !intersect.object.userData.isPathObject && - !(intersect.object.type === "GridHelper") - ); - if (intersects.length > 0) { - const { x, y, z } = intersects[0].point; + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + if (!widgetSelect.startsWith("ui")) return; + const group1 = scene.getObjectByName("itemsGroup"); + if (!group1) return; + const intersects = raycaster.intersectObjects(scene.children, true).filter( + (intersect) => + !intersect.object.name.includes("Roof") && + !intersect.object.name.includes("agv-collider") && + !intersect.object.name.includes("MeasurementReference") && + !intersect.object.userData.isPathObject && + !(intersect.object.type === "GridHelper") + ); + if (intersects.length > 0) { + const { x, y, z } = intersects[0].point; - setZoneWidgets((prev) => ({ - ...prev, - [selectedZone.zoneId]: { - ...(prev[selectedZone.zoneId] || {}), - [widgetSelect]: [...(prev[selectedZone.zoneId]?.[widgetSelect] || []), [x, y, z]], - }, - })); - } + // ✅ Explicitly define position as a tuple + const newWidget: { id: string; type: string; position: [number, number, number] } = { + id: generateUniqueId(), + type: widgetSelect, + position: [x, y, z], // Ensures TypeScript recognizes it as a tuple + }; + + + let response = await adding3dWidgets(selectedZone.zoneId, organization, newWidget) + + + // ✅ Store widgets uniquely for each zone + setZoneWidgetData((prev) => ({ + ...prev, + [selectedZone.zoneId]: [...(prev[selectedZone.zoneId] || []), newWidget], + })); } }; + canvasElement.addEventListener("drop", onDrop); return () => { - canvasElement.removeEventListener("drop", onDrop) - // setWidgetSelect() + canvasElement.removeEventListener("drop", onDrop); }; - }, [widgetSelect, activeModule, widgetSubOption]); + }, [widgetSelect, activeModule, selectedZone.zoneId, widgetSubOption]); + + // Get widgets for the currently active zone + const activeZoneWidgets = zoneWidgetData[selectedZone.zoneId] || []; return ( <> - {zoneWidgets[selectedZone.zoneId]?.["ui-Widget 1"]?.map((pos, index) => ( - - ))} - {zoneWidgets[selectedZone.zoneId]?.["ui-Widget 2"]?.map((pos, index) => ( - - ))} - {zoneWidgets[selectedZone.zoneId]?.["ui-Widget 3"]?.map((pos, index) => ( - - ))} - {zoneWidgets[selectedZone.zoneId]?.["ui-Widget 4"]?.map((pos, index) => ( - - ))} + {activeZoneWidgets.map(({ id, type, position }) => { + switch (type) { + case "ui-Widget 1": + return ; + case "ui-Widget 2": + return ; + case "ui-Widget 3": + return ; + case "ui-Widget 4": + return ; + default: + return null; + } + })} ); } + diff --git a/app/src/components/ui/componets/DroppedFloatingWidgets.tsx b/app/src/components/ui/componets/DroppedFloatingWidgets.tsx index bf19bb9..d7a6960 100644 --- a/app/src/components/ui/componets/DroppedFloatingWidgets.tsx +++ b/app/src/components/ui/componets/DroppedFloatingWidgets.tsx @@ -1,6 +1,6 @@ + import { WalletIcon } from "../../icons/3dChartIcons"; import { useEffect, useRef, useState } from "react"; -import { Line } from "react-chartjs-2"; import { useDroppedObjectsStore, Zones, @@ -9,29 +9,102 @@ import useModuleStore from "../../../store/useModuleStore"; import { determinePosition } from "./functions/determinePosition"; import { getActiveProperties } from "./functions/getActiveProperties"; import { addingFloatingWidgets } from "../../../services/realTimeVisulization/zoneData/addFloatingWidgets"; +import { + DublicateIcon, + KebabIcon, + DeleteIcon, +} from "../../icons/ExportCommonIcons"; +import DistanceLines from "./DistanceLines"; // Import the DistanceLines component +import { deleteFloatingWidgetApi } from "../../../services/realTimeVisulization/zoneData/deleteFloatingWidget"; +import TotalCardComponent from "../realTimeVis/floating/TotalCardComponent"; +import WarehouseThroughputComponent from "../realTimeVis/floating/WarehouseThroughputComponent"; +import FleetEfficiencyComponent from "../realTimeVis/floating/FleetEfficiencyComponent"; +import { useWidgetStore } from "../../../store/useWidgetStore"; +interface DraggingState { + zone: string; + index: number; + initialPosition: { + top: number | "auto"; + left: number | "auto"; + right: number | "auto"; + bottom: number | "auto"; + }; +} + +interface DraggingState { + zone: string; + index: number; + initialPosition: { + top: number | "auto"; + left: number | "auto"; + right: number | "auto"; + bottom: number | "auto"; + }; +} const DroppedObjects: React.FC = () => { const zones = useDroppedObjectsStore((state) => state.zones); - + const [openKebabId, setOpenKebabId] = useState(null); const updateObjectPosition = useDroppedObjectsStore( (state) => state.updateObjectPosition ); - const [draggingIndex, setDraggingIndex] = useState<{ - zone: string; - index: number; - } | null>(null); + const deleteObject = useDroppedObjectsStore((state) => state.deleteObject); + + const duplicateObject = useDroppedObjectsStore((state) => state.duplicateObject); + const [draggingIndex, setDraggingIndex] = useState( + null + ); const [offset, setOffset] = useState<[number, number] | null>(null); - const positionRef = useRef<[number, number] | null>(null); + const { setSelectedChartId } = useWidgetStore(); + const [activeEdges, setActiveEdges] = useState<{ + vertical: "top" | "bottom"; + horizontal: "left" | "right"; + } | null>(null); // State to track active edges for distance lines + const [currentPosition, setCurrentPosition] = useState<{ + top: number | "auto"; + left: number | "auto"; + right: number | "auto"; + bottom: number | "auto"; + } | null>(null); // State to track the current position during drag const animationRef = useRef(null); const { activeModule } = useModuleStore(); - // Get the first zone and its objects - const zoneEntries = Object.entries(zones); - if (zoneEntries.length === 0) return null; // No zone, nothing to render - const [zoneName, zone] = zoneEntries[0]; // Only render the first zone + // Clean up animation frame on unmount + useEffect(() => { + return () => { + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } + }; + }, []); - // Handle pointer down event - function handlePointerDown(event: React.PointerEvent, index: number) { + const zoneEntries = Object.entries(zones); + if (zoneEntries.length === 0) return null; + const [zoneName, zone] = zoneEntries[0]; + + function handleDuplicate(zoneName: string, index: number) { + setOpenKebabId(null) + duplicateObject(zoneName, index); // Call the duplicateObject method from the store + } + + async function handleDelete(zoneName: string, id: string, index: number) { + try { + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + + let res = await deleteFloatingWidgetApi(id, organization); + console.log('res: ', res); + + if (res.message === "FloatingWidget deleted successfully") { + deleteObject(zoneName, index); // Call the deleteObject method from the store + } + } catch (error) { + console.error("Error deleting floating widget:", error); + } + } + + + const handlePointerDown = (event: React.PointerEvent, index: number) => { const obj = zone.objects[index]; const container = document.getElementById("real-time-vis-canvas"); if (!container) return; @@ -40,41 +113,41 @@ const DroppedObjects: React.FC = () => { const relativeX = event.clientX - rect.left; const relativeY = event.clientY - rect.top; - // Determine which properties are active for this object + // Determine active properties for the initial position const [activeProp1, activeProp2] = getActiveProperties(obj.position); - // Calculate the offset based on the active properties + // Set active edges for distance lines + const vertical = activeProp1 === "top" ? "top" : "bottom"; + const horizontal = activeProp2 === "left" ? "left" : "right"; + setActiveEdges({ vertical, horizontal }); + + // Store the initial position strategy and active edges + setDraggingIndex({ + zone: zoneName, + index, + initialPosition: { ...obj.position }, + }); + + // Calculate offset from mouse to object edges let offsetX = 0; let offsetY = 0; if (activeProp1 === "top") { - offsetY = - relativeY - - (typeof obj.position.top === "number" ? obj.position.top : 0); - } else if (activeProp1 === "bottom") { - offsetY = - rect.height - - relativeY - - (typeof obj.position.bottom === "number" ? obj.position.bottom : 0); + offsetY = relativeY - (obj.position.top as number); + } else { + offsetY = rect.height - relativeY - (obj.position.bottom as number); } if (activeProp2 === "left") { - offsetX = - relativeX - - (typeof obj.position.left === "number" ? obj.position.left : 0); - } else if (activeProp2 === "right") { - offsetX = - rect.width - - relativeX - - (typeof obj.position.right === "number" ? obj.position.right : 0); + offsetX = relativeX - (obj.position.left as number); + } else { + offsetX = rect.width - relativeX - (obj.position.right as number); } - setDraggingIndex({ zone: zoneName, index }); setOffset([offsetY, offsetX]); - } + }; - // Handle pointer move event - function handlePointerMove(event: React.PointerEvent) { + const handlePointerMove = (event: React.PointerEvent) => { if (!draggingIndex || !offset) return; const container = document.getElementById("real-time-vis-canvas"); @@ -84,91 +157,194 @@ const DroppedObjects: React.FC = () => { const relativeX = event.clientX - rect.left; const relativeY = event.clientY - rect.top; - // Determine which properties are active for the dragged object - const obj = zone.objects[draggingIndex.index]; - const [activeProp1, activeProp2] = getActiveProperties(obj.position); + // Dynamically determine the current position strategy + const newPositionStrategy = determinePosition(rect, relativeX, relativeY); + const [activeProp1, activeProp2] = getActiveProperties(newPositionStrategy); - // Calculate the new position based on the active properties - let newX = 0; + // Update active edges for distance lines + const vertical = activeProp1 === "top" ? "top" : "bottom"; + const horizontal = activeProp2 === "left" ? "left" : "right"; + setActiveEdges({ vertical, horizontal }); + + // Calculate new position based on the active properties let newY = 0; - - if (activeProp2 === "left") { - newX = relativeX - offset[1]; - } else if (activeProp2 === "right") { - newX = rect.width - (relativeX + offset[1]); - } + let newX = 0; if (activeProp1 === "top") { newY = relativeY - offset[0]; - } else if (activeProp1 === "bottom") { + } else { newY = rect.height - (relativeY + offset[0]); } - // Ensure the object stays within the canvas boundaries + if (activeProp2 === "left") { + newX = relativeX - offset[1]; + } else { + newX = rect.width - (relativeX + offset[1]); + } + + // Apply boundaries newX = Math.max(0, Math.min(rect.width - 50, newX)); newY = Math.max(0, Math.min(rect.height - 50, newY)); - // Update the position reference - positionRef.current = [newY, newX]; + // Create new position object + const newPosition = { + ...newPositionStrategy, + [activeProp1]: newY, + [activeProp2]: newX, + // Clear opposite properties + [activeProp1 === "top" ? "bottom" : "top"]: "auto", + [activeProp2 === "left" ? "right" : "left"]: "auto", + }; + + // Update the current position state for DistanceLines + setCurrentPosition(newPosition); - // Update the object's position using requestAnimationFrame for smoother animations if (!animationRef.current) { animationRef.current = requestAnimationFrame(() => { - if (positionRef.current) { - updateObjectPosition(zoneName, draggingIndex.index, { - ...obj.position, - [activeProp1]: positionRef.current[0], - [activeProp2]: positionRef.current[1], - }); - } + updateObjectPosition(zoneName, draggingIndex.index, newPosition); animationRef.current = null; }); } - } + }; - // Handle pointer up event - async function handlePointerUp(event: React.MouseEvent) { + const handlePointerUp = async (event: React.PointerEvent) => { try { if (!draggingIndex || !offset) return; - const email = localStorage.getItem("email") || ""; - const organization = email?.split("@")[1]?.split(".")[0]; const container = document.getElementById("real-time-vis-canvas"); - if (!container) throw new Error("Canvas container not found"); + if (!container) return; const rect = container.getBoundingClientRect(); const relativeX = event.clientX - rect.left; const relativeY = event.clientY - rect.top; - // Recalculate the position using determinePosition - const newPosition = determinePosition(rect, relativeX, relativeY); + // Only now determine the final position strategy + const finalPosition = determinePosition(rect, relativeX, relativeY); + const [activeProp1, activeProp2] = getActiveProperties(finalPosition); - // Validate the dragging index and get the object - if (!zone.objects[draggingIndex.index]) { - throw new Error("Dragged object not found in the zone"); + // Calculate final position using the new strategy + let finalY = 0; + let finalX = 0; + + if (activeProp1 === "top") { + finalY = relativeY - offset[0]; + } else { + finalY = rect.height - (relativeY + offset[0]); } - const obj = { ...zone.objects[draggingIndex.index], position: newPosition }; - let response = await addingFloatingWidgets(zone.zoneId, organization, obj); + + if (activeProp2 === "left") { + finalX = relativeX - offset[1]; + } else { + finalX = rect.width - (relativeX + offset[1]); + } + + // Apply boundaries + finalX = Math.max(0, Math.min(rect.width - 50, finalX)); + finalY = Math.max(0, Math.min(rect.height - 50, finalY)); + + const boundedPosition = { + ...finalPosition, + [activeProp1]: finalY, + [activeProp2]: finalX, + }; + + // Save to backend + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + const response = await addingFloatingWidgets(zone.zoneId, organization, { + ...zone.objects[draggingIndex.index], + position: boundedPosition, + }); + if (response.message === "Widget updated successfully") { - updateObjectPosition(zoneName, draggingIndex.index, newPosition); + updateObjectPosition(zoneName, draggingIndex.index, boundedPosition); } - // Reset states + // Clean up setDraggingIndex(null); setOffset(null); - + setActiveEdges(null); // Clear active edges + setCurrentPosition(null); // Reset current position if (animationRef.current) { cancelAnimationFrame(animationRef.current); animationRef.current = null; } } catch (error) { - + console.error("Error in handlePointerUp:", error); } - } + }; + const handleKebabClick = (id: string, event: React.MouseEvent) => { + event.stopPropagation(); + setOpenKebabId((prevId) => (prevId === id ? null : id)); + }; + + const renderObjectContent = (obj: any) => { + switch (obj.className) { + case "floating total-card": + return ( + <> +
+
{obj.header}
+
+
{obj.value}
+
{obj.per}
+
+
+
+ +
+ + ); + case "warehouseThroughput floating": + return ( + <> +
+

Warehouse Throughput

+

+ (+5) more in 2025 +

+
+
+ {/* */} +
+ + ); + case "fleetEfficiency floating": + return ( + <> +

Fleet Efficiency

+
+
+
+
+
+
+
+
+ 0% +
+
{obj.per}%
+
Optimal
+
+ 100% +
+ + ); + default: + return null; + } + }; return ( -
+
{zone.objects.map((obj, index) => (
{ style={{ position: "absolute", top: - typeof obj.position.top !== "string" + typeof obj.position.top === "number" ? `${obj.position.top}px` : "auto", left: - typeof obj.position.left !== "string" + typeof obj.position.left === "number" ? `${obj.position.left}px` : "auto", right: - typeof obj.position.right !== "string" + typeof obj.position.right === "number" ? `${obj.position.right}px` : "auto", bottom: - typeof obj.position.bottom !== "string" + typeof obj.position.bottom === "number" ? `${obj.position.bottom}px` : "auto", - // transition: draggingIndex?.index === index ? "none" : "transform 0.1s ease-out", }} onPointerDown={(event) => handlePointerDown(event, index)} + onClick={() => { + setSelectedChartId(obj) + }} > {obj.className === "floating total-card" ? ( <> -
-
{obj.header}
-
-
{obj.value}
-
{obj.per}
-
-
-
- -
+ ) : obj.className === "warehouseThroughput floating" ? ( <> -
-

Warehouse Throughput

-

- (+5) more in 2025 -

-
-
- {/* */} -
+ ) : obj.className === "fleetEfficiency floating" ? ( <> -

Fleet Efficiency

-
-
-
-
-
-
-
-
- 0% -
-
{obj.per}%
-
Optimal
-
- 100% -
+ ) : null} + {renderObjectContent(obj)} +
handleKebabClick(obj.id, event)} + > + +
+ {openKebabId === obj.id && ( +
+
{ + event.stopPropagation(); + handleDuplicate(zoneName, index); // Call the duplicate handler + }}> +
+ +
+
Duplicate
+
+
{ + event.stopPropagation(); + handleDelete(zoneName, obj.id, index); // Call the delete handler + }}> +
+ +
+
Delete
+
+
+ )}
))} + + {/* Render DistanceLines component during drag */} + {draggingIndex !== null && + activeEdges !== null && + currentPosition !== null && ( + + )}
); }; export default DroppedObjects; + + diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index 45db987..f6a041a 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -63,6 +63,8 @@ const RealTimeVisulization: React.FC = () => { const organization = email?.split("@")[1]?.split(".")[0]; try { const response = await getZone2dData(organization); + console.log('response: ', response); + if (!Array.isArray(response)) { return; } @@ -83,7 +85,7 @@ const RealTimeVisulization: React.FC = () => { ); setZonesData(formattedData); } catch (error) { - console.log("error: ", error); + } } @@ -109,7 +111,7 @@ const RealTimeVisulization: React.FC = () => { }); }, [selectedZone]); - useEffect(() => {}, [floatingWidgets]); + // useEffect(() => {}, [floatingWidgets]); const handleDrop = async (event: React.DragEvent) => { try { @@ -135,6 +137,7 @@ const RealTimeVisulization: React.FC = () => { id: generateUniqueId(), position: determinePosition(canvasRect, relativeX, relativeY), }; + console.log('newObject: ', newObject); let response = await addingFloatingWidgets( selectedZone.zoneId, @@ -171,7 +174,7 @@ const RealTimeVisulization: React.FC = () => { ], }, })); - } catch (error) {} + } catch (error) { } }; return ( @@ -198,7 +201,8 @@ const RealTimeVisulization: React.FC = () => { >
- + {activeModule === "visualization" && selectedZone.zoneName !== "" && } + {/* */} {activeModule === "visualization" && ( <> { - let activeProps: ["top", "left"] | ["bottom", "right"] = ["top", "left"]; // Default to top-left - - if ( - typeof position.bottom !== "string" && - typeof position.right !== "string" - ) { - activeProps = ["bottom", "right"]; +export function getActiveProperties(position: any): [string, string] { + if (position.top !== "auto" && position.left !== "auto") { + return ["top", "left"]; // Top-left + } else if (position.top !== "auto" && position.right !== "auto") { + return ["top", "right"]; // Top-right + } else if (position.bottom !== "auto" && position.left !== "auto") { + return ["bottom", "left"]; // Bottom-left + } else { + return ["bottom", "right"]; // Bottom-right } - - return activeProps; -}; +} \ No newline at end of file diff --git a/app/src/components/ui/componets/handleDropTemplate .tsx b/app/src/components/ui/componets/handleDropTemplate .tsx new file mode 100644 index 0000000..7992ee8 --- /dev/null +++ b/app/src/components/ui/componets/handleDropTemplate .tsx @@ -0,0 +1,29 @@ +// import { useSelectedZoneStore } from "../../../store/useZoneStore"; + + +// type HandleDropTemplateProps = { +// templateId: string; +// }; + +// export const handleDropTemplate = ({ templateId }: HandleDropTemplateProps): void => { +// const { getTemplate } = useTemplateStore.getState(); +// const { setSelectedZone } = useSelectedZoneStore.getState(); + +// // Find the template by ID +// const template: Template | undefined = getTemplate(templateId); + +// if (!template) { +// console.error("Template not found!"); +// return; +// } + +// // Update the selected zone with the template data +// setSelectedZone((prev) => ({ +// ...prev, +// panelOrder: template.panelOrder, +// activeSides: Array.from(new Set([...prev.activeSides, ...template.panelOrder])), +// widgets: template.widgets, // Keep widget structure the same +// })); + +// console.log("Dropped template applied:", template); +// }; diff --git a/app/src/components/ui/menu/menu.tsx b/app/src/components/ui/menu/menu.tsx index b374237..a416c2c 100644 --- a/app/src/components/ui/menu/menu.tsx +++ b/app/src/components/ui/menu/menu.tsx @@ -1,6 +1,12 @@ import React, { useState } from "react"; +import { ArrowIcon } from "../../icons/ExportCommonIcons"; +import { toggleTheme } from "../../../utils/theme"; -const MenuBar = () => { +interface MenuBarProps { + setOpenMenu: (isOpen: boolean) => void; // Function to update menu state +} + +const MenuBar: React.FC = ({ setOpenMenu }) => { const [activeMenu, setActiveMenu] = useState(null); const [activeSubMenu, setActiveSubMenu] = useState(null); @@ -17,8 +23,20 @@ const MenuBar = () => { })); }; + function handleThemeChange(){ + toggleTheme(); + window.location.reload(); + } + + const savedTheme: string | null = localStorage.getItem("theme") || "light"; + return ( -
+
{ + setOpenMenu(false); + }} + > {/* Top-level menu buttons */}
{/* File Menu */} @@ -32,7 +50,9 @@ const MenuBar = () => { >
File - + + +
{/* File Dropdown */} @@ -44,10 +64,7 @@ const MenuBar = () => { onClick={() => toggleSelection("New File")} >
- - {selectedItems["New File"] && "✓ "} - New File - + New File
Ctrl + N
@@ -60,10 +77,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Open Local File")} >
- - {selectedItems["Open Local File"] && "✓ "} - Open Local File - + Open Local File
Ctrl + O
@@ -76,10 +90,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Save Version")} >
- - {selectedItems["Save Version"] && "✓ "} - Save Version - + Save Version
@@ -90,10 +101,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Make a Copy")} >
- - {selectedItems["Make a Copy"] && "✓ "} - Make a Copy - + Make a Copy
@@ -103,10 +111,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Share")} >
- - {selectedItems["Share"] && "✓ "} - Share - + Share
@@ -116,10 +121,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Rename")} >
- - {selectedItems["Rename"] && "✓ "} - Rename - + Rename
@@ -130,10 +132,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Import")} >
- - {selectedItems["Import"] && "✓ "} - Import - + Import
@@ -143,10 +142,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Close File")} >
- - {selectedItems["Close File"] && "✓ "} - Close File - + Close File
@@ -164,7 +160,9 @@ const MenuBar = () => { >
Edit - + + +
{/* Edit Dropdown */} @@ -176,10 +174,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Undo")} >
- - {selectedItems["Undo"] && "✓ "} - Undo - + Undo
Ctrl + Z
@@ -192,10 +187,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Redo")} >
- - {selectedItems["Redo"] && "✓ "} - Redo - + Redo
Ctrl + Shift + Z
@@ -209,10 +201,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Undo History")} >
- - {selectedItems["Undo History"] && "✓ "} - Undo History - + Undo History
@@ -222,10 +211,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Redo History")} >
- - {selectedItems["Redo History"] && "✓ "} - Redo History - + Redo History
@@ -236,10 +222,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Find")} >
- - {selectedItems["Find"] && "✓ "} - Find - + Find
Ctrl + F
@@ -252,10 +235,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Delete")} >
- - {selectedItems["Delete"] && "✓ "} - Delete - + Delete
@@ -265,10 +245,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Select by...")} >
- - {selectedItems["Select by..."] && "✓ "} - Select by... - + Select by...
@@ -278,10 +255,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Keymap")} >
- - {selectedItems["Keymap"] && "✓ "} - Keymap - + Keymap
@@ -299,7 +273,9 @@ const MenuBar = () => { >
View - + + +
{/* View Dropdown */} @@ -311,10 +287,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Grid")} >
- - {selectedItems["Grid"] && "✓ "} - Grid - + Grid
@@ -325,11 +298,10 @@ const MenuBar = () => { onMouseLeave={() => setActiveSubMenu(null)} >
- - {selectedItems["Gizmo"] && "✓ "} - Gizmo + Gizmo + + -
@@ -346,16 +318,14 @@ const MenuBar = () => { Visibility
+
{/* Cube view */}
toggleSelection("Cube view")} > - - {selectedItems["Cube view"] && "✓ "} - Cube view - + Cube view
{/* Sphere view */} @@ -363,21 +333,7 @@ const MenuBar = () => { className="submenu-item" onClick={() => toggleSelection("Sphere view")} > - - {selectedItems["Sphere view"] && "✓ "} - Sphere view - -
- - {/* Custom settings */} -
toggleSelection("Custom settings")} - > - - {selectedItems["Custom settings"] && "✓ "} - Custom settings - + Sphere view
)} @@ -389,10 +345,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Zoom")} >
- - {selectedItems["Zoom"] && "✓ "} - Zoom - + Zoom
@@ -402,10 +355,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Full Screen")} >
- - {selectedItems["Full Screen"] && "✓ "} - Full Screen - + Full Screen
F11
@@ -424,11 +374,7 @@ const MenuBar = () => { setActiveSubMenu(null); }} > -
- Version history -
- - +
Version history
{/* Export As Menu */} @@ -440,14 +386,27 @@ const MenuBar = () => { setActiveSubMenu(null); }} > -
- Export as... -
+
Export as...
+
+
setActiveMenu("theme")} + onMouseLeave={() => { + setActiveMenu(null); + setActiveSubMenu(null); + }} + onClick={() => { + handleThemeChange(); + }} + > +
+ Theme
{savedTheme}
+
{/* Apps Menu */} -
setActiveMenu("Apps")} onMouseLeave={() => { @@ -457,68 +416,64 @@ const MenuBar = () => { >
Apps - + + +
- {/* Apps Dropdown */} {activeMenu === "Apps" && (
- {/* New App */}
toggleSelection("New App")} >
- {selectedItems["New App"] && "✓ "} + New App
-
- {/* Work-flow Monitor */}
toggleSelection("Work-flow Monitor")} >
- {selectedItems["Work-flow Monitor"] && "✓ "} + Work-flow Monitor
- {/* Temperature Visualizer */}
toggleSelection("Temperature Visualizer")} >
- {selectedItems["Temperature Visualizer"] && "✓ "} + Temperature Visualizer
- {/* View all */}
toggleSelection("View all")} >
- {selectedItems["View all"] && "✓ "} + View all
)} -
+
*/} {/* Help Menu */}
{ >
Help - + + +
{/* Help Dropdown */} @@ -543,10 +500,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Shortcuts")} >
- - {selectedItems["Shortcuts"] && "✓ "} - Shortcuts - + Shortcuts
Ctrl + Shift + ?
@@ -559,10 +513,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Manual")} >
- - {selectedItems["Manual"] && "✓ "} - Manual - + Manual
@@ -572,10 +523,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Video Tutorials")} >
- - {selectedItems["Video Tutorials"] && "✓ "} - Video Tutorials - + Video Tutorials
@@ -585,10 +533,7 @@ const MenuBar = () => { onClick={() => toggleSelection("Report a bug")} >
- - {selectedItems["Report a bug"] && "✓ "} - Report a bug - + Report a bug
@@ -600,5 +545,3 @@ const MenuBar = () => { }; export default MenuBar; - - diff --git a/app/src/components/ui/realTimeVis/charts/BarGraphComponent.tsx b/app/src/components/ui/realTimeVis/charts/BarGraphComponent.tsx index 26f7a4b..4876fe4 100644 --- a/app/src/components/ui/realTimeVis/charts/BarGraphComponent.tsx +++ b/app/src/components/ui/realTimeVis/charts/BarGraphComponent.tsx @@ -211,9 +211,10 @@ const BarGraphComponent = ({ fontWeight = "Regular", }: ChartComponentProps) => { const { themeColor } = useThemeStore(); - const { measurements: chartMeasurements, duration: chartDuration } = useChartStore(); + const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); const [measurements, setmeasurements] = useState({}); const [duration, setDuration] = useState("1h") + const [name, setName] = useState("Widget") const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({ labels: [], datasets: [], @@ -236,6 +237,10 @@ const BarGraphComponent = ({ ], }; + useEffect(() => { + console.log("titleeeeeeeeeeeeeeeeeee",title); + },[]) + // Memoize Theme Colors const buttonActionColor = useMemo(() => themeColor[0] || "#5c87df", [themeColor]); const buttonAbortColor = useMemo(() => themeColor[1] || "#ffffff", [themeColor]); @@ -270,7 +275,7 @@ const BarGraphComponent = ({ plugins: { title: { display: true, - text: title, + text: name, font: chartFontStyle, }, legend: { @@ -285,7 +290,7 @@ const BarGraphComponent = ({ }, }, }), - [title, chartFontStyle] + [title, chartFontStyle, name] ); // useEffect(() => {console.log(measurements); @@ -304,15 +309,12 @@ const BarGraphComponent = ({ const startStream = () => { - console.log("inputtttttttttt",inputData); socket.emit("lineInput", inputData); }; socket.on("connect", startStream); socket.on("lineOutput", (response) => { - console.log("responce dataaaaaaaaa",response.data); - const responseData = response.data; // Extract timestamps and values @@ -347,6 +349,7 @@ const BarGraphComponent = ({ if (response.status === 200) { setmeasurements(response.data.Data.measurements) setDuration(response.data.Data.duration) + setName(response.data.widgetName) } else { console.log("Unexpected response:", response); } @@ -365,7 +368,7 @@ const BarGraphComponent = ({ fetchSavedInputes(); } } - ,[chartMeasurements, chartDuration]) + ,[chartMeasurements, chartDuration, widgetName]) return 0 ? chartData : defaultData} options={options} />; }; diff --git a/app/src/components/ui/realTimeVis/charts/DoughnutGraphComponent.tsx b/app/src/components/ui/realTimeVis/charts/DoughnutGraphComponent.tsx index c1d5ac2..6eec49e 100644 --- a/app/src/components/ui/realTimeVis/charts/DoughnutGraphComponent.tsx +++ b/app/src/components/ui/realTimeVis/charts/DoughnutGraphComponent.tsx @@ -1,22 +1,64 @@ -import { useMemo } from "react"; -import { Doughnut, Line } from "react-chartjs-2"; +import React, { useEffect, useMemo, useState } from "react"; +import { Doughnut } from "react-chartjs-2"; +import io from "socket.io-client"; +import { useThemeStore } from "../../../../store/useThemeStore"; +import useChartStore from "../../../../store/useChartStore"; +import { useWidgetStore } from "../../../../store/useWidgetStore"; +import axios from "axios"; interface ChartComponentProps { + id: string; type: any; title: string; fontFamily?: string; fontSize?: string; fontWeight?: "Light" | "Regular" | "Bold"; - data: any; } const DoughnutGraphComponent = ({ + id, + type, title, fontFamily, fontSize, fontWeight = "Regular", }: ChartComponentProps) => { - // Memoize Font Weight Mapping + const { themeColor } = useThemeStore(); + const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); + const [measurements, setmeasurements] = useState({}); + const [duration, setDuration] = useState("1h") + const [name, setName] = useState("Widget") + const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({ + labels: [], + datasets: [], + }); + const { selectedChartId } = useWidgetStore(); + + const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL; + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0] + const defaultData = { + labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], + datasets: [ + { + label: "Dataset", + data: [12, 19, 3, 5, 2, 3], + backgroundColor: ["#6f42c1"], + borderColor: "#b392f0", + borderWidth: 1, + }, + ], + }; + + useEffect(() => { + console.log("titleeeeeeeeeeeeeeeeeee",title); + },[]) + + // Memoize Theme Colors + const buttonActionColor = useMemo(() => themeColor[0] || "#5c87df", [themeColor]); + const buttonAbortColor = useMemo(() => themeColor[1] || "#ffffff", [themeColor]); + + // Memoize Font Styling const chartFontWeightMap = useMemo( () => ({ Light: "lighter" as const, @@ -26,19 +68,9 @@ const DoughnutGraphComponent = ({ [] ); - // Parse and Memoize Font Size - const fontSizeValue = useMemo( - () => (fontSize ? parseInt(fontSize) : 12), - [fontSize] - ); + const fontSizeValue = useMemo(() => (fontSize ? parseInt(fontSize) : 12), [fontSize]); + const fontWeightValue = useMemo(() => chartFontWeightMap[fontWeight], [fontWeight, chartFontWeightMap]); - // Determine and Memoize Font Weight - const fontWeightValue = useMemo( - () => chartFontWeightMap[fontWeight], - [fontWeight, chartFontWeightMap] - ); - - // Memoize Chart Font Style const chartFontStyle = useMemo( () => ({ family: fontFamily || "Arial", @@ -48,46 +80,110 @@ const DoughnutGraphComponent = ({ [fontFamily, fontSizeValue, fontWeightValue] ); - const options = useMemo( - () => ({ - responsive: true, - maintainAspectRatio: false, - plugins: { - title: { - display: true, - text: title, - font: chartFontStyle, - }, - legend: { - display: false, - }, - }, - scales: { - x: { - ticks: { - display: false, // This hides the x-axis labels + // Memoize Chart Options + const options = useMemo( + () => ({ + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: true, + text: name, + font: chartFontStyle, + }, + legend: { + display: false, }, }, - }, - }), - [title, chartFontStyle] - ); + scales: { + // x: { + // ticks: { + // display: true, // This hides the x-axis labels + // }, + // }, + }, + }), + [title, chartFontStyle, name] + ); - const chartData = { - labels: ["January", "February", "March", "April", "May", "June", "July"], - datasets: [ - { - label: "My First Dataset", - data: [65, 59, 80, 81, 56, 55, 40], - backgroundColor: "#6f42c1", // Updated to #6f42c1 (Purple) - borderColor: "#ffffff", // Keeping border color white - borderWidth: 2, - fill: false, - }, - ], - }; + // useEffect(() => {console.log(measurements); + // },[measurements]) - return ; + useEffect(() => { + if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return; + + const socket = io(`http://${iotApiUrl}`); + + const inputData = { + measurements, + duration, + interval: 1000, + }; + + + const startStream = () => { + socket.emit("lineInput", inputData); + }; + + socket.on("connect", startStream); + + socket.on("lineOutput", (response) => { + const responseData = response.data; + + // Extract timestamps and values + const labels = responseData.time; + const datasets = Object.keys(measurements).map((key) => { + const measurement = measurements[key]; + const datasetKey = `${measurement.name}.${measurement.fields}`; + return { + label: datasetKey, + data: responseData[datasetKey]?.values ?? [], + backgroundColor: "#6f42c1", + borderColor: "#b392f0", + borderWidth: 1, + }; + }); + + setChartData({ labels, datasets }); + }); + + return () => { + socket.off("lineOutput"); + socket.emit("stop_stream"); // Stop streaming when component unmounts + socket.disconnect(); + }; + }, [measurements, duration, iotApiUrl]); + + const fetchSavedInputes = async() => { + + if (id !== "") { + try { + const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`); + if (response.status === 200) { + setmeasurements(response.data.Data.measurements) + setDuration(response.data.Data.duration) + setName(response.data.widgetName) + } else { + console.log("Unexpected response:", response); + } + } catch (error) { + console.error("There was an error!", error); + } + } + } + + useEffect(() => { + fetchSavedInputes(); + }, []); + + useEffect(() => { + if (selectedChartId?.id === id) { + fetchSavedInputes(); + } + } + ,[chartMeasurements, chartDuration, widgetName]) + + return 0 ? chartData : defaultData} options={options} />; }; -export default DoughnutGraphComponent; +export default DoughnutGraphComponent; \ No newline at end of file diff --git a/app/src/components/ui/realTimeVis/charts/LineGraphComponent.tsx b/app/src/components/ui/realTimeVis/charts/LineGraphComponent.tsx index 34420fa..bf76add 100644 --- a/app/src/components/ui/realTimeVis/charts/LineGraphComponent.tsx +++ b/app/src/components/ui/realTimeVis/charts/LineGraphComponent.tsx @@ -24,9 +24,10 @@ const LineGraphComponent = ({ fontWeight = "Regular", }: ChartComponentProps) => { const { themeColor } = useThemeStore(); - const { measurements: chartMeasurements, duration: chartDuration } = useChartStore(); + const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); const [measurements, setmeasurements] = useState({}); const [duration, setDuration] = useState("1h") + const [name, setName] = useState("Widget") const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({ labels: [], datasets: [], @@ -49,6 +50,10 @@ const LineGraphComponent = ({ ], }; + useEffect(() => { + console.log("titleeeeeeeeeeeeeeeeeee",title); + },[]) + // Memoize Theme Colors const buttonActionColor = useMemo(() => themeColor[0] || "#5c87df", [themeColor]); const buttonAbortColor = useMemo(() => themeColor[1] || "#ffffff", [themeColor]); @@ -83,7 +88,7 @@ const LineGraphComponent = ({ plugins: { title: { display: true, - text: title, + text: name, font: chartFontStyle, }, legend: { @@ -98,7 +103,7 @@ const LineGraphComponent = ({ }, }, }), - [title, chartFontStyle] + [title, chartFontStyle, name] ); // useEffect(() => {console.log(measurements); @@ -157,6 +162,7 @@ const LineGraphComponent = ({ if (response.status === 200) { setmeasurements(response.data.Data.measurements) setDuration(response.data.Data.duration) + setName(response.data.widgetName) } else { console.log("Unexpected response:", response); } @@ -175,7 +181,7 @@ const LineGraphComponent = ({ fetchSavedInputes(); } } - ,[chartMeasurements, chartDuration]) + ,[chartMeasurements, chartDuration, widgetName]) return 0 ? chartData : defaultData} options={options} />; }; diff --git a/app/src/components/ui/realTimeVis/charts/PieGraphComponent.tsx b/app/src/components/ui/realTimeVis/charts/PieGraphComponent.tsx index 46516fa..094b9e7 100644 --- a/app/src/components/ui/realTimeVis/charts/PieGraphComponent.tsx +++ b/app/src/components/ui/realTimeVis/charts/PieGraphComponent.tsx @@ -210,9 +210,10 @@ const PieChartComponent = ({ fontWeight = "Regular", }: ChartComponentProps) => { const { themeColor } = useThemeStore(); - const { measurements: chartMeasurements, duration: chartDuration } = useChartStore(); + const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); const [measurements, setmeasurements] = useState({}); const [duration, setDuration] = useState("1h") + const [name, setName] = useState("Widget") const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({ labels: [], datasets: [], @@ -235,6 +236,10 @@ const PieChartComponent = ({ ], }; + useEffect(() => { + console.log("titleeeeeeeeeeeeeeeeeee",title); + },[]) + // Memoize Theme Colors const buttonActionColor = useMemo(() => themeColor[0] || "#5c87df", [themeColor]); const buttonAbortColor = useMemo(() => themeColor[1] || "#ffffff", [themeColor]); @@ -269,7 +274,7 @@ const PieChartComponent = ({ plugins: { title: { display: true, - text: title, + text: name, font: chartFontStyle, }, legend: { @@ -284,7 +289,7 @@ const PieChartComponent = ({ // }, }, }), - [title, chartFontStyle] + [title, chartFontStyle, name] ); // useEffect(() => {console.log(measurements); @@ -343,6 +348,7 @@ const PieChartComponent = ({ if (response.status === 200) { setmeasurements(response.data.Data.measurements) setDuration(response.data.Data.duration) + setName(response.data.widgetName) } else { console.log("Unexpected response:", response); } @@ -361,7 +367,7 @@ const PieChartComponent = ({ fetchSavedInputes(); } } - ,[chartMeasurements, chartDuration]) + ,[chartMeasurements, chartDuration, widgetName]) return 0 ? chartData : defaultData} options={options} />; }; diff --git a/app/src/components/ui/realTimeVis/charts/PolarAreaGraphComponent.tsx b/app/src/components/ui/realTimeVis/charts/PolarAreaGraphComponent.tsx index 563e1e9..92581c0 100644 --- a/app/src/components/ui/realTimeVis/charts/PolarAreaGraphComponent.tsx +++ b/app/src/components/ui/realTimeVis/charts/PolarAreaGraphComponent.tsx @@ -1,22 +1,64 @@ -import { useMemo } from "react"; -import { Line, PolarArea } from "react-chartjs-2"; +import React, { useEffect, useMemo, useState } from "react"; +import { PolarArea } from "react-chartjs-2"; +import io from "socket.io-client"; +import { useThemeStore } from "../../../../store/useThemeStore"; +import useChartStore from "../../../../store/useChartStore"; +import { useWidgetStore } from "../../../../store/useWidgetStore"; +import axios from "axios"; interface ChartComponentProps { + id: string; type: any; title: string; fontFamily?: string; fontSize?: string; fontWeight?: "Light" | "Regular" | "Bold"; - data: any; } const PolarAreaGraphComponent = ({ + id, + type, title, fontFamily, fontSize, fontWeight = "Regular", }: ChartComponentProps) => { - // Memoize Font Weight Mapping + const { themeColor } = useThemeStore(); + const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); + const [measurements, setmeasurements] = useState({}); + const [duration, setDuration] = useState("1h") + const [name, setName] = useState("Widget") + const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({ + labels: [], + datasets: [], + }); + const { selectedChartId } = useWidgetStore(); + + const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL; + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0] + const defaultData = { + labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], + datasets: [ + { + label: "Dataset", + data: [12, 19, 3, 5, 2, 3], + backgroundColor: ["#6f42c1"], + borderColor: "#b392f0", + borderWidth: 1, + }, + ], + }; + + useEffect(() => { + console.log("titleeeeeeeeeeeeeeeeeee",title); + },[]) + + // Memoize Theme Colors + const buttonActionColor = useMemo(() => themeColor[0] || "#5c87df", [themeColor]); + const buttonAbortColor = useMemo(() => themeColor[1] || "#ffffff", [themeColor]); + + // Memoize Font Styling const chartFontWeightMap = useMemo( () => ({ Light: "lighter" as const, @@ -26,19 +68,9 @@ const PolarAreaGraphComponent = ({ [] ); - // Parse and Memoize Font Size - const fontSizeValue = useMemo( - () => (fontSize ? parseInt(fontSize) : 12), - [fontSize] - ); + const fontSizeValue = useMemo(() => (fontSize ? parseInt(fontSize) : 12), [fontSize]); + const fontWeightValue = useMemo(() => chartFontWeightMap[fontWeight], [fontWeight, chartFontWeightMap]); - // Determine and Memoize Font Weight - const fontWeightValue = useMemo( - () => chartFontWeightMap[fontWeight], - [fontWeight, chartFontWeightMap] - ); - - // Memoize Chart Font Style const chartFontStyle = useMemo( () => ({ family: fontFamily || "Arial", @@ -48,46 +80,110 @@ const PolarAreaGraphComponent = ({ [fontFamily, fontSizeValue, fontWeightValue] ); - const options = useMemo( - () => ({ - responsive: true, - maintainAspectRatio: false, - plugins: { - title: { - display: true, - text: title, - font: chartFontStyle, - }, - legend: { - display: false, - }, - }, - scales: { - x: { - ticks: { - display: false, // This hides the x-axis labels + // Memoize Chart Options + const options = useMemo( + () => ({ + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: true, + text: name, + font: chartFontStyle, + }, + legend: { + display: false, }, }, - }, - }), - [title, chartFontStyle] - ); + scales: { + // x: { + // ticks: { + // display: true, // This hides the x-axis labels + // }, + // }, + }, + }), + [title, chartFontStyle, name] + ); - const chartData = { - labels: ["January", "February", "March", "April", "May", "June", "July"], - datasets: [ - { - label: "My First Dataset", - data: [65, 59, 80, 81, 56, 55, 40], - backgroundColor: "#6f42c1", // Updated to #6f42c1 (Purple) - borderColor: "#ffffff", // Keeping border color white - borderWidth: 2, - fill: false, - }, - ], - }; + // useEffect(() => {console.log(measurements); + // },[measurements]) - return ; + useEffect(() => { + if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return; + + const socket = io(`http://${iotApiUrl}`); + + const inputData = { + measurements, + duration, + interval: 1000, + }; + + + const startStream = () => { + socket.emit("lineInput", inputData); + }; + + socket.on("connect", startStream); + + socket.on("lineOutput", (response) => { + const responseData = response.data; + + // Extract timestamps and values + const labels = responseData.time; + const datasets = Object.keys(measurements).map((key) => { + const measurement = measurements[key]; + const datasetKey = `${measurement.name}.${measurement.fields}`; + return { + label: datasetKey, + data: responseData[datasetKey]?.values ?? [], + backgroundColor: "#6f42c1", + borderColor: "#b392f0", + borderWidth: 1, + }; + }); + + setChartData({ labels, datasets }); + }); + + return () => { + socket.off("lineOutput"); + socket.emit("stop_stream"); // Stop streaming when component unmounts + socket.disconnect(); + }; + }, [measurements, duration, iotApiUrl]); + + const fetchSavedInputes = async() => { + + if (id !== "") { + try { + const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`); + if (response.status === 200) { + setmeasurements(response.data.Data.measurements) + setDuration(response.data.Data.duration) + setName(response.data.widgetName) + } else { + console.log("Unexpected response:", response); + } + } catch (error) { + console.error("There was an error!", error); + } + } + } + + useEffect(() => { + fetchSavedInputes(); + }, []); + + useEffect(() => { + if (selectedChartId?.id === id) { + fetchSavedInputes(); + } + } + ,[chartMeasurements, chartDuration, widgetName]) + + return 0 ? chartData : defaultData} options={options} />; }; -export default PolarAreaGraphComponent; +export default PolarAreaGraphComponent; \ No newline at end of file diff --git a/app/src/components/ui/realTimeVis/floating/FleetEfficiencyComponent.tsx b/app/src/components/ui/realTimeVis/floating/FleetEfficiencyComponent.tsx new file mode 100644 index 0000000..9b2fd7f --- /dev/null +++ b/app/src/components/ui/realTimeVis/floating/FleetEfficiencyComponent.tsx @@ -0,0 +1,33 @@ +import React from 'react' + +type Props = {} + +const FleetEfficiencyComponent = ({ + object +}: any) => { + return ( + <> +

Fleet Efficiency

+
+
+
+
+
+
+
+
+ 0% +
+
{object.per}%
+
Optimal
+
+ 100% +
+ + ) +} + +export default FleetEfficiencyComponent \ No newline at end of file diff --git a/app/src/components/ui/realTimeVis/floating/TotalCardComponent.tsx b/app/src/components/ui/realTimeVis/floating/TotalCardComponent.tsx new file mode 100644 index 0000000..00ec479 --- /dev/null +++ b/app/src/components/ui/realTimeVis/floating/TotalCardComponent.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import { WalletIcon } from '../../../icons/3dChartIcons' +import { useWidgetStore } from '../../../../store/useWidgetStore'; + +const TotalCardComponent = ({ + object +}: any) => { + + const { setSelectedChartId } = + useWidgetStore(); + return ( + <> +
{ + setSelectedChartId(object.id) + }}> +
{object.header}
+
+
{object.value}
+
{object.per}
+
+
+
+ +
+ + ) +} + +export default TotalCardComponent \ No newline at end of file diff --git a/app/src/components/ui/realTimeVis/floating/WarehouseThroughputComponent.tsx b/app/src/components/ui/realTimeVis/floating/WarehouseThroughputComponent.tsx new file mode 100644 index 0000000..3aa8698 --- /dev/null +++ b/app/src/components/ui/realTimeVis/floating/WarehouseThroughputComponent.tsx @@ -0,0 +1,132 @@ +import React, { useState } from 'react' +import { Line } from 'react-chartjs-2' +import axios from 'axios'; + +const WarehouseThroughputComponent = ({ + object +}: any) => { + + const [measurements, setmeasurements] = useState({}); + const [duration, setDuration] = useState("1h") + + const lineGraphData = { + labels: [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ], // Months of the year + datasets: [ + { + label: "Throughput (units/month)", + data: [500, 400, 300, 450, 350, 250, 200, 300, 250, 150, 100, 150], // Example monthly data + borderColor: "#6f42c1", // Use the desired color for the line (purple) + backgroundColor: "rgba(111, 66, 193, 0.2)", // Use a semi-transparent purple for the fill + borderWidth: 2, // Line thickness + fill: true, // Enable fill for this dataset + pointRadius: 0, // Remove dots at each data point + tension: 0.5, // Smooth interpolation for the line + }, + ], + }; + + // Line graph options + const lineGraphOptions = { + responsive: true, + maintainAspectRatio: false, // Allow custom height/width adjustments + plugins: { + legend: { + display: false, // Hide legend + }, + title: { + display: false, // No chart title needed + }, + tooltip: { + callbacks: { + label: (context: any) => { + const value = context.parsed.y; + return `${value} units`; // Customize tooltip to display "units" + }, + }, + }, + }, + scales: { + x: { + grid: { + display: false, // Hide x-axis grid lines + }, + ticks: { + maxRotation: 0, // Prevent label rotation + autoSkip: false, // Display all months + font: { + size: 8, // Adjust font size for readability + color: "#ffffff", // Light text color for labels + }, + }, + }, + y: { + display: true, // Show y-axis + grid: { + drawBorder: false, // Remove border line + color: "rgba(255, 255, 255, 0.2)", // Light gray color for grid lines + borderDash: [5, 5], // Dotted line style (array defines dash and gap lengths) + }, + ticks: { + font: { + size: 8, // Adjust font size for readability + color: "#ffffff", // Light text color for ticks + }, + }, + }, + }, + elements: { + line: { + tension: 0.5, // Smooth interpolation for the line + }, + }, + }; + + + // const fetchSavedInputes = async() => { + + // if (object.id !== "") { + // try { + // const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_LOCAL_BASE_URL}/api/v2/WidgetData/${id}/${organization}`); + // if (response.status === 200) { + // setmeasurements(response.data.Data.measurements) + // setDuration(response.data.Data.duration) + // setName(response.data.widgetName) + // } else { + // console.log("Unexpected response:", response); + // } + // } catch (error) { + // console.error("There was an error!", error); + // } + // } + // } + + + return ( + <> +
+

Warehouse Throughput

+

+ (+5) more in 2025 +

+
+
+ +
+ + ) +} + +export default WarehouseThroughputComponent \ No newline at end of file diff --git a/app/src/modules/builder/geomentries/assets/addAssetModel.ts b/app/src/modules/builder/geomentries/assets/addAssetModel.ts index e0e50c6..65832f8 100644 --- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts +++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts @@ -9,6 +9,8 @@ import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils'; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import { Socket } from 'socket.io-client'; import * as CONSTANTS from '../../../../types/world/worldConstants'; +import { getAssetEventType } from '../../../../services/simulation/getAssetEventType'; +import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; async function addAssetModel( raycaster: THREE.Raycaster, @@ -58,6 +60,7 @@ async function addAssetModel( if (intersectPoint.y < 0) { intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z); } + console.log('selectedItem: ', selectedItem); const cachedModel = THREE.Cache.get(selectedItem.id); if (cachedModel) { // console.log(`[Cache] Fetching ${selectedItem.name}`); @@ -136,51 +139,88 @@ async function handleModelLoad( modelname: selectedItem.name, modelfileID: selectedItem.id, position: [intersectPoint!.x, intersectPoint!.y, intersectPoint!.z], - rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, }, + rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, isLocked: false, isVisible: true }; - setFloorItems((prevItems) => { - const updatedItems = [...(prevItems || []), newFloorItem]; - localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); - return updatedItems; - }); - const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; - //REST + getAssetEventType(selectedItem.id, organization).then(async (res) => { + console.log('res: ', res); - // await setFloorItemApi( - // organization, - // newFloorItem.modeluuid, - // newFloorItem.modelname, - // newFloorItem.position, - // { "x": model.rotation.x, "y": model.rotation.y, "z": model.rotation.z }, - // newFloorItem.modelfileID!, - // false, - // true, - // ); + if (res.type === "Conveyor") { + const pointUUIDs = res.points.map(() => THREE.MathUtils.generateUUID()); - //SOCKET + const eventData: Extract = { + type: 'Conveyor', + points: res.points.map((point: any, index: number) => ({ + uuid: pointUUIDs[index], + position: point.position as [number, number, number], + rotation: point.rotation as [number, number, number], + actions: [{ + uuid: THREE.MathUtils.generateUUID(), + name: 'Action 1', + type: 'Inherit', + material: 'Inherit', + delay: 'Inherit', + spawnInterval: 'Inherit', + isUsed: false + }], + triggers: [], + connections: { + source: { pathUUID: model.uuid, pointUUID: pointUUIDs[index] }, + targets: [] + } + })), + speed: 'Inherit' + }; - const data = { - organization, - modeluuid: newFloorItem.modeluuid, - modelname: newFloorItem.modelname, - modelfileID: newFloorItem.modelfileID, - position: newFloorItem.position, - rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, - isLocked: false, - isVisible: true, - socketId: socket.id, - }; + console.log('eventData: ', eventData); + newFloorItem.eventData = eventData; + } - socket.emit("v1:FloorItems:set", data); + setFloorItems((prevItems) => { + const updatedItems = [...(prevItems || []), newFloorItem]; + localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); + return updatedItems; + }); - gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" }); - gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } }); + // API + + // await setFloorItemApi( + // organization, + // newFloorItem.modeluuid, + // newFloorItem.modelname, + // newFloorItem.modelfileID, + // newFloorItem.position, + // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + // false, + // true, + // newFloorItem.eventData + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + isLocked: false, + isVisible: true, + eventData: newFloorItem.eventData, + socketId: socket.id + }; + + socket.emit("v2:model-asset:add", data); + + gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" }); + gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } }); + }); } export default addAssetModel; diff --git a/app/src/modules/builder/geomentries/lines/distanceText.tsx b/app/src/modules/builder/geomentries/lines/distanceText.tsx index 0519c84..98e20a6 100644 --- a/app/src/modules/builder/geomentries/lines/distanceText.tsx +++ b/app/src/modules/builder/geomentries/lines/distanceText.tsx @@ -1,90 +1,143 @@ -import { useEffect, useState } from "react" +import { useEffect, useState } from "react"; import { getLines } from "../../../../services/factoryBuilder/lines/getLinesApi"; import * as THREE from "three"; -import { useActiveLayer, useDeletedLines, useNewLines, useToggleView } from "../../../../store/store"; +import { + useActiveLayer, + useDeletedLines, + useNewLines, + useToggleView, +} from "../../../../store/store"; import objectLinesToArray from "./lineConvertions/objectLinesToArray"; import { Html } from "@react-three/drei"; import * as Types from "../../../../types/world/worldTypes"; const DistanceText = () => { - const [lines, setLines] = useState<{ distance: string; position: THREE.Vector3; userData: Types.Line; layer: string }[]>([]); - const { activeLayer } = useActiveLayer(); - const { toggleView } = useToggleView(); - const { newLines, setNewLines } = useNewLines(); - const { deletedLines, setDeletedLines } = useDeletedLines(); + const [lines, setLines] = useState< + { + distance: string; + position: THREE.Vector3; + userData: Types.Line; + layer: string; + }[] + >([]); + const { activeLayer } = useActiveLayer(); + const { toggleView } = useToggleView(); + const { newLines, setNewLines } = useNewLines(); + const { deletedLines, setDeletedLines } = useDeletedLines(); - useEffect(() => { - const email = localStorage.getItem('email') - if (!email) return; - const organization = (email.split("@")[1]).split(".")[0]; + useEffect(() => { + const email = localStorage.getItem("email"); + if (!email) return; + const organization = email.split("@")[1].split(".")[0]; - getLines(organization).then((data) => { - data = objectLinesToArray(data); + getLines(organization).then((data) => { + data = objectLinesToArray(data); - const lines = data.filter((line: Types.Line) => line[0][2] === activeLayer) - .map((line: Types.Line) => { - const point1 = new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z); - const point2 = new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z); - const distance = point1.distanceTo(point2); - const midpoint = new THREE.Vector3().addVectors(point1, point2).divideScalar(2); - return { - distance: distance.toFixed(1), - position: midpoint, - userData: line, - layer: activeLayer, - }; - }); - setLines(lines) - }) - }, [activeLayer]) + const lines = data + .filter((line: Types.Line) => line[0][2] === activeLayer) + .map((line: Types.Line) => { + const point1 = new THREE.Vector3( + line[0][0].x, + line[0][0].y, + line[0][0].z + ); + const point2 = new THREE.Vector3( + line[1][0].x, + line[1][0].y, + line[1][0].z + ); + const distance = point1.distanceTo(point2); + const midpoint = new THREE.Vector3() + .addVectors(point1, point2) + .divideScalar(2); + return { + distance: distance.toFixed(1), + position: midpoint, + userData: line, + layer: activeLayer, + }; + }); + setLines(lines); + }); + }, [activeLayer]); - useEffect(() => { - if (newLines.length > 0) { - if (newLines[0][0][2] !== activeLayer) return; - const newLinesData = newLines.map((line: Types.Line) => { - const point1 = new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z); - const point2 = new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z); - const distance = point1.distanceTo(point2); - const midpoint = new THREE.Vector3().addVectors(point1, point2).divideScalar(2); + useEffect(() => { + if (newLines.length > 0) { + if (newLines[0][0][2] !== activeLayer) return; + const newLinesData = newLines.map((line: Types.Line) => { + const point1 = new THREE.Vector3( + line[0][0].x, + line[0][0].y, + line[0][0].z + ); + const point2 = new THREE.Vector3( + line[1][0].x, + line[1][0].y, + line[1][0].z + ); + const distance = point1.distanceTo(point2); + const midpoint = new THREE.Vector3() + .addVectors(point1, point2) + .divideScalar(2); - return { - distance: distance.toFixed(1), - position: midpoint, - userData: line, - layer: activeLayer, - }; - }); - setLines((prevLines) => [...prevLines, ...newLinesData]); - setNewLines([]); - } - }, [newLines, activeLayer]); + return { + distance: distance.toFixed(1), + position: midpoint, + userData: line, + layer: activeLayer, + }; + }); + setLines((prevLines) => [...prevLines, ...newLinesData]); + setNewLines([]); + } + }, [newLines, activeLayer]); + useEffect(() => { + if ((deletedLines as Types.Lines).length > 0) { + setLines((prevLines) => + prevLines.filter( + (line) => + !deletedLines.some( + (deletedLine: any) => + deletedLine[0][1] === line.userData[0][1] && + deletedLine[1][1] === line.userData[1][1] + ) + ) + ); + setDeletedLines([]); + } + }, [deletedLines]); - useEffect(() => { - if ((deletedLines as Types.Lines).length > 0) { - setLines((prevLines) => - prevLines.filter( - (line) => !deletedLines.some((deletedLine: any) => deletedLine[0][1] === line.userData[0][1] && deletedLine[1][1] === line.userData[1][1]) - ) - ); - setDeletedLines([]); - } - }, [deletedLines]); + return ( + <> + {toggleView && ( + + {lines.map((text) => ( + +
+ {text.distance} m +
+ + ))} +
+ )} + + ); +}; - return ( - <> - {toggleView && ( - - {lines.map((text) => ( - -
{text.distance} m
- - ))} -
- )} - - ) - -} - -export default DistanceText; \ No newline at end of file +export default DistanceText; diff --git a/app/src/modules/builder/groups/floorItemsGroup.tsx b/app/src/modules/builder/groups/floorItemsGroup.tsx index cf9d6b8..3b38d01 100644 --- a/app/src/modules/builder/groups/floorItemsGroup.tsx +++ b/app/src/modules/builder/groups/floorItemsGroup.tsx @@ -60,6 +60,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject }; getFloorItems(organization).then((data) => { + console.log('data: ', data); const uniqueItems = (data as Types.FloorItems).filter((item, index, self) => index === self.findIndex((t) => t.modelfileID === item.modelfileID) ); @@ -305,7 +306,10 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject }; }, [deleteModels, transformMode, controls, selectedItem, state.camera, state.pointer, activeTool, activeModule]); + useEffect(() => { + console.log('floorItems: ', floorItems); + }, [floorItems]) useFrame(() => { if (controls) diff --git a/app/src/modules/builder/groups/zoneGroup.tsx b/app/src/modules/builder/groups/zoneGroup.tsx index 3d47a7e..c5e54d3 100644 --- a/app/src/modules/builder/groups/zoneGroup.tsx +++ b/app/src/modules/builder/groups/zoneGroup.tsx @@ -434,9 +434,9 @@ const ZoneGroup: React.FC = () => { const point2 = new THREE.Vector3(nextPoint[0], nextPoint[1], nextPoint[2]); const planeWidth = point1.distanceTo(point2); - const planeHeight = CONSTANTS.wallConfig.height; + const planeHeight = CONSTANTS.zoneConfig.height; - const midpoint = new THREE.Vector3((point1.x + point2.x) / 2, (CONSTANTS.wallConfig.height / 2) + ((zone.layer - 1) * CONSTANTS.wallConfig.height), (point1.z + point2.z) / 2); + const midpoint = new THREE.Vector3((point1.x + point2.x) / 2, (CONSTANTS.zoneConfig.height / 2) + ((zone.layer - 1) * CONSTANTS.zoneConfig.height), (point1.z + point2.z) / 2); const angle = Math.atan2(point2.z - point1.z, point2.x - point1.x); diff --git a/app/src/modules/collaboration/socketResponses.dev.tsx b/app/src/modules/collaboration/socketResponses.dev.tsx index 031629f..1a5ef83 100644 --- a/app/src/modules/collaboration/socketResponses.dev.tsx +++ b/app/src/modules/collaboration/socketResponses.dev.tsx @@ -81,7 +81,8 @@ export default function SocketResponses({ // console.log('data: ', data); }) - socket.on('FloorItemsUpdateResponse', async (data: any) => { + socket.on('model-asset:response:updates', async (data: any) => { + console.log('data: ', data); if (socket.id === data.socketId) { return } diff --git a/app/src/modules/collaboration/users/Avatar.tsx b/app/src/modules/collaboration/users/Avatar.tsx index 93d45c3..f08a545 100644 --- a/app/src/modules/collaboration/users/Avatar.tsx +++ b/app/src/modules/collaboration/users/Avatar.tsx @@ -1,59 +1,59 @@ -import React, { useEffect, useState } from "react"; -import { getInitials } from "./functions/getInitials"; -import { getAvatarColor } from "./functions/getAvatarColor"; - -interface AvatarProps { - name: string; // Name can be a full name or initials - size?: number; - index?: number; - textColor?: string; -} - -const CustomAvatar: React.FC = ({ - name, - size = 100, - index = 0, - textColor = "#ffffff", -}) => { - const [imageSrc, setImageSrc] = useState(null); - - useEffect(() => { - const canvas = document.createElement("canvas"); // Create an offscreen canvas - canvas.width = size; - canvas.height = size; - const ctx = canvas.getContext("2d"); - if (ctx) { - const initials = getInitials(name); // Convert name to initials if needed - - // Draw background - ctx.fillStyle = getAvatarColor(index); - ctx.fillRect(0, 0, size, size); - - // Draw initials - ctx.fillStyle = textColor; - ctx.font = `bold ${size / 2}px Arial`; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillText(initials, size / 2, size / 2); - - // Generate image source - const dataURL = canvas.toDataURL("image/png"); - setImageSrc(dataURL); - } - }, [name, size, textColor]); - - if (!imageSrc) { - return null; // Return null while the image is being generated - } - - return ( - User Avatar - ); -}; - -export default CustomAvatar; +import React, { useEffect, useState } from "react"; +import { getInitials } from "./functions/getInitials"; +import { getAvatarColor } from "./functions/getAvatarColor"; + +interface AvatarProps { + name: string; // Name can be a full name or initials + size?: number; + index?: number; + textColor?: string; +} + +const CustomAvatar: React.FC = ({ + name, + size = 100, + index = 0, + textColor = "#ffffff", +}) => { + const [imageSrc, setImageSrc] = useState(null); + + useEffect(() => { + const canvas = document.createElement("canvas"); // Create an offscreen canvas + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext("2d"); + if (ctx) { + const initials = getInitials(name); // Convert name to initials if needed + + // Draw background + ctx.fillStyle = getAvatarColor(index); + ctx.fillRect(0, 0, size, size); + + // Draw initials + ctx.fillStyle = textColor; + ctx.font = `bold ${size / 2}px Arial`; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(initials, size / 2, size / 2); + + // Generate image source + const dataURL = canvas.toDataURL("image/png"); + setImageSrc(dataURL); + } + }, [name, size, textColor]); + + if (!imageSrc) { + return null; // Return null while the image is being generated + } + + return ( + User Avatar + ); +}; + +export default CustomAvatar; diff --git a/app/src/modules/collaboration/users/functions/getAvatarColor.ts b/app/src/modules/collaboration/users/functions/getAvatarColor.ts index d3186d2..3deacca 100644 --- a/app/src/modules/collaboration/users/functions/getAvatarColor.ts +++ b/app/src/modules/collaboration/users/functions/getAvatarColor.ts @@ -1,26 +1,26 @@ -const avatarColors: string[] = [ - "#FF5733", // Red Orange - "#48ac2a", // Leaf Green - "#0050eb", // Royal Blue - "#FF33A1", // Hot Pink - "#FF8C33", // Deep Orange - "#8C33FF", // Violet - "#FF3333", // Bright Red - "#43c06d", // Emerald Green - "#A133FF", // Amethyst Purple - "#C70039", // Crimson - "#900C3F", // Maroon - "#581845", // Plum - "#3498DB", // Sky Blue - "#2ECC71", // Green Mint - "#E74C3C", // Tomato Red - "#00adff", // Azure - "#DBAD05", // Amber Yellow - "#FF5733", // Red Orange - "#FF33A1", // Hot Pink - "#900C3F", // Maroon -]; - -export function getAvatarColor(index: number): string { - return avatarColors[index % avatarColors.length]; -} +const avatarColors: string[] = [ + "#FF5733", // Red Orange + "#48ac2a", // Leaf Green + "#0050eb", // Royal Blue + "#FF33A1", // Hot Pink + "#FF8C33", // Deep Orange + "#8C33FF", // Violet + "#FF3333", // Bright Red + "#43c06d", // Emerald Green + "#A133FF", // Amethyst Purple + "#C70039", // Crimson + "#900C3F", // Maroon + "#581845", // Plum + "#3498DB", // Sky Blue + "#2ECC71", // Green Mint + "#E74C3C", // Tomato Red + "#00adff", // Azure + "#DBAD05", // Amber Yellow + "#FF5733", // Red Orange + "#FF33A1", // Hot Pink + "#900C3F", // Maroon +]; + +export function getAvatarColor(index: number): string { + return avatarColors[index % avatarColors.length]; +} diff --git a/app/src/modules/collaboration/users/functions/getInitials.ts b/app/src/modules/collaboration/users/functions/getInitials.ts index 5ebaa19..572d49a 100644 --- a/app/src/modules/collaboration/users/functions/getInitials.ts +++ b/app/src/modules/collaboration/users/functions/getInitials.ts @@ -1,10 +1,10 @@ -export const getInitials = (fullName: string): string => { - // Extract initials from the name - const words = fullName.split(" "); - const initials = words - .map((word) => word[0]) - .slice(0, 2) - .join("") - .toUpperCase(); - return initials; +export const getInitials = (fullName: string): string => { + // Extract initials from the name + const words = fullName.split(" "); + const initials = words + .map((word) => word[0]) + .slice(0, 2) + .join("") + .toUpperCase(); + return initials; }; \ No newline at end of file diff --git a/app/src/modules/scene/controls/selection/selectionControls.tsx b/app/src/modules/scene/controls/selection/selectionControls.tsx index c9038ab..9d5f7ea 100644 --- a/app/src/modules/scene/controls/selection/selectionControls.tsx +++ b/app/src/modules/scene/controls/selection/selectionControls.tsx @@ -42,6 +42,8 @@ const SelectionControls: React.FC = () => { itemsGroupRef.current = itemsGroup; let isSelecting = false; + let isRightClick = false; + let rightClickMoved = false; let isCtrlSelecting = false; const helper = new SelectionHelper(gl); @@ -52,16 +54,23 @@ const SelectionControls: React.FC = () => { } const onPointerDown = (event: PointerEvent) => { - if (event.button !== 0) return - isSelecting = false; - isCtrlSelecting = event.ctrlKey; - if (event.ctrlKey && duplicatedObjects.length === 0) { - if (controls) (controls as any).enabled = false; - selectionBox.startPoint.set(pointer.x, pointer.y, 0); + if (event.button === 2) { + isRightClick = true; + rightClickMoved = false; + } else if (event.button === 0) { + isSelecting = false; + isCtrlSelecting = event.ctrlKey; + if (event.ctrlKey && duplicatedObjects.length === 0) { + if (controls) (controls as any).enabled = false; + selectionBox.startPoint.set(pointer.x, pointer.y, 0); + } } }; const onPointerMove = (event: PointerEvent) => { + if (isRightClick) { + rightClickMoved = true; + } isSelecting = true; if (helper.isDown && event.ctrlKey && duplicatedObjects.length === 0 && isCtrlSelecting) { selectionBox.endPoint.set(pointer.x, pointer.y, 0); @@ -69,6 +78,14 @@ const SelectionControls: React.FC = () => { }; const onPointerUp = (event: PointerEvent) => { + if (event.button === 2) { + isRightClick = false; + if (!rightClickMoved) { + clearSelection(); + } + return; + } + if (isSelecting && isCtrlSelecting) { isCtrlSelecting = false; isSelecting = false; @@ -94,10 +111,13 @@ const SelectionControls: React.FC = () => { } }; + const onContextMenu = (event: MouseEvent) => { event.preventDefault(); - clearSelection(); - } + if (!rightClickMoved) { + clearSelection(); + } + }; if (!toggleView && activeModule === "builder") { helper.enabled = true; diff --git a/app/src/modules/scene/environment/ground.tsx b/app/src/modules/scene/environment/ground.tsx index 6a54577..ebc017e 100644 --- a/app/src/modules/scene/environment/ground.tsx +++ b/app/src/modules/scene/environment/ground.tsx @@ -1,9 +1,9 @@ -import * as THREE from 'three'; import { useToggleView } from '../../../store/store'; import * as CONSTANTS from '../../../types/world/worldConstants'; const Ground = ({ grid, plane }: any) => { - const { toggleView, setToggleView } = useToggleView(); + const { toggleView } = useToggleView(); + const savedTheme: string | null = localStorage.getItem('theme'); return ( @@ -19,4 +19,4 @@ const Ground = ({ grid, plane }: any) => { ) } -export default Ground; \ No newline at end of file +export default Ground; diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index 74693b4..0a01a99 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -1,11 +1,11 @@ -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { Canvas } from "@react-three/fiber"; import { Environment, KeyboardControls } from "@react-three/drei"; import World from "./world/world"; import Controls from "./controls/controls"; import TransformControl from "./controls/transformControls"; -import PostProcessing from "./postProcessing/postProcessing" +import PostProcessing from "./postProcessing/postProcessing"; import Sun from "./environment/sky"; import CamModelsGroup from "../collaboration/collabCams"; import Shadows from "./environment/shadow"; @@ -15,33 +15,31 @@ import background from "../../assets/textures/hdr/mudroadpuresky2k.hdr"; import SelectionControls from "./controls/selection/selectionControls"; import MeasurementTool from "./tools/measurementTool"; import Simulation from "../simulation/simulation"; -import DroppedObjects from "../../components/ui/componets/DroppedFloatingWidgets"; // import Simulation from "./simulationtemp/simulation"; import ZoneCentreTarget from "../../components/ui/componets/zoneCameraTarget"; -import ProductionCapacity from "../../components/layout/3D-cards/cards/ProductionCapacity"; import Dropped3dWidgets from "../../components/ui/componets/Dropped3dWidget"; -import { useWidgetSubOption } from "../../store/store"; export default function Scene() { - - const map = useMemo(() => [ - { name: "forward", keys: ["ArrowUp", "w", "W"] }, - { name: "backward", keys: ["ArrowDown", "s", "S"] }, - { name: "left", keys: ["ArrowLeft", "a", "A"] }, - { name: "right", keys: ["ArrowRight", "d", "D"] }, - ], []) + const map = useMemo( + () => [ + { name: "forward", keys: ["ArrowUp", "w", "W"] }, + { name: "backward", keys: ["ArrowDown", "s", "S"] }, + { name: "left", keys: ["ArrowLeft", "a", "A"] }, + { name: "right", keys: ["ArrowRight", "d", "D"] }, + ], + [] + ); + const savedTheme: string | null = localStorage.getItem("theme"); return ( { e.preventDefault(); }} - > @@ -52,7 +50,7 @@ export default function Scene() { - + {savedTheme !== "dark" && } diff --git a/app/src/modules/scene/world/world.tsx b/app/src/modules/scene/world/world.tsx index 5864ec8..fedc109 100644 --- a/app/src/modules/scene/world/world.tsx +++ b/app/src/modules/scene/world/world.tsx @@ -364,7 +364,7 @@ export default function World() { anglesnappedPoint={anglesnappedPoint} /> - + {/* */} diff --git a/app/src/modules/simulation/behaviour/behaviour.tsx b/app/src/modules/simulation/behaviour/behaviour.tsx index c1281b5..5075384 100644 --- a/app/src/modules/simulation/behaviour/behaviour.tsx +++ b/app/src/modules/simulation/behaviour/behaviour.tsx @@ -67,7 +67,7 @@ function Behaviour() { point: { uuid: pointUUID, position: [pointPosition.x, pointPosition.y, pointPosition.z], - actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: '', hitCount: 1, end: '', buffer: 0 }, + actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: {}, hitCount: 1, end: {}, buffer: 0 }, connections: { source: { pathUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, speed: 2, }, diff --git a/app/src/modules/simulation/path/pathConnector.tsx b/app/src/modules/simulation/path/pathConnector.tsx index 0888966..7352932 100644 --- a/app/src/modules/simulation/path/pathConnector.tsx +++ b/app/src/modules/simulation/path/pathConnector.tsx @@ -96,24 +96,16 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }; const existingTargets = path.point.connections.targets || []; - // Check if we're trying to add a connection to a Conveyor + // Check if target is a Conveyor const toPath = simulationPaths.find(p => p.modeluuid === toPathUUID); - const isConnectingToConveyor = toPath?.type === 'Conveyor'; - - // Count existing connections - if (existingTargets.length >= 2) { - console.log("Vehicle can have maximum 2 connections"); + if (toPath?.type !== 'Conveyor') { + console.log("Vehicle can only connect to Conveyors"); return path; } - // Check if we already have a Conveyor connection and trying to add another - const hasConveyorConnection = existingTargets.some(target => { - const targetPath = simulationPaths.find(p => p.modeluuid === target.pathUUID); - return targetPath?.type === 'Conveyor'; - }); - - if (hasConveyorConnection && isConnectingToConveyor) { - console.log("Vehicle can only have one connection to a Conveyor"); + // Check if already has a connection + if (existingTargets.length >= 1) { + console.log("Vehicle can have only one connection"); return path; } @@ -141,24 +133,16 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }; const existingTargets = path.point.connections.targets || []; - // Check if we're receiving a connection from a Conveyor + // Check if source is a Conveyor const fromPath = simulationPaths.find(p => p.modeluuid === fromPathUUID); - const isConnectingFromConveyor = fromPath?.type === 'Conveyor'; - - // Count existing connections - if (existingTargets.length >= 2) { - console.log("Vehicle can have maximum 2 connections"); + if (fromPath?.type !== 'Conveyor') { + console.log("Vehicle can only connect to Conveyors"); return path; } - // Check if we already have a Conveyor connection and trying to add another - const hasConveyorConnection = existingTargets.some(target => { - const targetPath = simulationPaths.find(p => p.modeluuid === target.pathUUID); - return targetPath?.type === 'Conveyor'; - }); - - if (hasConveyorConnection && isConnectingFromConveyor) { - console.log("Vehicle can only have one connection to a Conveyor"); + // Check if already has a connection + if (existingTargets.length >= 1) { + console.log("Vehicle can have only one connection"); return path; } @@ -212,6 +196,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec drag = true; } }; + const onContextMenu = (evt: MouseEvent) => { evt.preventDefault(); if (drag || evt.button === 0) return; @@ -282,7 +267,16 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec return; } - // For Vehicles, skip the "already connected" check since they can have multiple connections + // For Vehicles, check if they're already connected to anything + if (intersected.userData.path.type === 'Vehicle') { + const vehicleConnections = intersected.userData.path.point.connections.targets.length; + if (vehicleConnections >= 1) { + console.log("Vehicle can only have one connection"); + return; + } + } + + // For non-Vehicle paths, check if already connected if (intersected.userData.path.type !== 'Vehicle') { const isAlreadyConnected = simulationPaths.some(path => { if (path.type === 'Conveyor') { @@ -300,48 +294,14 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } } - // Check vehicle connection limits - const checkVehicleConnections = (pathUUID: string) => { - const path = simulationPaths.find(p => p.modeluuid === pathUUID); - if (path?.type === 'Vehicle') { - return path.point.connections.targets.length >= 2; - } - return false; - }; - if (firstSelected) { - // Check if either selected point is from a Vehicle with max connections - if (checkVehicleConnections(firstSelected.pathUUID) || - checkVehicleConnections(pathUUID)) { - console.log("Vehicle already has maximum connections"); + // Check if trying to connect Vehicle to non-Conveyor + if ((firstPath?.type === 'Vehicle' && secondPath?.type !== 'Conveyor') || + (secondPath?.type === 'Vehicle' && firstPath?.type !== 'Conveyor')) { + console.log("Vehicle can only connect to Conveyors"); return; } - // Check if we're trying to add a second Conveyor connection to a Vehicle - if (firstPath?.type === 'Vehicle' && secondPath?.type === 'Conveyor') { - const hasConveyorConnection = firstPath.point.connections.targets.some(target => { - const targetPath = simulationPaths.find(p => p.modeluuid === target.pathUUID); - return targetPath?.type === 'Conveyor'; - }); - - if (hasConveyorConnection) { - console.log("Vehicle can only have one connection to a Conveyor"); - return; - } - } - - if (secondPath?.type === 'Vehicle' && firstPath?.type === 'Conveyor') { - const hasConveyorConnection = secondPath.point.connections.targets.some(target => { - const targetPath = simulationPaths.find(p => p.modeluuid === target.pathUUID); - return targetPath?.type === 'Conveyor'; - }); - - if (hasConveyorConnection) { - console.log("Vehicle can only have one connection to a Conveyor"); - return; - } - } - // Prevent same-path connections if (firstSelected.pathUUID === pathUUID) { console.log("Cannot connect spheres on the same path."); @@ -478,28 +438,19 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec return false; }); - // Check vehicle connection limits + // Check vehicle connection rules const isVehicleAtMaxConnections = pathData.type === 'Vehicle' && - pathData.point.connections.targets.length >= 2; - - const isVehicleConveyorConflict = - (firstPath?.type === 'Vehicle' && secondPath?.type === 'Conveyor' && - firstPath.point.connections.targets.some(t => { - const targetPath = simulationPaths.find(p => p.modeluuid === t.pathUUID); - return targetPath?.type === 'Conveyor'; - })) || - (secondPath?.type === 'Vehicle' && firstPath?.type === 'Conveyor' && - secondPath.point.connections.targets.some(t => { - const targetPath = simulationPaths.find(p => p.modeluuid === t.pathUUID); - return targetPath?.type === 'Conveyor'; - })); + pathData.point.connections.targets.length >= 1; + const isVehicleConnectingToNonConveyor = + (firstPath?.type === 'Vehicle' && secondPath?.type !== 'Conveyor') || + (secondPath?.type === 'Vehicle' && firstPath?.type !== 'Conveyor'); if ( !isDuplicateConnection && !isVehicleToVehicle && !isNonVehicleAlreadyConnected && !isVehicleAtMaxConnections && - !isVehicleConveyorConflict && + !isVehicleConnectingToNonConveyor && firstSelected.sphereUUID !== sphereUUID && firstSelected.pathUUID !== pathUUID && (firstSelected.isCorner || isConnectable) diff --git a/app/src/modules/simulation/path/pathCreation.tsx b/app/src/modules/simulation/path/pathCreation.tsx index 33ca2f8..202c7e7 100644 --- a/app/src/modules/simulation/path/pathCreation.tsx +++ b/app/src/modules/simulation/path/pathCreation.tsx @@ -1,8 +1,8 @@ import * as THREE from 'three'; import * as Types from '../../../types/world/worldTypes'; -import { useRef, useState, useEffect } from 'react'; +import { useRef, useState, useEffect, useMemo } from 'react'; import { Sphere, TransformControls } from '@react-three/drei'; -import { useIsConnecting, useRenderDistance, useSelectedActionSphere, useSelectedPath, useSimulationPaths } from '../../../store/store'; +import { useEditingPoint, useEyeDropMode, useIsConnecting, usePreviewPosition, useRenderDistance, useSelectedActionSphere, useSelectedPath, useSimulationPaths } from '../../../store/store'; import { useFrame, useThree } from '@react-three/fiber'; import { useSubModuleStore } from '../../../store/useModuleStore'; @@ -10,13 +10,18 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject const { renderDistance } = useRenderDistance(); const { setSubModule } = useSubModuleStore(); const { setSelectedActionSphere, selectedActionSphere } = useSelectedActionSphere(); + const { eyeDropMode, setEyeDropMode } = useEyeDropMode(); + const { editingPoint, setEditingPoint } = useEditingPoint(); + const { previewPosition, setPreviewPosition } = usePreviewPosition(); + const { raycaster, camera, pointer, gl } = useThree(); + const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { setSelectedPath } = useSelectedPath(); const { simulationPaths, setSimulationPaths } = useSimulationPaths(); const { isConnecting } = useIsConnecting(); - const { camera } = useThree(); - + const groupRefs = useRef<{ [key: string]: THREE.Group }>({}); const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); + const isMovingRef = useRef(false); const transformRef = useRef(null); const [transformMode, setTransformMode] = useState<'translate' | 'rotate' | null>(null); @@ -77,6 +82,83 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject setSimulationPaths(updatedPaths); }; + useFrame(() => { + if (eyeDropMode) { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (point) { + setPreviewPosition({ x: point.x, y: point.z }); + } + } else { + setPreviewPosition(null); + } + }); + + useEffect(() => { + if (!camera) return; + const canvasElement = gl.domElement; + canvasElement.tabIndex = 0; + + + const onPointerDown = () => { + isMovingRef.current = false; + }; + + const onPointerMove = () => { + isMovingRef.current = true; + }; + + const onPointerUp = (event: PointerEvent) => { + if (!isMovingRef.current && eyeDropMode && event.button === 0 && previewPosition) { + event.preventDefault(); + if (editingPoint) { + handlePointUpdate(editingPoint, previewPosition.x, previewPosition.y); + setEditingPoint(null); + setEyeDropMode(false); + } + } + }; + + if (eyeDropMode) { + canvasElement.addEventListener("pointerdown", onPointerDown); + canvasElement.addEventListener("pointermove", onPointerMove); + canvasElement.addEventListener("pointerup", onPointerUp); + } + + return () => { + canvasElement.removeEventListener("pointerdown", onPointerDown); + canvasElement.removeEventListener("pointermove", onPointerMove); + canvasElement.removeEventListener("pointerup", onPointerUp); + }; + }, [eyeDropMode, editingPoint, previewPosition]); + + const handlePointUpdate = (pointType: 'start' | 'end', x: number, z: number) => { + if (!selectedActionSphere?.point?.uuid) return; + + const updatedPaths = simulationPaths.map((path) => { + if (path.type === "Vehicle" && path.point.uuid === selectedActionSphere.point.uuid) { + return { + ...path, + point: { + ...path.point, + actions: { + ...path.point.actions, + [pointType]: { + ...path.point.actions[pointType], + x: x, + y: z + } + } + } + }; + } + return path; + }); + + setSimulationPaths(updatedPaths); + }; return ( @@ -92,7 +174,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject position={path.assetPosition} rotation={path.assetRotation} onClick={(e) => { - if (isConnecting) return; + if (isConnecting || eyeDropMode) return; e.stopPropagation(); setSelectedPath({ path, group: groupRefs.current[path.modeluuid] }); setSelectedActionSphere(null); @@ -100,6 +182,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject setSubModule('mechanics'); }} onPointerMissed={() => { + if (eyeDropMode) return; setSelectedPath(null); setSubModule('properties'); }} @@ -113,7 +196,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject name='events-sphere' ref={el => (sphereRefs.current[point.uuid] = el!)} onClick={(e) => { - if (isConnecting) return; + if (isConnecting || eyeDropMode) return; e.stopPropagation(); setSelectedActionSphere({ path, @@ -124,6 +207,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject }} userData={{ point, path }} onPointerMissed={() => { + if (eyeDropMode) return; setSubModule('properties'); setSelectedActionSphere(null); }} @@ -155,7 +239,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject ref={el => (groupRefs.current[path.modeluuid] = el!)} position={path.assetPosition} onClick={(e) => { - if (isConnecting) return; + if (isConnecting || eyeDropMode) return; e.stopPropagation(); setSelectedPath({ path, group: groupRefs.current[path.modeluuid] }); setSelectedActionSphere(null); @@ -163,6 +247,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject setSubModule('mechanics'); }} onPointerMissed={() => { + if (eyeDropMode) return; setSelectedPath(null); setSubModule('properties'); }} @@ -175,7 +260,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject name='events-sphere' ref={el => (sphereRefs.current[path.point.uuid] = el!)} onClick={(e) => { - if (isConnecting) return; + if (isConnecting || eyeDropMode) return; e.stopPropagation(); setSelectedActionSphere({ path, @@ -186,6 +271,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject }} userData={{ point: path.point, path }} onPointerMissed={() => { + if (eyeDropMode) return; setSubModule('properties'); setSelectedActionSphere(null); }} diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index 10934fb..739a92b 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -1,6 +1,5 @@ -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef, useMemo } from 'react'; import { useSelectedActionSphere, useSelectedPath, useSimulationPaths } from '../../store/store'; -import { useThree } from '@react-three/fiber'; import * as THREE from 'three'; import Behaviour from './behaviour/behaviour'; import PathCreation from './path/pathCreation'; @@ -14,7 +13,6 @@ function Simulation() { const [processes, setProcesses] = useState([]); useEffect(() => { - console.log('simulationPaths: ', simulationPaths); }, [simulationPaths]); // useEffect(() => { @@ -29,9 +27,10 @@ function Simulation() { // } // }, [selectedPath]); + return ( <> - + {activeModule === 'simulation' && ( <> diff --git a/app/src/modules/visualization/handleSaveTemplate.ts b/app/src/modules/visualization/handleSaveTemplate.ts index c489688..a71e654 100644 --- a/app/src/modules/visualization/handleSaveTemplate.ts +++ b/app/src/modules/visualization/handleSaveTemplate.ts @@ -1,74 +1,89 @@ +import { saveTemplateApi } from "../../services/realTimeVisulization/zoneData/saveTempleteApi"; import { Template } from "../../store/useTemplateStore"; import { captureVisualization } from "./captureVisualization"; type HandleSaveTemplateProps = { addTemplate: (template: Template) => void; + floatingWidget: []; // Updated type from `[]` to `any[]` for clarity + widgets3D: []; // Updated type from `[]` to `any[]` for clarity selectedZone: { - panelOrder: string[]; // Adjust the type based on actual data structure - widgets: any[]; // Replace `any` with the actual widget type + panelOrder: string[]; + widgets: any[]; }; templates?: Template[]; }; -// Generate a unique ID (placeholder function) +// Generate a unique ID const generateUniqueId = (): string => { return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`; }; -// Refactored function export const handleSaveTemplate = async ({ addTemplate, + floatingWidget, + widgets3D, selectedZone, templates = [], }: HandleSaveTemplateProps): Promise => { try { // Check if the selected zone has any widgets if (!selectedZone.widgets || selectedZone.widgets.length === 0) { - console.warn("Cannot save an empty template."); + console.warn("No widgets found in the selected zone."); return; } // Check if the template already exists - const isDuplicate = templates.some((template) => { - const isSamePanelOrder = + const isDuplicate = templates.some( + (template) => JSON.stringify(template.panelOrder) === - JSON.stringify(selectedZone.panelOrder); - const isSameWidgets = + JSON.stringify(selectedZone.panelOrder) && JSON.stringify(template.widgets) === - JSON.stringify(selectedZone.widgets); - return isSamePanelOrder && isSameWidgets; - }); + JSON.stringify(selectedZone.widgets) + ); if (isDuplicate) { - console.warn("This template already exists."); return; } // Capture visualization snapshot const snapshot = await captureVisualization(); - if (!snapshot) { - console.error("Failed to capture visualization snapshot."); - return; - } - + console.log("snapshot: ", snapshot); + // if (!snapshot) { + // return; + // } // Create a new template const newTemplate: Template = { id: generateUniqueId(), - name: `Template ${Date.now()}`, + name: `Template ${new Date().toISOString()}`, // Better name formatting panelOrder: selectedZone.panelOrder, widgets: selectedZone.widgets, snapshot, + floatingWidget, + widgets3D, }; - console.log("Saving template:", newTemplate); + // Extract organization from email + const email = localStorage.getItem("email") || ""; + const organization = email.includes("@") + ? email.split("@")[1]?.split(".")[0] + : ""; + + if (!organization) { + console.error("Organization could not be determined from email."); + return; + } // Save the template try { + const response = await saveTemplateApi(organization, newTemplate); + console.log("Save API Response:", response); + + // Add template only if API call succeeds addTemplate(newTemplate); - } catch (error) { - console.error("Failed to add template:", error); + } catch (apiError) { + console.error("Error saving template to API:", apiError); } } catch (error) { - console.error("Failed to save template:", error); + console.error("Error in handleSaveTemplate:", error); } }; diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index a7800e1..7aad5cb 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from "react"; import ModuleToggle from "../components/ui/ModuleToggle"; import SideBarLeft from "../components/layout/sidebarLeft/SideBarLeft"; import SideBarRight from "../components/layout/sidebarRight/SideBarRight"; -import useModuleStore from "../store/useModuleStore"; +import useModuleStore, { useThreeDStore } from "../store/useModuleStore"; import RealTimeVisulization from "../components/ui/componets/RealTimeVisulization"; import Tools from "../components/ui/Tools"; // import Scene from "../modules/scene/scene"; @@ -37,8 +37,8 @@ const Project: React.FC = () => { setZones([]); const email = localStorage.getItem("email"); if (email) { - useSocketStore.getState().initializeSocket(email); const Organization = email!.split("@")[1].split(".")[0]; + useSocketStore.getState().initializeSocket(email, Organization); const name = localStorage.getItem("userName"); if (Organization && name) { setOrganization(Organization); @@ -49,14 +49,14 @@ const Project: React.FC = () => { } }, []); const { isPlaying } = usePlayButtonStore(); + const { toggleThreeD } = useThreeDStore(); return (
{loadingProgress && } {!isPlaying && ( <> - - + {toggleThreeD && } diff --git a/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts b/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts index 31d5b11..f6cd496 100644 --- a/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts +++ b/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts @@ -1,13 +1,28 @@ -let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +let url_Backend_dwinzo = `http://${process.env.REACT_APP_TEST}`; -export const setFloorItemApi = async (organization: string, modeluuid: string, modelname: string, position: Object, rotation: Object, modelfileID: string, isLocked: boolean, isVisible: boolean) => { +export const setFloorItemApi = async ( + organization: string, + modeluuid: string, + modelname: string, + modelfileID: string, + position: Object, + rotation: Object, + isLocked: boolean, + isVisible: boolean, + eventData?: any +) => { try { - const response = await fetch(`${url_Backend_dwinzo}/api/v1/setFloorItems`, { + const body: any = { organization, modeluuid, modelname, position, rotation, modelfileID, isLocked, isVisible }; + if (eventData) { + body.eventData = eventData; + } + + const response = await fetch(`${url_Backend_dwinzo}/api/v2/setasset`, { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ organization, modeluuid, modelname, position, rotation, modelfileID, isLocked, isVisible }), + body: JSON.stringify(body), }); if (!response.ok) { @@ -15,6 +30,7 @@ export const setFloorItemApi = async (organization: string, modeluuid: string, m } const result = await response.json(); + console.log('result: ', result); return result; } catch (error) { if (error instanceof Error) { diff --git a/app/src/services/realTimeVisulization/zoneData/add3dWidget.ts b/app/src/services/realTimeVisulization/zoneData/add3dWidget.ts new file mode 100644 index 0000000..82562b7 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/add3dWidget.ts @@ -0,0 +1,36 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const adding3dWidgets = async ( + zoneId: string, + organization: string, + widget: {} +) => { + console.log('widget: ', widget); + console.log('organization: ', organization); + console.log('zoneId: ', zoneId); + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/3dwidget/save`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ organization, zoneId, widget }), + } + ); + + if (!response.ok) { + throw new Error("Failed to add 3dwidget in the zone"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts b/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts index 338f26b..fb644c6 100644 --- a/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts +++ b/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts @@ -1,10 +1,14 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; // let url_Backend_dwinzo = `http://192.168.0.102:5000`; + export const addingFloatingWidgets = async ( zoneId: string, organization: string, widget: {} ) => { + console.log('organization: ', organization); + console.log('widget: ', widget); + console.log('zoneId: ', zoneId); try { const response = await fetch( `${url_Backend_dwinzo}/api/v2/floatwidget/save`, diff --git a/app/src/services/realTimeVisulization/zoneData/deleteFloatingWidget.ts b/app/src/services/realTimeVisulization/zoneData/deleteFloatingWidget.ts new file mode 100644 index 0000000..85c96b8 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/deleteFloatingWidget.ts @@ -0,0 +1,35 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; + +export const deleteFloatingWidgetApi = async ( + floatWidgetID: string, + organization: string +) => { + console.log('organization: ', organization); + console.log('floatWidgetID: ', floatWidgetID); + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/floatwidget/delete`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ organization, floatWidgetID }), + } + ); + + if (!response.ok) { + throw new Error("Failed to delete floating widget in the zone"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/deleteTemplate.ts b/app/src/services/realTimeVisulization/zoneData/deleteTemplate.ts new file mode 100644 index 0000000..e452f6d --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/deleteTemplate.ts @@ -0,0 +1,32 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; + +export const deleteTemplateApi = async ( + templateID: string, + organization?: string +) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/TemplateDelete/${templateID}/${organization}`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + throw new Error("Failed to delete template "); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/get3dWidgetData.ts b/app/src/services/realTimeVisulization/zoneData/get3dWidgetData.ts new file mode 100644 index 0000000..b5b6200 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/get3dWidgetData.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const get3dWidgetZoneData = async ( + ZoneId?: string, + organization?: string +) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/3dwidgetData/${ZoneId}/${organization}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); + if (!response.ok) { + throw new Error("Failed to fetch Zone3dWidgetData"); + } + + return await response.json(); + } catch (error: any) { + throw new Error(error.message); + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/getSelect2dZoneData.ts b/app/src/services/realTimeVisulization/zoneData/getSelect2dZoneData.ts index 26a88c5..00d4dfe 100644 --- a/app/src/services/realTimeVisulization/zoneData/getSelect2dZoneData.ts +++ b/app/src/services/realTimeVisulization/zoneData/getSelect2dZoneData.ts @@ -1,4 +1,5 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; export const getSelect2dZoneData = async ( ZoneId?: string, diff --git a/app/src/services/realTimeVisulization/zoneData/getTemplate.ts b/app/src/services/realTimeVisulization/zoneData/getTemplate.ts new file mode 100644 index 0000000..a3aa3a3 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/getTemplate.ts @@ -0,0 +1,23 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const getTemplateData = async (organization?: string) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/templateData/${organization}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + throw new Error("Failed to fetch ZoneFloatingData"); + } + + return await response.json(); + } catch (error: any) { + throw new Error(error.message); + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/getZones.ts b/app/src/services/realTimeVisulization/zoneData/getZones.ts index a760959..8dbf79a 100644 --- a/app/src/services/realTimeVisulization/zoneData/getZones.ts +++ b/app/src/services/realTimeVisulization/zoneData/getZones.ts @@ -1,4 +1,5 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; export const getZoneData = async (zoneId: string, organization: string) => { console.log("organization: ", organization); @@ -14,7 +15,6 @@ export const getZoneData = async (zoneId: string, organization: string) => { } ); - if (!response.ok) { throw new Error("Failed to fetch zoneData"); } diff --git a/app/src/services/realTimeVisulization/zoneData/loadTemplate.ts b/app/src/services/realTimeVisulization/zoneData/loadTemplate.ts new file mode 100644 index 0000000..915160d --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/loadTemplate.ts @@ -0,0 +1,33 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const loadTempleteApi = async ( + templateID: string, + zoneId: string, + organization: string +) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/TemplatetoZone`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ organization, zoneId, templateID }), + } + ); + + if (!response.ok) { + throw new Error("Failed to add 3dwidget in the zone"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts b/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts new file mode 100644 index 0000000..5c18031 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts @@ -0,0 +1,28 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const saveTemplateApi = async (organization: string, template: {}) => { + console.log('template: ', template); + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/template/save`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ organization, template }), + }); + + if (!response.ok) { + throw new Error("Failed to save template zone"); + } + + const result = await response.json(); + console.log('result: ', result); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/useFloatingDataStore.ts b/app/src/services/realTimeVisulization/zoneData/useFloatingDataStore.ts new file mode 100644 index 0000000..39a542a --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/useFloatingDataStore.ts @@ -0,0 +1,8 @@ +import { create } from "zustand"; + +const useFloatingDataStore = create((set) => ({ + floatingdata: [], // Initial state + setfloatingadata: (newData: []) => set({ floatingdata: newData }), // Setter function +})); + +export default useFloatingDataStore; diff --git a/app/src/services/simulation/getAssetEventType.ts b/app/src/services/simulation/getAssetEventType.ts new file mode 100644 index 0000000..a681b12 --- /dev/null +++ b/app/src/services/simulation/getAssetEventType.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_TEST}`; + +export const getAssetEventType = async (modelId: string, organization: string) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/pointData/${modelId}/${organization}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch model event type"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; \ No newline at end of file diff --git a/app/src/store/store.ts b/app/src/store/store.ts index fe35801..d111aae 100644 --- a/app/src/store/store.ts +++ b/app/src/store/store.ts @@ -5,19 +5,16 @@ import { io } from "socket.io-client"; export const useSocketStore = create((set: any, get: any) => ({ socket: null, - initializeSocket: (email: any) => { + initializeSocket: (email: string, organization: string) => { const existingSocket = get().socket; if (existingSocket) { return; } - const socket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/`, - { - reconnection: false, - auth: { email }, - } - ); + const socket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder`, { + reconnection: false, + auth: { email, organization }, + }); set({ socket }); }, @@ -352,10 +349,30 @@ export const useStartSimulation = create((set: any) => ({ startSimulation: false, setStartSimulation: (x: any) => set({ startSimulation: x }), })); + +export const useEyeDropMode = create((set: any) => ({ + eyeDropMode: false, + setEyeDropMode: (x: any) => set({ eyeDropMode: x }), +})); + +export const useEditingPoint = create((set: any) => ({ + editingPoint: false, + setEditingPoint: (x: any) => set({ editingPoint: x }), +})); + +export const usePreviewPosition = create<{ + previewPosition: { x: number; y: number } | null; + setPreviewPosition: (position: { x: number; y: number } | null) => void; +}>((set) => ({ + previewPosition: null, + setPreviewPosition: (position) => set({ previewPosition: position }), +})); + export const usezoneTarget = create((set: any) => ({ zoneTarget: [], setZoneTarget: (x: any) => set({ zoneTarget: x }), })); + export const usezonePosition = create((set: any) => ({ zonePosition: [], setZonePosition: (x: any) => set({ zonePosition: x }), @@ -375,6 +392,7 @@ export const useAsset3dWidget = create((set: any) => ({ widgetSelect: "", setWidgetSelect: (x: any) => set({ widgetSelect: x }), })); + export const useWidgetSubOption = create((set: any) => ({ widgetSubOption: "2D", setWidgetSubOption: (x: any) => set({ widgetSubOption: x }), diff --git a/app/src/store/useChartStore.ts b/app/src/store/useChartStore.ts index 4d61952..079b9ff 100644 --- a/app/src/store/useChartStore.ts +++ b/app/src/store/useChartStore.ts @@ -9,20 +9,26 @@ interface MeasurementStore { measurements: Record; // Change array to Record interval: number; duration: string; + name: string; setMeasurements: (newMeasurements: Record) => void; updateDuration: (newDuration: string) => void; + updateName: (newName: string) => void; } const useChartStore = create((set) => ({ measurements: {}, // Initialize as an empty object interval: 1000, duration: "1h", + name:'', setMeasurements: (newMeasurements) => set(() => ({ measurements: newMeasurements })), updateDuration: (newDuration) => set(() => ({ duration: newDuration })), + + updateName: (newName) => + set(() => ({ duration: newName })), })); export default useChartStore; diff --git a/app/src/store/useDroppedObjectsStore.ts b/app/src/store/useDroppedObjectsStore.ts index dc648a9..83f3a8a 100644 --- a/app/src/store/useDroppedObjectsStore.ts +++ b/app/src/store/useDroppedObjectsStore.ts @@ -1,4 +1,5 @@ import { create } from "zustand"; +import { addingFloatingWidgets } from "../services/realTimeVisulization/zoneData/addFloatingWidgets"; type DroppedObject = { className: string; @@ -35,6 +36,8 @@ type DroppedObjectsState = { bottom: number | "auto"; } ) => void; + deleteObject: (zoneName: string, index: number) => void; // Add this line + duplicateObject: (zoneName: string, index: number) => void; // Add this line }; export const useDroppedObjectsStore = create((set) => ({ @@ -73,6 +76,72 @@ export const useDroppedObjectsStore = create((set) => ({ }, }; }), + + deleteObject: (zoneName: string, index: number) => + set((state) => { + const zone = state.zones[zoneName]; + if (!zone) return state; + return { + zones: { + [zoneName]: { + ...zone, + objects: zone.objects.filter((_, i) => i !== index), // Remove object at the given index + }, + }, + }; + }), + duplicateObject: async (zoneName: string, index: number) => { + const state = useDroppedObjectsStore.getState(); // Get the current state + const zone = state.zones[zoneName]; + + if (!zone) return; + + const originalObject = zone.objects[index]; + if (!originalObject) return; + + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + + // Create a shallow copy of the object with a unique ID and slightly adjusted position + const duplicatedObject: DroppedObject = { + ...originalObject, + id: `${originalObject.id}-copy-${Date.now()}`, // Unique ID + position: { + ...originalObject.position, + top: + typeof originalObject.position.top === "number" + ? originalObject.position.top + 20 // Offset vertically + : originalObject.position.top, + left: + typeof originalObject.position.left === "number" + ? originalObject.position.left + 20 // Offset horizontally + : originalObject.position.left, + }, + }; + + console.log("zone: ", zone.zoneId); + console.log("duplicatedObject: ", duplicatedObject); + + // Make async API call outside of Zustand set function + // let response = await addingFloatingWidgets( + // zone.zoneId, + // organization, + // duplicatedObject + // ); + + // if (response.message === "FloatWidget created successfully") { + // Update the state inside `set` + useDroppedObjectsStore.setState((state) => ({ + zones: { + ...state.zones, + [zoneName]: { + ...state.zones[zoneName], + objects: [...state.zones[zoneName].objects, duplicatedObject], // Append duplicated object + }, + }, + })); + // } + }, })); export interface DroppedObjects { @@ -90,3 +159,13 @@ export interface Zones { zoneId: string; objects: DroppedObject[]; } + +export const use3DWidget = create((set: any) => ({ + widgets3D: [], + setWidgets3D: (x: any) => set({ widgets3D: x }), +})); + +export const useFloatingWidget = create((set: any) => ({ + floatingWidget: [], + setFloatingWidget: (x: any) => set({ floatingWidget: x }), +})); diff --git a/app/src/store/useModuleStore.ts b/app/src/store/useModuleStore.ts index 6373af5..1012792 100644 --- a/app/src/store/useModuleStore.ts +++ b/app/src/store/useModuleStore.ts @@ -23,4 +23,17 @@ const useSubModuleStore = create((set) => ({ setSubModule: (subModule) => set({ subModule }), // Update subModule state })); -export { useSubModuleStore }; \ No newline at end of file +export { useSubModuleStore }; + +interface ThreeDState { + toggleThreeD: boolean; + setToggleThreeD: (value: boolean) => void; +} + +// Create the Zustand store +const useThreeDStore = create((set) => ({ + toggleThreeD: true, // Initial state + setToggleThreeD: (value) => set({ toggleThreeD: value }), // Action to update the state +})); + +export { useThreeDStore }; \ No newline at end of file diff --git a/app/src/store/useTemplateStore.ts b/app/src/store/useTemplateStore.ts index 2adcd2f..d416154 100644 --- a/app/src/store/useTemplateStore.ts +++ b/app/src/store/useTemplateStore.ts @@ -1,7 +1,5 @@ import { create } from "zustand"; -// type Side = "top" | "bottom" | "left" | "right"; - export interface Widget { id: string; type: string; @@ -15,21 +13,34 @@ export interface Template { name: string; panelOrder: string[]; widgets: Widget[]; - snapshot?: string | null; // Add an optional image property (base64) + floatingWidget: any[]; // Fixed empty array type + widgets3D: any[]; // Fixed empty array type + snapshot?: string | null; } interface TemplateStore { templates: Template[]; addTemplate: (template: Template) => void; + setTemplates: (templates: Template[]) => void; // Changed from `setTemplate` removeTemplate: (id: string) => void; } export const useTemplateStore = create((set) => ({ templates: [], + + // Add a new template to the list addTemplate: (template) => set((state) => ({ templates: [...state.templates, template], })), + + // Set (replace) the templates list with a new array + setTemplates: (templates) => + set(() => ({ + templates, // Ensures no duplication + })), + + // Remove a template by ID removeTemplate: (id) => set((state) => ({ templates: state.templates.filter((t) => t.id !== id), @@ -37,3 +48,4 @@ export const useTemplateStore = create((set) => ({ })); export default useTemplateStore; + diff --git a/app/src/store/useWidgetStore.ts b/app/src/store/useWidgetStore.ts index 115c6aa..047b57b 100644 --- a/app/src/store/useWidgetStore.ts +++ b/app/src/store/useWidgetStore.ts @@ -33,7 +33,7 @@ interface WidgetStore { setDraggedAsset: (asset: Widget | null) => void; // Setter for draggedAsset addWidget: (widget: Widget) => void; // Add a new widget setWidgets: (widgets: Widget[]) => void; // Replace the entire widgets array - setSelectedChartId: (widget: Widget | null) => void; // Set the selected chart/widget + setSelectedChartId: (widget: any | null) => void; // Set the selected chart/widget } // Create the store with Zustand diff --git a/app/src/styles/abstracts/variables.scss b/app/src/styles/abstracts/variables.scss index 4d201ad..44e5627 100644 --- a/app/src/styles/abstracts/variables.scss +++ b/app/src/styles/abstracts/variables.scss @@ -27,7 +27,7 @@ $input-text-color-dark: #b5b5c8; // Input field text color for dark mode // Accent colors $accent-color: #6f42c1; // Primary accent color -$accent-color-dark: #b392f0; // Primary accent color for dark mode +$accent-color-dark: #c4abf1; // Primary accent color for dark mode $highlight-accent-color: #e0dfff; // Highlighted accent for light mode $highlight-accent-color-dark: #403e6a; // Highlighted accent for dark mode @@ -45,6 +45,7 @@ $border-color-dark: #403e6a; // Border color for dark mode // Shadow color $shadow-color: #3c3c431a; // Shadow base color for light and dark mode +$shadow-color-dark: #8f8f8f1a; // Shadow base color for light and dark mode // Gradients $acent-gradient-dark: linear-gradient( diff --git a/app/src/styles/base/base.scss b/app/src/styles/base/base.scss index 86495f5..6adc1c5 100644 --- a/app/src/styles/base/base.scss +++ b/app/src/styles/base/base.scss @@ -58,7 +58,7 @@ --border-color: #{$border-color-dark}; // Border color for dark theme // Shadow variables - --shadow-main-dark: #{$shadow-color}; // Main shadow color + --shadow-main-dark: #{$shadow-color-dark}; // Main shadow color --box-shadow-light: 0px 2px 4px var(--shadow-main-dark); // Light shadow --box-shadow-medium: 0px 4px 8px var(--shadow-main-dark); // Medium shadow --box-shadow-heavy: 0px 8px 16px var(--shadow-main-dark); // Heavy shadow diff --git a/app/src/styles/components/input.scss b/app/src/styles/components/input.scss index 64eaaea..78a85ae 100644 --- a/app/src/styles/components/input.scss +++ b/app/src/styles/components/input.scss @@ -1,6 +1,22 @@ @use "../abstracts/variables" as *; @use "../abstracts/mixins" as *; +// global input style + +input { + width: 100%; + padding: 2px 4px; + border-radius: #{$border-radius-small}; + border: 1px solid var(--border-color); + outline: none; + background: transparent; + + &:focus, + &:active { + border: 1px solid var(--accent-color); + } +} + .input-value { color: var(--input-text-color); font-size: var(--font-size-regular); @@ -155,16 +171,6 @@ } } -.project-dropdowm-container { - position: relative; - height: 32px; - - .project-name { - line-height: 32px; - height: 100%; - } -} - .regularDropdown-container { width: 100%; min-width: 80px; @@ -179,7 +185,6 @@ display: flex; justify-content: space-between; cursor: pointer; - border: 1px solid var(--primary-color); border-radius: 6px; background-color: var(--background-color); } @@ -224,8 +229,8 @@ position: relative; .dropdown { - top: 3px; - right: 3px; + top: 2px; + right: 2px; position: absolute; background: var(--highlight-accent-color); border-radius: #{$border-radius-small}; @@ -238,19 +243,6 @@ } } -input { - width: 100%; - padding: 2px 4px; - border-radius: #{$border-radius-small}; - border: 1px solid var(--border-color); - outline: none; - - &:focus, - &:active { - border: 1px solid var(--accent-color); - } -} - .eye-dropper-input-container { display: flex; align-items: center; @@ -617,6 +609,7 @@ input { input { border: none; + background: transparent; &::placeholder { color: var(--text-disabled); @@ -655,4 +648,4 @@ input { .multi-email-invite-input.active { border: 1px solid var(--accent-color); } -} \ No newline at end of file +} diff --git a/app/src/styles/components/menu/menu.scss b/app/src/styles/components/menu/menu.scss index a4348a3..55b16f9 100644 --- a/app/src/styles/components/menu/menu.scss +++ b/app/src/styles/components/menu/menu.scss @@ -1,25 +1,58 @@ +@use "../../abstracts/variables" as *; +@use "../../abstracts/mixins" as *; + +.project-dropdowm-container { + display: flex; + align-items: center; + gap: 2px; + position: relative; + height: 32px; + .project-name { + line-height: 32px; + height: 100%; + } + .more-options-button { + @include flex-center; + border-radius: #{$border-radius-small}; + height: 28px; + position: relative; + &:hover { + background: var(--highlight-accent-color); + path { + fill: var(--accent-color); + } + } + } + .more-options-button.active { + background: var(--highlight-accent-color); + path { + fill: var(--accent-color); + } + } +} + .menu-bar { position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + top: 32px; + left: 0; z-index: 5; background-color: var(--background-color); color: var(--text-color); box-shadow: var(--box-shadow-light); border-radius: 8px; + border: 1px solid var(--border-color); .menu-buttons { display: flex; flex-direction: column; height: 100%; - padding: 8px 4px; + padding: 4px; min-width: 178px; .menu-button-container { position: relative; height: 100%; - padding: 8px; - + padding: 4px 8px 4px 12px; + border-radius: #{$border-radius-small}; .menu-button { width: 100%; cursor: pointer; @@ -32,7 +65,7 @@ .dropdown-icon { margin-left: 5px; font-size: var(--font-size-small); - color: #666666; + rotate: -90deg; } } @@ -46,23 +79,27 @@ box-shadow: var(--box-shadow-light); border: 1px solid var(--background-color); z-index: 100; - padding: 5px 0; - + padding: 4px; .menu-item-container { position: relative; - .menu-item { + padding: 4px 8px 4px 12px; + border-radius: #{$border-radius-small}; display: flex; justify-content: space-between; align-items: center; - padding: 8px 20px; cursor: pointer; white-space: nowrap; color: var(--text-color); - + .dropdown-icon { + rotate: -90deg; + } &:hover { background-color: var(--highlight-accent-color); - color: var(--highlight-accent-color); + span, + .menu-item-right span { + color: var(--accent-color); + } } .menu-item-right { @@ -92,19 +129,22 @@ box-shadow: var(--box-shadow-light); border: 1px solid var(--background-color); z-index: 101; - + padding: 4px; .submenu-item { - padding: 8px 20px; - cursor: pointer; + padding: 4px 8px 4px 12px; + border-radius: #{$border-radius-small}; display: flex; justify-content: space-between; + align-items: center; + cursor: pointer; + white-space: nowrap; color: var(--text-color); - &:hover { - background-color: var(--background-color-gray); - color: var(--highlight-accent-color); + background-color: var(--highlight-accent-color); + span { + color: var(--accent-color); + } } - .shortcut { color: var(--text-color); } @@ -115,13 +155,16 @@ &:hover { background-color: var(--highlight-accent-color); - color: var(--highlight-accent-color); + .menu-button { + color: var(--accent-color); + } } } } .split { width: 100%; height: 1px; - background-color: #e0dfff; + background-color: var(--highlight-accent-color); + margin: 2px 0; } } diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index f028f76..92b4eb2 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -253,6 +253,10 @@ .user-profile-container { display: flex; + .user-profile { + background: var(--accent-color); + color: var(--primary-color); + } .user-organization { height: 26px; @@ -314,6 +318,23 @@ .sidebar-right-content-container { .dataSideBar { + .inputs-wrapper { + .datas { + .input-value { + padding: 5px 10px; + } + + .input-value, + .rename-input { + margin-right: 24px; + width: 170px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } + width: 100%; height: 100%; display: flex; @@ -665,6 +686,10 @@ font-weight: var(--font-weight-regular); padding: 8px 0; } + .input-toggle-container { + padding: 0; + margin-bottom: 6px; + } .value-field-container { margin-bottom: 6px; @@ -1027,6 +1052,7 @@ position: relative; z-index: 3; font-size: var(--font-size-regular); + color: var(--background-color); } .asset-image { @@ -1077,6 +1103,7 @@ position: relative; z-index: 3; font-size: var(--font-size-regular); + color: var(--background-color); } .asset-image { diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index 9d4bde4..7ceb752 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -32,7 +32,10 @@ @use 'layout/toast'; // pages -@use 'pages/dashboard.scss'; +@use 'pages/dashboard'; @use 'pages/home'; @use 'pages/realTimeViz'; -@use 'pages/userAuth'; \ No newline at end of file +@use 'pages/userAuth'; + +// +@use './scene/scene' \ No newline at end of file diff --git a/app/src/styles/pages/dashboard.scss b/app/src/styles/pages/dashboard.scss index e5b07a4..3fc9a05 100644 --- a/app/src/styles/pages/dashboard.scss +++ b/app/src/styles/pages/dashboard.scss @@ -1,221 +1,221 @@ -@use "../abstracts/variables.scss" as *; -@use "../abstracts/mixins.scss" as *; - -.dashboard-main { - height: 100vh; - width: 100vw; - display: flex; - .side-pannel-container { - padding: 32px; - min-width: 240px; - height: 100vh; - display: flex; - flex-direction: column; - gap: 16px; - border-right: 1px solid var(--border-color); - .side-pannel-header { - @include flex-space-between; - .user-container { - @include flex-center; - gap: 6px; - .user-profile { - height: 32px; - width: 32px; - line-height: 32px; - text-align: center; - font-weight: var(--font-weight-medium); - background: var(--accent-color); - color: var(--primary-color); - border-radius: #{$border-radius-circle}; - } - .user-name { - color: var(--accent-color); - } - } - .notifications-container { - @include flex-center; - height: 24px; - width: 24px; - cursor: pointer; - } - } - .new-project-button { - padding: 12px 16px; - cursor: not-allowed; - color: var(--accent-color); - background-color: var(--background-color-secondary); - border-radius: #{$border-radius-large}; - } - .side-bar-content-container { - display: flex; - flex-direction: column; - justify-content: space-between; - height: 100%; - .side-bar-options-container { - .option-list { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - margin: 4px 0; - border-radius: #{$border-radius-medium}; - &:hover { - background: var(--background-color-secondary); - } - } - .active { - color: var(--accent-color); - font-weight: var(--font-weight-medium); - background-color: var(--highlight-accent-color); - &:hover { - background-color: var(--highlight-accent-color); - } - } - } - } - } - .dashboard-home-container { - width: 100%; - .dashboard-navbar-container { - margin-top: 28px; - padding: 8px 34px 8px 12px; - @include flex-center; - .title { - text-transform: capitalize; - font-size: var(--font-size-large); - width: 100%; - } - .market-place-button { - @include flex-center; - gap: 6px; - padding: 8px 14px; - background: var(--accent-gradient-color); - white-space: nowrap; - border-radius: #{$border-radius-large}; - color: var(--primary-color); - } - .search-wrapper { - width: 400px; - } - } - .container { - margin: 22px 0; - width: 100%; - padding: 0 12px; - .header { - font-size: var(--font-size-large); - } - .cards-container { - display: flex; - flex-wrap: wrap; - position: relative; - width: 100%; - padding: 8px; - gap: 18px; - } - } - } -} - -.dashboard-card-container { - height: 242px; - width: calc((100% / 5) - 23px); - min-width: 260px; - position: relative; - border: 1px solid var(--border-color); - border-radius: #{$border-radius-large}; - overflow: hidden; - .preview-container { - height: 100%; - width: 100%; - img { - height: 100%; - width: 100%; - object-fit: cover; - vertical-align: top; - border: none; - outline: none; - } - } - .project-details-container { - @include flex-space-between; - position: absolute; - bottom: 0; - width: 100%; - padding: 8px 16px; - background: var(--primary-color); - border-radius: 10px; - .project-details { - .project-name { - margin-bottom: 2px; - } - .project-data { - color: var(--accent-color); - } - } - .users-list-container { - @include flex-center; - gap: 6px; - .user-profile { - height: 26px; - width: 26px; - line-height: 26px; - text-align: center; - background-color: var(--accent-color); - color: var(--primary-color); - border-radius: #{$border-radius-circle}; - } - } - } -} - -.market-place-banner-container { - width: 100%; - height: 230px; - overflow: hidden; - position: relative; - padding: 0 24px; - img { - height: 100%; - width: 100%; - object-fit: cover; - border-radius: 30px; - } - .hero-text { - position: absolute; - left: 52px; - bottom: 25px; - font-size: 48px; - font-family: #{$font-roboto}; - font-weight: 800; - color: #ffffff; - text-transform: uppercase; - } - .context { - position: absolute; - top: 20px; - right: 58px; - text-transform: uppercase; - font-size: 22px; - width: 300px; - color: #ffffff; - font-family: #{$font-roboto}; - } - .arrow-context { - position: absolute; - bottom: 27px; - right: 300px; - } - .explore-button { - position: absolute; - top: 95px; - right: 52px; - padding: 10px 20px; - text-transform: uppercase; - font-size: 24px; - border: 1px solid #ffffff; - color: #ffffff; - font-family: #{$font-roboto}; - cursor: pointer; - } -} +@use "../abstracts/variables.scss" as *; +@use "../abstracts/mixins.scss" as *; + +.dashboard-main { + height: 100vh; + width: 100vw; + display: flex; + .side-pannel-container { + padding: 32px; + min-width: 240px; + height: 100vh; + display: flex; + flex-direction: column; + gap: 16px; + border-right: 1px solid var(--border-color); + .side-pannel-header { + @include flex-space-between; + .user-container { + @include flex-center; + gap: 6px; + .user-profile { + height: 32px; + width: 32px; + line-height: 32px; + text-align: center; + font-weight: var(--font-weight-medium); + background: var(--accent-color); + color: var(--primary-color); + border-radius: #{$border-radius-circle}; + } + .user-name { + color: var(--accent-color); + } + } + .notifications-container { + @include flex-center; + height: 24px; + width: 24px; + cursor: pointer; + } + } + .new-project-button { + padding: 12px 16px; + cursor: not-allowed; + color: var(--accent-color); + background-color: var(--background-color-secondary); + border-radius: #{$border-radius-large}; + } + .side-bar-content-container { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + .side-bar-options-container { + .option-list { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + margin: 4px 0; + border-radius: #{$border-radius-medium}; + &:hover { + background: var(--background-color-secondary); + } + } + .active { + color: var(--accent-color); + font-weight: var(--font-weight-medium); + background-color: var(--highlight-accent-color); + &:hover { + background-color: var(--highlight-accent-color); + } + } + } + } + } + .dashboard-home-container { + width: 100%; + .dashboard-navbar-container { + margin-top: 28px; + padding: 8px 34px 8px 12px; + @include flex-center; + .title { + text-transform: capitalize; + font-size: var(--font-size-large); + width: 100%; + } + .market-place-button { + @include flex-center; + gap: 6px; + padding: 8px 14px; + background: var(--accent-gradient-color); + white-space: nowrap; + border-radius: #{$border-radius-large}; + color: var(--primary-color); + } + .search-wrapper { + width: 400px; + } + } + .container { + margin: 22px 0; + width: 100%; + padding: 0 12px; + .header { + font-size: var(--font-size-large); + } + .cards-container { + display: flex; + flex-wrap: wrap; + position: relative; + width: 100%; + padding: 8px; + gap: 18px; + } + } + } +} + +.dashboard-card-container { + height: 242px; + width: calc((100% / 5) - 23px); + min-width: 260px; + position: relative; + border: 1px solid var(--border-color); + border-radius: #{$border-radius-large}; + overflow: hidden; + .preview-container { + height: 100%; + width: 100%; + img { + height: 100%; + width: 100%; + object-fit: cover; + vertical-align: top; + border: none; + outline: none; + } + } + .project-details-container { + @include flex-space-between; + position: absolute; + bottom: 0; + width: 100%; + padding: 8px 16px; + background: var(--primary-color); + border-radius: 10px; + .project-details { + .project-name { + margin-bottom: 2px; + } + .project-data { + color: var(--accent-color); + } + } + .users-list-container { + @include flex-center; + gap: 6px; + .user-profile { + height: 26px; + width: 26px; + line-height: 26px; + text-align: center; + background-color: var(--accent-color); + color: var(--primary-color); + border-radius: #{$border-radius-circle}; + } + } + } +} + +.market-place-banner-container { + width: 100%; + height: 230px; + overflow: hidden; + position: relative; + padding: 0 24px; + img { + height: 100%; + width: 100%; + object-fit: cover; + border-radius: 30px; + } + .hero-text { + position: absolute; + left: 52px; + bottom: 25px; + font-size: 48px; + font-family: #{$font-roboto}; + font-weight: 800; + color: #ffffff; + text-transform: uppercase; + } + .context { + position: absolute; + top: 20px; + right: 58px; + text-transform: uppercase; + font-size: 22px; + width: 300px; + color: #ffffff; + font-family: #{$font-roboto}; + } + .arrow-context { + position: absolute; + bottom: 27px; + right: 300px; + } + .explore-button { + position: absolute; + top: 95px; + right: 52px; + padding: 10px 20px; + text-transform: uppercase; + font-size: 24px; + border: 1px solid #ffffff; + color: #ffffff; + font-family: #{$font-roboto}; + cursor: pointer; + } +} diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index 69df460..d58f12d 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -22,7 +22,7 @@ min-height: 83px; background: var(--background-color); border: 1.23px solid var(--border-color); - box-shadow: 0px 4.91px 4.91px 0px #0000001c; + box-shadow: var(--box-shadow-heavy); border-radius: $border-radius-medium; padding: 18px; position: absolute; @@ -31,6 +31,7 @@ .scene-container { overflow: hidden; + background: #232323; } .icon { @@ -541,4 +542,145 @@ .zone.active { background-color: #007bff; color: white; +} + +.floating-wrapper { + .icon { + width: 25px !important; + height: 25px !important; + background-color: transparent; + } + + .kebab { + width: 30px; + height: 30px; + position: absolute !important; + top: 0px; + right: 0px; + z-index: 10; + cursor: pointer; + @include flex-center; + } + + .kebab-options { + position: absolute; + top: 18px; + right: 5px; + transform: translate(0px, 0); + background-color: var(--background-color); + z-index: 10; + + display: flex; + flex-direction: column; + gap: 6px; + border-radius: 4px; + + box-shadow: var(--box-shadow-medium); + + .btn { + display: flex; + gap: 6px; + align-items: center; + padding: 5px 10px; + color: var(--text-color); + cursor: pointer; + + &:hover { + .label { + color: var(--accent-color); + } + } + + &:hover { + background-color: var(--highlight-accent-color); + width: 100%; + + svg { + &:first-child { + fill: var(--accent-color); + } + + &:last-child { + fill: auto; + stroke: var(--accent-color); + } + } + } + } + + .dublicate { + cursor: not-allowed; + } + } +} + + + + + +.distance-line { + position: absolute; + border-style: dashed; + border-color: var(--accent-color); /* Green color for visibility */ + border-width: 1px; + pointer-events: none; /* Ensure lins don't interfere with dragging */ + z-index: 10000; +} + +/* Label styles for displaying distance values */ +.distance-label { + position: absolute; + background-color: var(--accent-color); + color: white; + font-size: 12px; + padding: 2px 6px; + border-radius: 3px; + white-space: nowrap; + transform: translate(-50%, -50%); /* Center the label */ +} + +/* Specific styles for each type of line */ + +/* Top distance line */ +.distance-line.top { + border-bottom: none; /* Remove bottom border for a single line */ + width: 2px; /* Thin vertical line */ +} + +.distance-line.top .distance-label { + top: -10px; /* Position label above the line */ + left: 50%; /* Center horizontally */ +} + +/* Bottom distance line */ +.distance-line.bottom { + border-top: none; /* Remove top border for a single line */ + width: 2px; /* Thin vertical line */ +} + +.distance-line.bottom .distance-label { + bottom: -10px; /* Position label below the line */ + left: 50%; /* Center horizontally */ +} + +/* Left distance line */ +.distance-line.left { + border-right: none; /* Remove right border for a single line */ + height: 2px; /* Thin horizontal line */ +} + +.distance-line.left .distance-label { + left: -10px; /* Position label to the left of the line */ + top: 50%; /* Center vertically */ +} + +/* Right distance line */ +.distance-line.right { + border-left: none; /* Remove left border for a single line */ + height: 2px; /* Thin horizontal line */ +} + +.distance-line.right .distance-label { + right: -10px; /* Position label to the right of the line */ + top: 50%; /* Center vertically */ } \ No newline at end of file diff --git a/app/src/styles/scene/scene.scss b/app/src/styles/scene/scene.scss new file mode 100644 index 0000000..9de35db --- /dev/null +++ b/app/src/styles/scene/scene.scss @@ -0,0 +1,23 @@ +@use "../abstracts/variables" as *; +@use "../abstracts/mixins" as *; + +.distance-text-wrapper { + pointer-events: none !important; +} +.distance-text { + pointer-events: none !important; + .distance { + position: absolute; + transform: translate(-50%, -50%) scale(.8); + pointer-events: none !important; + white-space: nowrap; + // style + font-size: var(--font-size-large); + padding: 2px 8px; + background: var(--primary-color); + color: var(--accent-color); + outline: 1px solid var(--accent-color); + border-radius: #{$border-radius-medium}; + box-shadow: var(--box-shadow-light); + } +} diff --git a/app/src/types/world/worldConstants.ts b/app/src/types/world/worldConstants.ts index 66743fb..2bc3835 100644 --- a/app/src/types/world/worldConstants.ts +++ b/app/src/types/world/worldConstants.ts @@ -1,3 +1,5 @@ +const savedTheme: string | null = localStorage.getItem("theme"); + export type Controls = { azimuthRotateSpeed: number; polarRotateSpeed: number; @@ -57,44 +59,39 @@ export type ThreeDimension = { }; export type GridConfig = { - size: number; - divisions: number; - primaryColor: string; - secondaryColor: string; - - position2D: [x: number, y: number, z: number]; - position3D: [x: number, y: number, z: number]; -}; + size: number; + divisions: number; + primaryColor: string; + secondaryColor: string; + position2D: [x: number, y: number, z: number]; + position3D: [x: number, y: number, z: number]; +} export type PlaneConfig = { - position2D: [x: number, y: number, z: number]; - position3D: [x: number, y: number, z: number]; - rotation: number; - - width: number; - height: number; - color: string; -}; + position2D: [x: number, y: number, z: number]; + position3D: [x: number, y: number, z: number]; + rotation: number; + width: number; + height: number; + color: string; +} export type ShadowConfig = { - shadowOffset: number; - - shadowmapSizewidth: number; - shadowmapSizeheight: number; - shadowcamerafar: number; - shadowcameranear: number; - shadowcameratop: number; - shadowcamerabottom: number; - shadowcameraleft: number; - shadowcameraright: number; - shadowbias: number; - shadownormalBias: number; - - shadowMaterialPosition: [x: number, y: number, z: number]; - shadowMaterialRotation: [x: number, y: number, z: number]; - - shadowMaterialOpacity: number; -}; + shadowOffset: number, + shadowmapSizewidth: number, + shadowmapSizeheight: number, + shadowcamerafar: number, + shadowcameranear: number, + shadowcameratop: number, + shadowcamerabottom: number, + shadowcameraleft: number, + shadowcameraright: number, + shadowbias: number, + shadownormalBias: number, + shadowMaterialPosition: [x: number, y: number, z: number], + shadowMaterialRotation: [x: number, y: number, z: number], + shadowMaterialOpacity: number, +} export type SkyConfig = { defaultTurbidity: number; @@ -112,40 +109,34 @@ export type AssetConfig = { }; export type PointConfig = { - defaultInnerColor: string; - defaultOuterColor: string; - deleteColor: string; - boxScale: [number, number, number]; - - wallOuterColor: string; - floorOuterColor: string; - aisleOuterColor: string; - zoneOuterColor: string; - - snappingThreshold: number; -}; + defaultInnerColor: string; + defaultOuterColor: string; + deleteColor: string; + boxScale: [number, number, number]; + wallOuterColor: string; + floorOuterColor: string; + aisleOuterColor: string; + zoneOuterColor: string; + snappingThreshold: number; +} export type LineConfig = { - tubularSegments: number; - radius: number; - radialSegments: number; - - wallName: string; - floorName: string; - aisleName: string; - zoneName: string; - referenceName: string; - - lineIntersectionPoints: number; - - defaultColor: string; - - wallColor: string; - floorColor: string; - aisleColor: string; - zoneColor: string; - helperColor: string; -}; + tubularSegments: number; + radius: number; + radialSegments: number; + wallName: string; + floorName: string; + aisleName: string; + zoneName: string; + referenceName: string; + lineIntersectionPoints: number; + defaultColor: string; + wallColor: string; + floorColor: string; + aisleColor: string; + zoneColor: string; + helperColor: string; +} export type WallConfig = { defaultColor: string; @@ -154,11 +145,10 @@ export type WallConfig = { }; export type FloorConfig = { - defaultColor: string; - height: number; - - textureScale: number; -}; + defaultColor: string; + height: number; + textureScale: number; +} export type RoofConfig = { defaultColor: string; @@ -166,17 +156,16 @@ export type RoofConfig = { }; export type AisleConfig = { - width: number; - height: number; - - defaultColor: number; -}; + width: number; + height: number; + defaultColor: number; +} export type ZoneConfig = { - defaultColor: string; - - color: string; -}; + defaultColor: string; + height: number; + color: string; +} export type ColumnConfig = { defaultColor: string; @@ -199,9 +188,7 @@ export const firstPersonControls: Controls = { minDistance: 0, // Minimum distance from the target maxDistance: 0, // Maximum distance from the target maxPolarAngle: Math.PI, // Maximum polar angle - leftMouse: 1, // Mouse button for rotation (ROTATE) - forwardSpeed: 0.3, // Speed of forward movement backwardSpeed: -0.3, // Speed of backward movement leftSpeed: -0.3, // Speed of left movement @@ -255,28 +242,27 @@ export const threeDimension: ThreeDimension = { export const camPositionUpdateInterval: number = 200; // Interval for updating the camera position export const gridConfig: GridConfig = { - size: 300, // Size of the grid - divisions: 75, // Number of divisions in the grid - primaryColor: "#d5d5d5", // Primary color of the grid - secondaryColor: "#e3e3e3", // Secondary color of the grid + size: 300, // Size of the grid + divisions: 75, // Number of divisions in the grid + primaryColor: savedTheme === "dark" ? "#131313" : "#d5d5d5", // Primary color of the grid + secondaryColor: savedTheme === "dark" ? "#434343" : "#e3e3e3", // Secondary color of the grid - position2D: [0, 0.1, 0], // Position of the grid in 2D view - position3D: [0, -0.5, 0], // Position of the grid in 3D view -}; + position2D: [0, 0.1, 0], // Position of the grid in 2D view + position3D: [0, -0.5, 0], // Position of the grid in 3D view +} export const planeConfig: PlaneConfig = { position2D: [0, -0.5, 0], // Position of the plane position3D: [0, -0.65, 0], // Position of the plane rotation: -Math.PI / 2, // Rotation of the plane - width: 300, // Width of the plane - height: 300, // Height of the plane - color: "#f3f3f3", // Color of the plane -}; + width: 300, // Width of the plane + height: 300, // Height of the plane + color: savedTheme === "dark" ? "#323232" : "#f3f3f3" // Color of the plane +} export const shadowConfig: ShadowConfig = { shadowOffset: 50, // Offset of the shadow - shadowmapSizewidth: 1024, // Width of the shadow map shadowmapSizeheight: 1024, // Height of the shadow map // shadowmapSizewidth: 8192, // Width of the shadow map @@ -289,10 +275,8 @@ export const shadowConfig: ShadowConfig = { shadowcameraright: 30, // Right plane of the shadow camera shadowbias: -0.001, // Bias of the shadow shadownormalBias: 0.02, // Normal bias of the shadow - shadowMaterialPosition: [0, 0.01, 0], // Position of the shadow material shadowMaterialRotation: [-Math.PI / 2, 0, 0], // Rotation of the shadow material - shadowMaterialOpacity: 0.1, // Opacity of the shadow material }; @@ -316,12 +300,10 @@ export const pointConfig: PointConfig = { defaultOuterColor: "#ffffff", // Default outer color of the points deleteColor: "#ff0000", // Color of the points when deleting boxScale: [0.5, 0.5, 0.5], // Scale of the points - wallOuterColor: "#C7C7C7", // Outer color of the wall points floorOuterColor: "#808080", // Outer color of the floor points aisleOuterColor: "#FBBC05", // Outer color of the aisle points zoneOuterColor: "#007BFF", // Outer color of the zone points - snappingThreshold: 1, // Threshold for snapping }; @@ -329,17 +311,13 @@ export const lineConfig: LineConfig = { tubularSegments: 64, // Number of tubular segments radius: 0.15, // Radius of the lines radialSegments: 8, // Number of radial segments - wallName: "WallLine", // Name of the wall lines floorName: "FloorLine", // Name of the floor lines aisleName: "AisleLine", // Name of the aisle lines zoneName: "ZoneLine", // Name of the zone lines referenceName: "ReferenceLine", // Name of the reference lines - lineIntersectionPoints: 300, // Number of intersection points - defaultColor: "#000000", // Default color of the lines - wallColor: "#C7C7C7", // Color of the wall lines floorColor: "#808080", // Color of the floor lines aisleColor: "#FBBC05", // Color of the aisle lines @@ -371,9 +349,10 @@ export const aisleConfig: AisleConfig = { }; export const zoneConfig: ZoneConfig = { - defaultColor: "black", // Default color of the zones - color: "blue", // Color of the zones -}; + defaultColor: "black", // Default color of the zones + height: 3, + color: "#8656DF" // Color of the zones +} export const columnConfig: ColumnConfig = { defaultColor: "White", // Default color of the columns diff --git a/app/src/types/world/worldTypes.d.ts b/app/src/types/world/worldTypes.d.ts index 31c032c..4d10a36 100644 --- a/app/src/types/world/worldTypes.d.ts +++ b/app/src/types/world/worldTypes.d.ts @@ -198,9 +198,30 @@ export type FloorItemType = { modelname: string; position: [number, number, number]; rotation: { x: number; y: number; z: number }; - modelfileID?: string; + modelfileID: string; isLocked: boolean; isVisible: boolean; + eventData?: { + type: 'Conveyor'; + points: { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | []; + triggers: { uuid: string; name: string; type: string; isUsed: boolean; bufferTime: number }[] | []; + connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; + }[]; + speed: number | string; + } | { + type: 'Vehicle'; + point: { + uuid: string; + position: [number, number, number]; + actions: { uuid: string; name: string; type: string; start: { x: number, y: number } | {}, hitCount: number, end: { x: number, y: number } | {}, buffer: number }; + connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; + speed: number; + }; + }; }; // Array of floor items for managing multiple objects on the floor @@ -310,7 +331,7 @@ interface VehicleEventsSchema { point: { uuid: string; position: [number, number, number]; - actions: { uuid: string; name: string; type: string; start: string, hitCount: number, end: string, buffer: number }; + actions: { uuid: string; name: string; type: string; start: { x: number, y: number } | {}, hitCount: number, end: { x: number, y: number } | {}, buffer: number }; connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; speed: number; }; diff --git a/app/src/utils/theme.ts b/app/src/utils/theme.ts index 28f2488..03d2454 100644 --- a/app/src/utils/theme.ts +++ b/app/src/utils/theme.ts @@ -1,34 +1,31 @@ -export {}; +export { }; + // Function to set the theme based on user preference or system default function setTheme() { - // Check for saved theme in localStorage const savedTheme: string | null = localStorage.getItem('theme'); - - // If no saved theme, use system default preference const systemPrefersDark: boolean = window.matchMedia('(prefers-color-scheme: dark)').matches; const defaultTheme: string = savedTheme || (systemPrefersDark ? 'dark' : 'light'); - - // Set the theme on page load document.documentElement.setAttribute('data-theme', defaultTheme); + localStorage.setItem('theme', defaultTheme); } -// Call the function to set the theme +// Function to toggle the theme +export function toggleTheme() { + const currentTheme: string | null = document.documentElement.getAttribute('data-theme'); + const newTheme: string = currentTheme === 'dark' ? 'light' : 'dark'; + + document.documentElement.setAttribute('data-theme', newTheme); + localStorage.setItem('theme', newTheme); +} + +// Initialize theme on page load setTheme(); -// Check if the toggle button exists -const toggleSwitch: Element | null = document.querySelector('.theme-switch'); +// Example: Call toggleTheme() when a button is clicked +const toggleSwitch: Element | null = document.querySelector('#theme-switch'); if (toggleSwitch) { - toggleSwitch.addEventListener('click', () => { - const currentTheme: string | null = document.documentElement.getAttribute('data-theme'); - - // Toggle between dark and light themes - const newTheme: string = currentTheme === 'dark' ? 'light' : 'dark'; - document.documentElement.setAttribute('data-theme', newTheme); - - // Save the new preference in localStorage - localStorage.setItem('theme', newTheme); - }); + toggleSwitch.addEventListener('click', toggleTheme); } else { console.warn("Theme switch button not found!"); }