From 70807d4ec4c629ed35b34d6167dbf4ed7b3cfad1 Mon Sep 17 00:00:00 2001 From: Nalvazhuthi <nalvazhuthi@hexrfactory.com> Date: Thu, 3 Apr 2025 18:02:28 +0530 Subject: [PATCH] added zones drop down in builder ouline and adjust width of displayZones --- app/package-lock.json | 98 +++--- app/package.json | 1 + .../icons/RealTimeVisulationIcons.tsx | 22 +- .../layout/sidebarLeft/SideBarLeft.tsx | 13 +- .../visualization/widgets/Widgets2D.tsx | 2 +- .../visualization/design/Design.tsx | 289 ++++++++++++------ app/src/components/ui/componets/Panel.tsx | 4 +- .../ui/componets/RealTimeVisulization.tsx | 2 +- .../componets/functions/determinePosition.ts | 28 +- app/src/components/ui/list/DropDownList.tsx | 79 ++++- app/src/components/ui/list/List.tsx | 128 ++++++-- .../visualization/handleSaveTemplate.ts | 2 +- app/src/pages/Project.tsx | 2 +- .../zoneData/saveTempleteApi.ts | 4 +- app/src/styles/components/lists.scss | 30 +- app/src/styles/layout/sidebar.scss | 20 +- app/src/styles/pages/realTimeViz.scss | 16 +- 17 files changed, 545 insertions(+), 195 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index a820896..8be748e 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -29,6 +29,7 @@ "chartjs-plugin-annotation": "^3.1.0", "glob": "^11.0.0", "gsap": "^3.12.5", + "html2canvas": "^1.4.1", "leva": "^0.10.0", "mqtt": "^5.10.4", "postprocessing": "^6.36.4", @@ -2019,7 +2020,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -2031,7 +2032,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -4134,26 +4135,6 @@ "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@testing-library/dom": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", - "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@testing-library/jest-dom": { "version": "5.17.0", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", @@ -4265,25 +4246,25 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "devOptional": true + "dev": true }, "node_modules/@turf/along": { "version": "7.2.0", @@ -8047,6 +8028,15 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -9017,7 +9007,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "devOptional": true + "dev": true }, "node_modules/cross-env": { "version": "7.0.3", @@ -9102,6 +9092,15 @@ "postcss": "^8.4" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-loader": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", @@ -9885,7 +9884,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.3.1" } @@ -12489,6 +12488,19 @@ } } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -15235,7 +15247,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "devOptional": true + "dev": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -20434,6 +20446,15 @@ "node": "*" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -20694,7 +20715,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "devOptional": true, + "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -20737,7 +20758,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "devOptional": true, + "dev": true, "dependencies": { "acorn": "^8.11.0" }, @@ -20749,7 +20770,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true + "dev": true }, "node_modules/tsconfig-paths": { "version": "3.15.0", @@ -21220,6 +21241,15 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -21236,7 +21266,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "devOptional": true + "dev": true }, "node_modules/v8-to-istanbul": { "version": "8.1.1", @@ -22295,7 +22325,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } diff --git a/app/package.json b/app/package.json index 66e3b39..ce5c7d3 100644 --- a/app/package.json +++ b/app/package.json @@ -24,6 +24,7 @@ "chartjs-plugin-annotation": "^3.1.0", "glob": "^11.0.0", "gsap": "^3.12.5", + "html2canvas": "^1.4.1", "leva": "^0.10.0", "mqtt": "^5.10.4", "postprocessing": "^6.36.4", diff --git a/app/src/components/icons/RealTimeVisulationIcons.tsx b/app/src/components/icons/RealTimeVisulationIcons.tsx index 1d144d6..84d8cca 100644 --- a/app/src/components/icons/RealTimeVisulationIcons.tsx +++ b/app/src/components/icons/RealTimeVisulationIcons.tsx @@ -8,37 +8,45 @@ export function CleanPannel() { xmlns="http://www.w3.org/2000/svg" > <g clipPath="url(#clip0_1782_1158)"> - <path d="M12 0H0V12H12V0Z" fill="white" fillOpacity="0.01" /> + <path + d="M12 0H0V12H12V0Z" + fill="var(--text-color)" + fillOpacity="0.01" + /> <path fillRule="evenodd" clipRule="evenodd" d="M5 1.47852H7V3.47853H10.75V5.47853H1.25V3.47853H5V1.47852Z" - stroke="#2B3344" + stroke="var(--text-color)" strokeLinecap="round" strokeLinejoin="round" /> - <path d="M2 10H10V5.5H2V10Z" stroke="#2B3344" strokeLinejoin="round" /> + <path + d="M2 10H10V5.5H2V10Z" + stroke="var(--text-color)" + strokeLinejoin="round" + /> <path d="M4 9.97439V8.47852" - stroke="#2B3344" + stroke="var(--text-color)" strokeLinecap="round" strokeLinejoin="round" /> <path d="M6 9.97461V8.47461" - stroke="#2B3344" + stroke="var(--text-color)" strokeLinecap="round" strokeLinejoin="round" /> <path d="M8 9.97439V8.47852" - stroke="#2B3344" + stroke="var(--text-color)" strokeLinecap="round" strokeLinejoin="round" /> <path d="M3 10H9" - stroke="#2B3344" + stroke="var(--text-color)" strokeLinecap="round" strokeLinejoin="round" /> diff --git a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx index fb7496a..b1aad0b 100644 --- a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx +++ b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx @@ -49,7 +49,7 @@ const SideBarLeft: React.FC = () => { </> ) : activeModule === "market" ? ( <></> - ) : ( + ) : activeModule === "builder" ? ( <> <ToggleHeader options={["Outline", "Assets"]} @@ -60,6 +60,17 @@ const SideBarLeft: React.FC = () => { {activeOption === "Outline" ? <Outline /> : <Assets />} </div> </> + ) : ( + <> + <ToggleHeader + options={["Outline"]} + activeOption={activeOption} + handleClick={handleToggleClick} + /> + <div className="sidebar-left-content-container"> + {activeOption === "Outline" ? <Outline /> : <Assets />} + </div> + </> )} </div> )} diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx index 561c82b..c26dc85 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx @@ -14,7 +14,7 @@ const chartTypes: ChartType[] = [ ]; const sampleData = { - labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"], + labels: ["January", "February", "March", "April", "May", "June", "July"], datasets: [ { data: [65, 59, 80, 81, 56, 55, 40], diff --git a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx index f0908b8..e78b4dd 100644 --- a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx +++ b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx @@ -1,18 +1,21 @@ -import { useState } from "react"; +import { useState, useEffect, useRef } from "react"; import { useWidgetStore } from "../../../../../store/useWidgetStore"; import ChartComponent from "../../../sidebarLeft/visualization/widgets/ChartComponent"; import RegularDropDown from "../../../../ui/inputs/RegularDropDown"; +import { WalletIcon } from "../../../../icons/3dChartIcons"; +import SimpleCard from "../../../../ui/realTimeVis/floating/SimpleCard"; -// Define Props Interface interface Widget { id: string; - type: string; // Chart type (e.g., "bar", "line") - panel: "top" | "bottom" | "left" | "right"; // Panel location - title: string; + type?: string; + panel: "top" | "bottom" | "left" | "right"; + title?: string; + header?: string; fontFamily?: string; fontSize?: string; fontWeight?: string; - data: { + className?: string; + data?: { labels: string[]; datasets: { data: number[]; @@ -20,162 +23,263 @@ interface Widget { borderColor: string; borderWidth: number; }[]; - }; // Data for the chart + }; + value?: string; + per?: string; +} + +interface ChartElement { + tagName: string; + className: string; + textContent: string; + selector: string; } const Design = () => { - const [selectedName, setSelectedName] = useState("drop down"); - console.log("selectedName: ", selectedName); - - const [selectedElement, setSelectedElement] = useState("drop down"); - console.log("selectedElement: ", selectedElement); - const [selectedFont, setSelectedFont] = useState("drop down"); - console.log("selectedFont: ", selectedFont); - const [selectedSize, setSelectedSize] = useState("drop down"); - console.log("selectedSize: ", selectedSize); - const [selectedWeight, setSelectedWeight] = useState("drop down"); - console.log("selectedWeight: ", selectedWeight); + const [elementColor, setElementColor] = useState("#6f42c1"); + const [showColorPicker, setShowColorPicker] = useState(false); + const [chartElements, setChartElements] = useState<ChartElement[]>([]); + const [selectedElementToStyle, setSelectedElementToStyle] = useState<string | null>(null); + const [nameInput, setNameInput] = useState(""); + const chartRef = useRef<HTMLDivElement>(null); - const [elementColor, setElementColor] = useState("#6f42c1"); // Default color for elements - const [showColorPicker, setShowColorPicker] = useState(false); // Manage visibility of the color picker + const { selectedChartId, setSelectedChartId, widgets, setWidgets } = useWidgetStore(); - // Zustand Store Hooks - const { selectedChartId, setSelectedChartId, widgets, setWidgets } = - useWidgetStore(); + // Initialize name input and extract elements when selectedChartId changes + useEffect(() => { + setNameInput(selectedChartId?.header || selectedChartId?.title || ""); + + if (!chartRef.current) return; + + const timer = setTimeout(() => { + const chartContainer = chartRef.current; + if (!chartContainer) return; + + const elements = Array.from(chartContainer.querySelectorAll("*")) + .filter((el) => { + const tagName = el.tagName.toLowerCase(); + return !["script", "style", "meta", "link", "head"].includes(tagName); + }) + .map((el, index) => { + const tagName = el.tagName.toLowerCase(); + const className = typeof el.className === "string" ? el.className : ""; + const textContent = el.textContent?.trim() || ""; + + let selector = tagName; + + if (className && typeof className === "string") { + const classList = className.split(/\s+/).filter((c) => c.length > 0); + if (classList.length > 0) { + selector += "." + classList.join("."); + } + } + + if (!className || className.trim() === "") { + const parent = el.parentElement; + if (parent) { + const siblings = Array.from(parent.children).filter( + (child) => child.tagName.toLowerCase() === tagName + ); + const position = siblings.indexOf(el) + 1; + selector += `:nth-of-type(${position})`; + } + } + + return { + tagName, + className, + textContent, + selector, + }; + }); + + setChartElements(elements); + }, 300); + + return () => clearTimeout(timer); + }, [selectedChartId]); + + const applyStyles = () => { + if (!selectedElementToStyle || !chartRef.current) return; + + const element = chartRef.current.querySelector(selectedElementToStyle); + if (!element) return; + + const elementToStyle = element as HTMLElement; + + if (selectedFont !== "drop down") { + elementToStyle.style.fontFamily = selectedFont; + } + if (selectedSize !== "drop down") { + elementToStyle.style.fontSize = selectedSize; + } + if (selectedWeight !== "drop down") { + elementToStyle.style.fontWeight = selectedWeight.toLowerCase(); + } + if (elementColor) { + elementToStyle.style.color = elementColor; + } + }; + + useEffect(() => { + applyStyles(); + }, [selectedFont, selectedSize, selectedWeight, elementColor, selectedElementToStyle]); - // Handle Widget Updates const handleUpdateWidget = (updatedProperties: Partial<Widget>) => { if (!selectedChartId) return; - // Update the selectedChartId const updatedChartId = { ...selectedChartId, ...updatedProperties, }; setSelectedChartId(updatedChartId); - // Update the widgets array const updatedWidgets = widgets.map((widget) => - widget.id === selectedChartId.id - ? { ...widget, ...updatedProperties } - : widget + widget.id === selectedChartId.id ? { ...widget, ...updatedProperties } : widget ); setWidgets(updatedWidgets); }; - // Default Chart Data + const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => { + const newName = e.target.value; + setNameInput(newName); + + if (selectedChartId?.title) { + handleUpdateWidget({ title: newName }); + } else if (selectedChartId?.header) { + handleUpdateWidget({ header: newName }); + } + }; + const defaultChartData = { labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"], datasets: [ { data: [65, 59, 80, 81, 56, 55, 40], - backgroundColor: elementColor, // Default background color - borderColor: "#ffffff", // Default border color + backgroundColor: elementColor, + borderColor: "#ffffff", borderWidth: 1, }, ], }; + const elementOptions = chartElements.map((el) => { + let displayName = el.tagName; + if (el.className) displayName += `.${el.className}`; + if (el.textContent) + displayName += ` (${el.textContent.substring(0, 20)}${ + el.textContent.length > 20 ? "..." : "" + })`; + return { + display: displayName, + value: el.selector, + }; + }); + return ( <div className="design"> - {/* Title of the Selected Widget */} <div className="selectedWidget"> - {selectedChartId?.title || "Widget 1"} + {selectedChartId?.title || selectedChartId?.header || "Widget 1"} </div> - {/* Chart Component */} - <div className="reviewChart"> - {selectedChartId && ( + <div className="reviewChart" ref={chartRef}> + {selectedChartId?.title ? ( <ChartComponent - type={selectedChartId.type} + type={selectedChartId.type || "bar"} title={selectedChartId.title} - data={selectedChartId.data || defaultChartData} // Use widget data or default + data={selectedChartId.data || defaultChartData} + /> + ) : ( + <SimpleCard + header={selectedChartId?.header || ""} + icon={WalletIcon} + value={selectedChartId?.value || ""} + per={selectedChartId?.per || ""} /> )} </div> - {/* Options Container */} <div className="optionsContainer"> - {/* Name Dropdown */} + <div className="option"> + <span>Element to Style</span> + <RegularDropDown + header={selectedElementToStyle || "Select Element"} + options={ + elementOptions.length > 0 + ? elementOptions.map((opt) => opt.display) + : ["No elements found"] + } + onSelect={(value) => { + const selected = elementOptions.find( + (opt) => opt.display === value + ); + setSelectedElementToStyle(selected?.value || null); + }} + /> + </div> + <div className="option"> <span>Name</span> - <RegularDropDown - header={selectedChartId?.title || "Select Name"} - options={["Option 1", "Option 2", "Option 3"]} - onSelect={(value) => { - setSelectedName(value); - handleUpdateWidget({ title: value }); - }} + <input + type="text" + value={nameInput} + onChange={handleNameChange} + placeholder="Enter name" /> </div> - {/* Element Dropdown */} - <div className="option"> - <span>Element</span> - <RegularDropDown - header={selectedChartId?.type || "Select Element"} - options={["bar", "line", "pie", "doughnut", "radar", "polarArea"]} // Valid chart types - onSelect={(value) => { - setSelectedElement(value); - handleUpdateWidget({ type: value }); - }} - /> - </div> + {selectedChartId?.title && ( + <div className="option"> + <span>Chart Type</span> + <RegularDropDown + header={selectedChartId?.type || "Select Type"} + options={["bar", "line", "pie", "doughnut", "radar", "polarArea"]} + onSelect={(value) => { + handleUpdateWidget({ type: value }); + }} + /> + </div> + )} - {/* Font Family Dropdown */} <div className="option"> <span>Font Family</span> <RegularDropDown header={selectedChartId?.fontFamily || "Select Font"} options={["Arial", "Roboto", "Sans-serif"]} - onSelect={(value) => { - setSelectedFont(value); - handleUpdateWidget({ fontFamily: value }); - }} + onSelect={(value) => setSelectedFont(value)} /> </div> - {/* Size Dropdown */} <div className="option"> <span>Size</span> <RegularDropDown header={selectedChartId?.fontSize || "Select Size"} options={["12px", "14px", "16px", "18px"]} - onSelect={(value) => { - setSelectedSize(value); - handleUpdateWidget({ fontSize: value }); - }} + onSelect={(value) => setSelectedSize(value)} /> </div> - {/* Weight Dropdown */} <div className="option"> <span>Weight</span> <RegularDropDown header={selectedChartId?.fontWeight || "Select Weight"} options={["Light", "Regular", "Bold"]} - onSelect={(value) => { - setSelectedWeight(value); - handleUpdateWidget({ fontWeight: value }); - }} + onSelect={(value) => setSelectedWeight(value)} /> </div> - {/* Element Color Picker */} <div className="option"> <div className="header" onClick={() => setShowColorPicker((prev) => !prev)} > <span>Element Color</span> - <div className="icon">▾</div>{" "} - {/* Change icon based on the visibility */} + <div className="icon">▾</div> </div> - {/* Show color picker only when 'showColorPicker' is true */} {showColorPicker && ( <div className="colorDisplayer"> <input @@ -183,20 +287,21 @@ const Design = () => { value={elementColor} onChange={(e) => { setElementColor(e.target.value); - handleUpdateWidget({ - data: { - labels: selectedChartId?.data?.labels || [], - datasets: [ - { - ...selectedChartId?.data?.datasets[0], - backgroundColor: e.target.value, // Update the element color - }, - ], - }, - }); + if (selectedChartId?.data) { + handleUpdateWidget({ + data: { + ...selectedChartId.data, + datasets: [ + { + ...selectedChartId.data.datasets[0], + backgroundColor: e.target.value, + }, + ], + }, + }); + } }} /> - {/* Display the selected color value */} <span style={{ marginLeft: "10px" }}>{elementColor}</span> </div> )} @@ -206,4 +311,4 @@ const Design = () => { ); }; -export default Design; +export default Design; \ No newline at end of file diff --git a/app/src/components/ui/componets/Panel.tsx b/app/src/components/ui/componets/Panel.tsx index 7d34c3f..8ec26a0 100644 --- a/app/src/components/ui/componets/Panel.tsx +++ b/app/src/components/ui/componets/Panel.tsx @@ -124,8 +124,8 @@ const Panel: React.FC<PanelProps> = ({ selectedZone.widgets.filter((w) => w.panel === panel).length; const calculatePanelCapacity = (panel: Side) => { - const CHART_WIDTH = 150; - const CHART_HEIGHT = 150; + const CHART_WIDTH = 170; + const CHART_HEIGHT = 170; const FALLBACK_HORIZONTAL_CAPACITY = 5; const FALLBACK_VERTICAL_CAPACITY = 3; diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index 619cbb0..5cb8867 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -231,7 +231,7 @@ const RealTimeVisulization: React.FC = () => { onDrop={(event) => handleDrop(event)} onDragOver={(event) => event.preventDefault()} > - <Scene /> + {/* <Scene /> */} </div> {activeModule === "visualization" && selectedZone.zoneName !== "" && <DroppedObjects />} {activeModule === "visualization" && <SocketRealTimeViz />} diff --git a/app/src/components/ui/componets/functions/determinePosition.ts b/app/src/components/ui/componets/functions/determinePosition.ts index 5e3db3f..325ad14 100644 --- a/app/src/components/ui/componets/functions/determinePosition.ts +++ b/app/src/components/ui/componets/functions/determinePosition.ts @@ -1,4 +1,3 @@ - export function determinePosition( canvasRect: DOMRect, relativeX: number, @@ -12,6 +11,23 @@ export function determinePosition( const centerX = canvasRect.width / 2; const centerY = canvasRect.height / 2; + // Define a threshold for considering a point as "centered" + const centerThreshold = 10; // Adjust this value as needed + + // Check if the point is within the center threshold + const isCenterX = Math.abs(relativeX - centerX) <= centerThreshold; + const isCenterY = Math.abs(relativeY - centerY) <= centerThreshold; + + // If the point is centered, return a special "centered" position + if (isCenterX && isCenterY) { + return { + top: "auto", + left: "auto", + right: "auto", + bottom: "auto", + }; + } + let position: { top: number | "auto"; left: number | "auto"; @@ -21,7 +37,7 @@ export function determinePosition( if (relativeY < centerY) { if (relativeX < centerX) { - + // Top-left quadrant position = { top: relativeY - 41.5, left: relativeX - 125, @@ -29,7 +45,7 @@ export function determinePosition( bottom: "auto", }; } else { - + // Top-right quadrant position = { top: relativeY - 41.5, right: canvasRect.width - relativeX - 125, @@ -39,7 +55,7 @@ export function determinePosition( } } else { if (relativeX < centerX) { - + // Bottom-left quadrant position = { bottom: canvasRect.height - relativeY - 41.5, left: relativeX - 125, @@ -47,7 +63,7 @@ export function determinePosition( top: "auto", }; } else { - + // Bottom-right quadrant position = { bottom: canvasRect.height - relativeY - 41.5, right: canvasRect.width - relativeX - 125, @@ -58,4 +74,4 @@ export function determinePosition( } return position; -} +} \ No newline at end of file diff --git a/app/src/components/ui/list/DropDownList.tsx b/app/src/components/ui/list/DropDownList.tsx index e77f35b..ecdf7cf 100644 --- a/app/src/components/ui/list/DropDownList.tsx +++ b/app/src/components/ui/list/DropDownList.tsx @@ -32,14 +32,22 @@ const DropDownList: React.FC<DropDownListProps> = ({ listType = "default", remove, }) => { - const [isOpen, setIsOpen] = useState<boolean>(defaultOpen); - const { zones, setZones } = useZones() + const { zones, setZones } = useZones(); const handleToggle = () => { setIsOpen((prev) => !prev); // Toggle the state }; - const [zoneDataList, setZoneDataList] = useState<{ id: string; name: string }[]>([]); + + interface Asset { + id: string; + name: string; + } + + const [zoneDataList, setZoneDataList] = useState< + { id: string; name: string; assets: Asset[] }[] + >([]); + const { selectedZone, setSelectedZone } = useSelectedZoneStore(); useEffect(() => { @@ -53,12 +61,64 @@ const DropDownList: React.FC<DropDownListProps> = ({ // { id: "70fa55cd-b5c9-4f80-a8c4-6319af3bfb4e", name: "zone6" }, // ]) - - const value = (zones || []).map((val: { zoneId: string; zoneName: string }) => ({ - id: val.zoneId, - name: val.zoneName - })); - setZoneDataList(prev => (JSON.stringify(prev) !== JSON.stringify(value) ? value : prev)); + const value = (zones || []).map( + (val: { zoneId: string; zoneName: string }) => ({ + id: val.zoneId, + name: val.zoneName, + }) + ); + setZoneDataList([ + { + id: "zone1", + name: "Zone 1", + assets: [ + { + id: "asset1", + name: "Asset 1", + }, + { + id: "asset2", + name: "Asset 2", + }, + { + id: "asset3", + name: "Asset 3", + }, + ], + }, + { + id: "zone2", + name: "Zone 2", + assets: [ + { + id: "asset4", + name: "Asset 4", + }, + { + id: "asset5", + name: "Asset 5", + }, + { + id: "asset6", + name: "Asset 6", + }, + ], + }, + { + id: "zone3", + name: "Zone 3", + assets: [ + { + id: "asset7", + name: "Asset 7", + }, + { + id: "asset8", + name: "Asset 8", + }, + ], + }, + ]); }, [zones]); return ( @@ -101,6 +161,7 @@ const DropDownList: React.FC<DropDownListProps> = ({ value="Buildings" showKebabMenu={false} showAddIcon={false} + items={zoneDataList} /> <DropDownList value="Zones" diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index 0b2e63b..7629730 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -1,20 +1,44 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import RenameInput from "../inputs/RenameInput"; -import { EyeIcon, LockIcon, RmoveIcon } from "../../icons/ExportCommonIcons"; + import { useSelectedZoneStore } from "../../../store/useZoneStore"; import { getZoneData } from "../../../services/realTimeVisulization/zoneData/getZones"; -import useModuleStore, { useSubModuleStore } from "../../../store/useModuleStore"; +import useModuleStore, { + useSubModuleStore, +} from "../../../store/useModuleStore"; +import { + ArrowIcon, + EyeIcon, + LockIcon, + RmoveIcon, +} from "../../icons/ExportCommonIcons"; + +interface Asset { + id: string; + name: string; +} + +interface ZoneItem { + id: string; + name: string; + assets?: Asset[]; +} interface ListProps { - items?: { id: string; name: string }[]; // Optional array of items to render - placeholder?: string; // Optional placeholder text + items?: ZoneItem[]; + placeholder?: string; remove?: boolean; } const List: React.FC<ListProps> = ({ items = [], remove }) => { + console.log("items: ", items); const { activeModule, setActiveModule } = useModuleStore(); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); const { setSubModule } = useSubModuleStore(); + const [expandedZones, setExpandedZones] = useState<Record<string, boolean>>( + {} + ); + useEffect(() => { useSelectedZoneStore.getState().setSelectedZone({ zoneName: "", @@ -27,11 +51,16 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => { widgets: [], }); }, [activeModule]); - + + const toggleZoneExpansion = (zoneId: string) => { + setExpandedZones((prev) => ({ + ...prev, + [zoneId]: !prev[zoneId], + })); + }; async function handleSelectZone(id: string) { try { - // Avoid re-fetching if the same zone is already selected if (selectedZone?.zoneId === id) { console.log("Zone is already selected:", selectedZone.zoneName); return; @@ -65,27 +94,74 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => { <> {items.length > 0 ? ( <ul className="list-wrapper"> - {items.map((item, index) => ( - <li key={index} className="list-container"> - <div className="list-item"> - <div className="value" onClick={() => handleSelectZone(item.id)}> - <RenameInput value={item.name} /> - </div> - <div className="options-container"> - <div className="lock option"> - <LockIcon isLocked /> - </div> - <div className="visibe option"> - <EyeIcon isClosed /> - </div> - {remove && ( - <div className="remove option"> - <RmoveIcon /> + {items.map((item) => ( + <React.Fragment key={`zone-${item.id}`}> + <li className="list-container"> + <div className="list-item"> + <div className="zone-header"> + <div + className="value" + onClick={() => handleSelectZone(item.id)} + > + <RenameInput value={item.name} /> </div> - )} + </div> + <div className="options-container"> + <div className="lock option"> + <LockIcon isLocked /> + </div> + <div className="visibe option"> + <EyeIcon isClosed /> + </div> + {remove && ( + <div className="remove option"> + <RmoveIcon /> + </div> + )} + {item.assets && item.assets.length > 0 && ( + <div + className="expand-icon option" + onClick={() => toggleZoneExpansion(item.id)} + > + <ArrowIcon /> + </div> + )} + </div> </div> - </div> - </li> + </li> + {/* Nested assets list - only shown when expanded */} + {item.assets && + item.assets.length > 0 && + expandedZones[item.id] && ( + <ul className="asset-list"> + {item.assets.map((asset) => ( + <li + key={`asset-${asset.id}`} + className="list-container asset-item" + > + <div className="list-item"> + <div className="value"> + <RenameInput value={asset.name} /> + </div> + <div className="options-container"> + <div className="lock option"> + <LockIcon isLocked /> + </div> + <div className="visibe option"> + <EyeIcon isClosed /> + </div> + {remove && ( + <div className="remove option"> + <RmoveIcon /> + </div> + )} + </div> + </div> + </li> + ))} + </ul> + )} + </React.Fragment> ))} </ul> ) : ( diff --git a/app/src/modules/visualization/handleSaveTemplate.ts b/app/src/modules/visualization/handleSaveTemplate.ts index 6a3bad4..e5f90ab 100644 --- a/app/src/modules/visualization/handleSaveTemplate.ts +++ b/app/src/modules/visualization/handleSaveTemplate.ts @@ -30,7 +30,7 @@ export const handleSaveTemplate = async ({ }: HandleSaveTemplateProps): Promise<void> => { try { // Check if the selected zone has any widgets - if (!selectedZone.widgets || selectedZone.widgets.length === 0) { + if (!selectedZone.panelOrder || selectedZone.panelOrder.length === 0) { console.warn("No widgets found in the selected zone."); return; } diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 54fe61f..88fd467 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -56,7 +56,7 @@ const Project: React.FC = () => { return ( <div className="project-main"> - {loadingProgress && <LoadingPage progress={loadingProgress} />} + {/* {loadingProgress && <LoadingPage progress={loadingProgress} />} */} {!isPlaying && ( <> {toggleThreeD && <ModuleToggle />} diff --git a/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts b/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts index 5c18031..9eae5cb 100644 --- a/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts +++ b/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts @@ -1,7 +1,7 @@ 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); + console.log('template: ', template); try { const response = await fetch(`${url_Backend_dwinzo}/api/v2/template/save`, { method: "POST", @@ -16,7 +16,7 @@ export const saveTemplateApi = async (organization: string, template: {}) => { } const result = await response.json(); - console.log('result: ', result); + return result; } catch (error) { if (error instanceof Error) { diff --git a/app/src/styles/components/lists.scss b/app/src/styles/components/lists.scss index a9b0145..f396a50 100644 --- a/app/src/styles/components/lists.scss +++ b/app/src/styles/components/lists.scss @@ -3,15 +3,23 @@ .dropdown-list-container { border-bottom: 1px solid var(--border-color); + + .lists-container { + margin-bottom: 6px; + } + &:last-child { border: none; } + .head { @include flex-space-between; padding: 6px 12px; + .options { @include flex-center; gap: 6px; + .option { @include flex-center; cursor: pointer; @@ -22,34 +30,52 @@ } .list-wrapper { + + .no-item { padding: 12px; } + .list-container { padding: 2px; + margin-left: 10px; + overflow: hidden; + + .list-item { @include flex-space-between; width: 100%; text-align: center; - padding: 4px 12px; + padding: 4px 8px; border-radius: #{$border-radius-large}; + .value { width: 100%; text-align: start; max-width: 180px; } + .options-container { @include flex-center; gap: 6px; + .option { @include flex-center; cursor: pointer; } } } + .active { background-color: var(--highlight-accent-color); color: var(--primary-color); } } -} + + .asset-list { + border-left: 2px solid var(--border-color); + + margin-left: 20px + } + +} \ No newline at end of file diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index 63297c9..d4cf595 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -253,6 +253,7 @@ .user-profile-container { display: flex; + .user-profile { background: var(--accent-color); color: var(--primary-color); @@ -483,6 +484,9 @@ height: 150px; background: #f0f0f0; // border-radius: 8px; + display: flex; + align-items: center; + // justify-content: center; } .optionsContainer { @@ -497,7 +501,8 @@ justify-content: space-between; gap: 12px; - .regularDropdown-container { + .regularDropdown-container, + input { width: 160px; } @@ -686,6 +691,7 @@ font-weight: var(--font-weight-regular); padding: 8px 0; } + .input-toggle-container { padding: 0; margin-bottom: 6px; @@ -1009,11 +1015,9 @@ top: 50%; right: -10px; transform: translate(0, -50%); - background: linear-gradient( - 144.19deg, - #f1e7cd 16.62%, - #fffaef 85.81% - ); + background: linear-gradient(144.19deg, + #f1e7cd 16.62%, + #fffaef 85.81%); } .category-image { @@ -1117,11 +1121,13 @@ } } } + .assets-result { width: 100%; height: 100%; margin: 8px 10px; + .assets-wrapper { margin: 0; } -} +} \ No newline at end of file diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index e64ec08..fd42be0 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -61,7 +61,7 @@ border-radius: 8px; max-width: 80%; overflow: auto; - // max-width: calc(100% - 450px); + max-width: calc(100% - 500px); &::-webkit-scrollbar { display: none; @@ -170,10 +170,12 @@ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; border-radius: 6px; - overflow: visible !important; + overflow: auto; z-index: $z-index-tools; overflow: auto; - + &::-webkit-scrollbar { + display: none; + } .panel-content { position: relative; height: 100%; @@ -319,6 +321,7 @@ right: 0; top: 0; bottom: 0; + } } @@ -715,6 +718,13 @@ z-index: 2 !important; } +.connectionSuccess { + outline-color: #43C06D; +} + +.connectionFails { + outline-color: #ffe3e0; +} .editWidgetOptions-wrapper {