From a73c893040129f9bb11d4bf6f98da694e2eb49ab Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Thu, 27 Mar 2025 10:54:40 +0530 Subject: [PATCH 1/5] Added kebab for delete widgets --- .../components/icons/ExportCommonIcons.tsx | 96 +++++++ .../visualization/Visualization.tsx | 2 +- .../ui/charts/PieGraphComponent.tsx | 1 - .../components/ui/componets/DisplayZone.tsx | 242 +++++++++--------- .../ui/componets/DraggableWidget.tsx | 148 ++++++++++- app/src/components/ui/componets/Panel.tsx | 27 +- .../ui/componets/RealTimeVisulization.tsx | 92 ++++++- app/src/components/ui/list/DropDownList.tsx | 2 +- app/src/components/ui/list/List.tsx | 1 - .../visualization/ui/styledWidgets.scss | 2 +- app/src/styles/pages/realTimeViz.scss | 122 ++++++++- 11 files changed, 580 insertions(+), 155 deletions(-) diff --git a/app/src/components/icons/ExportCommonIcons.tsx b/app/src/components/icons/ExportCommonIcons.tsx index dd170f8..4386198 100644 --- a/app/src/components/icons/ExportCommonIcons.tsx +++ b/app/src/components/icons/ExportCommonIcons.tsx @@ -511,3 +511,99 @@ export function AI_Icon() { ); } + +export const KebabIcon = () => { + return ( + + + + + + ); +}; + +export const DublicateIcon = () => { + return ( + + + + ); +}; + +export const DeleteIcon = () => { + return ( + + + + + + + + ); +}; diff --git a/app/src/components/layout/sidebarRight/visualization/Visualization.tsx b/app/src/components/layout/sidebarRight/visualization/Visualization.tsx index ec4d3ad..c2ac477 100644 --- a/app/src/components/layout/sidebarRight/visualization/Visualization.tsx +++ b/app/src/components/layout/sidebarRight/visualization/Visualization.tsx @@ -13,7 +13,7 @@ const Visualization = () => { return (
diff --git a/app/src/components/ui/charts/PieGraphComponent.tsx b/app/src/components/ui/charts/PieGraphComponent.tsx index 0066ec3..7c8bb1d 100644 --- a/app/src/components/ui/charts/PieGraphComponent.tsx +++ b/app/src/components/ui/charts/PieGraphComponent.tsx @@ -53,7 +53,6 @@ const PieChartComponent = ({ .getPropertyValue("--accent-color") .trim(); - console.log("accentColor: ", accentColor); const options = useMemo( () => ({ responsive: true, diff --git a/app/src/components/ui/componets/DisplayZone.tsx b/app/src/components/ui/componets/DisplayZone.tsx index e699f27..ea6f62a 100644 --- a/app/src/components/ui/componets/DisplayZone.tsx +++ b/app/src/components/ui/componets/DisplayZone.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useRef, useState, useCallback } from "react"; import { Widget } from "../../../store/useWidgetStore"; // Define the type for `Side` @@ -51,135 +51,139 @@ const DisplayZone: React.FC = ({ // Ref for the container element const containerRef = useRef(null); - // Example state for selectedOption and options (adjust based on your actual use case) - const [selectedOption, setSelectedOption] = React.useState( - null - ); - // console.log('setSelectedOption: ', setSelectedOption); - const [options, setOptions] = React.useState([]); - // console.log('setOptions: ', setOptions); + // State to track overflow visibility + const [showLeftArrow, setShowLeftArrow] = useState(false); + const [showRightArrow, setShowRightArrow] = useState(false); - // Scroll to the selected option when it changes - useEffect(() => { + // Function to calculate overflow state + const updateOverflowState = useCallback(() => { const container = containerRef.current; - if (container && selectedOption) { - // Handle scrolling to the selected option - const index = options.findIndex((option) => { - const formattedOption = formatOptionName(option); - const selectedFormattedOption = - selectedOption?.split("_")[1] || selectedOption; - return formattedOption === selectedFormattedOption; - }); - - if (index !== -1) { - const optionElement = container.children[index] as HTMLElement; - if (optionElement) { - optionElement.scrollIntoView({ - behavior: "smooth", - block: "nearest", - inline: "center", - }); - } - } - } - }, [selectedOption, options]); - - useEffect(() => { - const container = containerRef.current; - - const handleWheel = (event: WheelEvent) => { - event.preventDefault(); - if (container) { - container.scrollBy({ - left: event.deltaY * 2, // Adjust the multiplier for faster scrolling - behavior: "smooth", - }); - } - }; - - let isDragging = false; - let startX: number; - let scrollLeft: number; - - const handleMouseDown = (event: MouseEvent) => { - isDragging = true; - startX = event.pageX - (container?.offsetLeft || 0); - scrollLeft = container?.scrollLeft || 0; - }; - - const handleMouseMove = (event: MouseEvent) => { - if (!isDragging || !container) return; - event.preventDefault(); - const x = event.pageX - (container.offsetLeft || 0); - const walk = (x - startX) * 2; // Adjust the multiplier for faster dragging - container.scrollLeft = scrollLeft - walk; - }; - - const handleMouseUp = () => { - isDragging = false; - }; - - const handleMouseLeave = () => { - isDragging = false; - }; - if (container) { - container.addEventListener("wheel", handleWheel, { passive: false }); - container.addEventListener("mousedown", handleMouseDown); - container.addEventListener("mousemove", handleMouseMove); - container.addEventListener("mouseup", handleMouseUp); - container.addEventListener("mouseleave", handleMouseLeave); - } + const isOverflowing = container.scrollWidth > container.clientWidth; + const canScrollLeft = container.scrollLeft > 0; + const canScrollRight = + container.scrollLeft + container.clientWidth < container.scrollWidth; - return () => { - if (container) { - container.removeEventListener("wheel", handleWheel); - container.removeEventListener("mousedown", handleMouseDown); - container.removeEventListener("mousemove", handleMouseMove); - container.removeEventListener("mouseup", handleMouseUp); - container.removeEventListener("mouseleave", handleMouseLeave); - } - }; + console.log("isOverflowing:", isOverflowing); + console.log("canScrollLeft:", canScrollLeft); + console.log("canScrollRight:", canScrollRight); + + setShowLeftArrow(isOverflowing && canScrollLeft); + setShowRightArrow(isOverflowing && canScrollRight); + } }, []); - // Helper function to format option names (customize as needed) - const formatOptionName = (option: string): string => { - // Replace underscores with spaces and capitalize the first letter - return option.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase()); + useEffect(() => { + const container = containerRef.current; + + if (container) { + // Initial calculation after the DOM has been rendered + const handleInitialRender = () => { + requestAnimationFrame(updateOverflowState); + }; + + handleInitialRender(); + + // Update on window resize or scroll + const handleResize = () => updateOverflowState(); + const handleScroll = () => updateOverflowState(); + + // Add mouse wheel listener for horizontal scrolling + const handleWheel = (event: WheelEvent) => { + event.preventDefault(); // Prevent default vertical scrolling + if (container) { + container.scrollBy({ + left: event.deltaY * 2, // Translate vertical scroll to horizontal scroll + behavior: "smooth", + }); + } + }; + + container.addEventListener("scroll", handleScroll); + window.addEventListener("resize", handleResize); + container.addEventListener("wheel", handleWheel, { passive: false }); + + return () => { + container.removeEventListener("scroll", handleScroll); + window.removeEventListener("resize", handleResize); + container.removeEventListener("wheel", handleWheel); + }; + } + }, [updateOverflowState]); + + // Handle scrolling with navigation arrows + const handleScrollLeft = () => { + const container = containerRef.current; + if (container) { + container.scrollBy({ + left: -200, // Scroll left by 200px + behavior: "smooth", + }); + } + }; + + const handleScrollRight = () => { + const container = containerRef.current; + if (container) { + container.scrollBy({ + left: 200, // Scroll right by 200px + behavior: "smooth", + }); + } }; return ( -
- {Object.keys(zonesData).map((zoneName, index) => ( -
{ - console.log('zoneName: ', zoneName); +
+ {/* Left Arrow */} + {showLeftArrow && ( + + )} - setSelectedZone({ - zoneName, - activeSides: zonesData[zoneName].activeSides || [], - panelOrder: zonesData[zoneName].panelOrder || [], - lockedPanels: zonesData[zoneName].lockedPanels || [], - widgets: zonesData[zoneName].widgets || [], - }) - // setSelectedZone({ - // zoneName, - // ...zonesData[zoneName], - // }); - console.log(selectedZone); - }} - > - {zoneName} -
- ))} + {/* Zones Wrapper */} +
+ {Object.keys(zonesData).map((zoneName, index) => ( +
{ + console.log("zoneName: ", zoneName); + + setSelectedZone({ + zoneName, + activeSides: zonesData[zoneName].activeSides || [], + panelOrder: zonesData[zoneName].panelOrder || [], + lockedPanels: zonesData[zoneName].lockedPanels || [], + widgets: zonesData[zoneName].widgets || [], + }); + }} + > + {zoneName} +
+ ))} +
+ + {/* Right Arrow */} + {showRightArrow && ( + + )}
); }; diff --git a/app/src/components/ui/componets/DraggableWidget.tsx b/app/src/components/ui/componets/DraggableWidget.tsx index 1157292..d25c8ec 100644 --- a/app/src/components/ui/componets/DraggableWidget.tsx +++ b/app/src/components/ui/componets/DraggableWidget.tsx @@ -3,20 +3,58 @@ import ProgressCard from "../realTimeVis/charts/ProgressCard"; import PieGraphComponent from "../realTimeVis/charts/PieGraphComponent"; import BarGraphComponent from "../realTimeVis/charts/BarGraphComponent"; import LineGraphComponent from "../realTimeVis/charts/LineGraphComponent"; -import RadarGraphComponent from "../realTimeVis/charts/RadarGraphComponent"; import DoughnutGraphComponent from "../realTimeVis/charts/DoughnutGraphComponent"; import PolarAreaGraphComponent from "../realTimeVis/charts/PolarAreaGraphComponent"; +import { + DeleteIcon, + DublicateIcon, + KebabIcon, +} from "../../icons/ExportCommonIcons"; +import { useEffect, useRef } from "react"; + +type Side = "top" | "bottom" | "left" | "right"; + +interface Widget { + id: string; + type: string; + title: string; + panel: Side; + data: any; +} export const DraggableWidget = ({ widget, hiddenPanels, // Add this prop to track hidden panels - index, onReorder + index, + onReorder, + openKebabId, + setOpenKebabId, + selectedZone, + setSelectedZone, }: { + selectedZone: { + zoneName: string; + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: Widget[]; + }; + setSelectedZone: React.Dispatch< + React.SetStateAction<{ + zoneName: string; + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: Widget[]; + }> + >; widget: any; hiddenPanels: string[]; // Array of hidden panel names - index: number; onReorder: (fromIndex: number, toIndex: number) => void + index: number; + onReorder: (fromIndex: number, toIndex: number) => void; + openKebabId: string | null; // ID of the widget with an open kebab menu + setOpenKebabId: (id: string | null) => void; // Function to update the open kebab menu }) => { - const { selectedChartId, setSelectedChartId } = useWidgetStore(); const handlePointerDown = () => { @@ -29,7 +67,7 @@ export const DraggableWidget = ({ const isPanelHidden = hiddenPanels.includes(widget.panel); const handleDragStart = (event: React.DragEvent) => { - event.dataTransfer.setData('text/plain', index.toString()); // Store the index of the dragged widget + event.dataTransfer.setData("text/plain", index.toString()); // Store the index of the dragged widget }; const handleDragEnter = (event: React.DragEvent) => { event.preventDefault(); // Allow drop @@ -41,21 +79,89 @@ export const DraggableWidget = ({ const handleDrop = (event: React.DragEvent) => { event.preventDefault(); - const fromIndex = parseInt(event.dataTransfer.getData('text/plain'), 10); // Get the dragged widget's index + const fromIndex = parseInt(event.dataTransfer.getData("text/plain"), 10); // Get the dragged widget's index const toIndex = index; // The index of the widget where the drop occurred if (fromIndex !== toIndex) { onReorder(fromIndex, toIndex); // Call the reorder function passed as a prop } }; + // Handle kebab icon click to toggle kebab options + const handleKebabClick = (event: React.MouseEvent) => { + event.stopPropagation(); // Prevent click from propagating to parent elements + if (openKebabId === widget.id) { + // If the current kebab is already open, close it + setOpenKebabId(null); + } else { + // Open the kebab for the clicked widget and close others + setOpenKebabId(widget.id); + } + }; + + const deleteSelectedChart = () => { + // Filter out the widget to be deleted + const updatedWidgets = selectedZone.widgets.filter( + (w: Widget) => w.id !== widget.id + ); + + // Update the selectedZone state + setSelectedZone((prevZone: any) => ({ + ...prevZone, + widgets: updatedWidgets, // Replace the widgets array with the updated one + })); + + // Close the kebab menu after deletion + setOpenKebabId(null); + }; + const widgetRef = useRef(null); + + // Handle click outside to close the kebab menu + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + widgetRef.current && + !widgetRef.current.contains(event.target as Node) + ) { + setOpenKebabId(null); // Close the kebab menu if the click is outside + } + }; + + document.addEventListener("mousedown", handleClickOutside); + + // Cleanup event listener on component unmount + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [setOpenKebabId]); + const duplicateWidget = () => { + // Create a copy of the current widget with a new unique ID + const duplicatedWidget: Widget = { + ...widget, + id: `${widget.id}-copy-${Date.now()}`, // Generate a unique ID using a timestamp + }; + + // Add the duplicated widget to the selectedZone's widgets array + setSelectedZone((prevZone: any) => ({ + ...prevZone, + widgets: [...prevZone.widgets, duplicatedWidget], + })); + + // Close the kebab menu after duplication + setOpenKebabId(null); + + console.log("Duplicated widget with ID:", duplicatedWidget.id); + }; + + return ( <> @@ -241,5 +248,3 @@ const Panel: React.FC = ({ }; export default Panel; - - diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index 385ae5d..f80b9ae 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -8,7 +8,6 @@ import Scene from "../../../modules/scene/scene"; import useModuleStore from "../../../store/useModuleStore"; import { getZonesApi } from "../../../services/realTimeVisulization/zoneData/getZones"; - type Side = "top" | "bottom" | "left" | "right"; type FormattedZoneData = Record< @@ -41,7 +40,87 @@ const RealTimeVisulization: React.FC = () => { const { isPlaying } = usePlayButtonStore(); const { activeModule } = useModuleStore(); - const [zonesData, setZonesData] = useState({}); + const [zonesData, setZonesData] = useState<{ + [key: string]: { + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: Widget[]; + }; + }>({ + "Manufacturing unit": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Assembly unit": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Packing unit": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + Warehouse: { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + Inventory: { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 1": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 2": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 3": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 4": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 5": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 6": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 7": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + }); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); // useEffect(() => { @@ -73,10 +152,7 @@ const RealTimeVisulization: React.FC = () => { // GetZoneData(); // }, []); - useEffect(() => { - - console.log('zonesData: ', zonesData); - }, [zonesData]); + useEffect(() => {}, [zonesData]); // useEffect(() => { // setZonesData((prev) => { @@ -111,7 +187,8 @@ const RealTimeVisulization: React.FC = () => { style={{ height: "100%", width: "100%", - borderRadius: isPlaying || activeModule !== "visualization" ? "" : "6px", + borderRadius: + isPlaying || activeModule !== "visualization" ? "" : "6px", }} > @@ -137,6 +214,7 @@ const RealTimeVisulization: React.FC = () => { selectedZone={selectedZone} setSelectedZone={setSelectedZone} hiddenPanels={hiddenPanels} + setZonesData={setZonesData} /> )} diff --git a/app/src/components/ui/list/DropDownList.tsx b/app/src/components/ui/list/DropDownList.tsx index a61054b..98c357e 100644 --- a/app/src/components/ui/list/DropDownList.tsx +++ b/app/src/components/ui/list/DropDownList.tsx @@ -39,7 +39,7 @@ const DropDownList: React.FC = ({ useEffect(() => { async function GetZoneData() { const response = await getZonesApi("hexrfactory") - console.log('response: ', response.data); + setZoneDataList([{ id: "1", name: "zone1" }, { id: "2", name: "Zone 2" },]) } diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index bdf4749..1bf326f 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -8,7 +8,6 @@ interface ListProps { } const List: React.FC = ({ items = [] }) => { - console.log('items: ', items); return ( <> {items.length > 0 ? ( diff --git a/app/src/styles/components/visualization/ui/styledWidgets.scss b/app/src/styles/components/visualization/ui/styledWidgets.scss index 6006308..9c72fae 100644 --- a/app/src/styles/components/visualization/ui/styledWidgets.scss +++ b/app/src/styles/components/visualization/ui/styledWidgets.scss @@ -8,7 +8,7 @@ flex-direction: column; gap: 6px; width: 100%; - min-width: 250px; + // min-width: 1450px; .header { display: flex; justify-content: center; diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index f4c9ab9..29650e1 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -1,4 +1,5 @@ @use "../abstracts/variables.scss" as *; +@use "../abstracts/mixins.scss" as *; // Main Container .realTime-viz { @@ -43,15 +44,28 @@ left: 50%; transform: translate(-50%, 0); gap: 6px; - padding: 4px; + border-radius: 8px; max-width: 80%; overflow: auto; - max-width: calc(100% - 450px); + // max-width: calc(100% - 450px); + &::-webkit-scrollbar { display: none; } + .arrow { + background-color: var(--accent-color); + color: var(--background-color); + } + + .zones-wrapper { + &::-webkit-scrollbar { + display: none; + } + + } + .zone { width: auto; background-color: var(--background-color); @@ -155,7 +169,8 @@ position: relative; height: 100%; padding: 10px; - overflow: auto; + + display: flex; flex-direction: column; gap: 10px; @@ -176,6 +191,65 @@ box-shadow: 0px 2px 6px 0px rgba(60, 60, 67, 0.1); padding: 6px 0; background-color: white; + position: relative; + + .kebab { + width: 30px; + height: 30px; + position: absolute; + top: 0px; + right: 0px; + z-index: 10; + + @include flex-center; + } + + .kebab-options { + position: absolute; + top: 12px; + right: -100px; + 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); + + .label { + &:hover { + 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); + } + } + } + } + } } .close-btn { @@ -236,11 +310,12 @@ } } -.playingFlase{ - .zoon-wrapper{ +.playingFlase { + .zoon-wrapper { bottom: 300px !important; } } + // Side Buttons .side-button-container { position: absolute; @@ -401,3 +476,40 @@ } } } + + + + + +.arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + background-color: rgba(0, 0, 0, 0.5); + color: white; + border: none; + cursor: pointer; + font-size: 20px; + padding: 6px; + z-index: 10; +} + +.left-arrow { + left: 0; +} + +.right-arrow { + right: 0; +} + +.zone { + padding: 10px; + border: 1px solid #ccc; + border-radius: 5px; + cursor: pointer; +} + +.zone.active { + background-color: #007bff; + color: white; +} \ No newline at end of file From 68211c277bace1fc7dd2d427d76a844eeda1f696 Mon Sep 17 00:00:00 2001 From: Vishnu Date: Thu, 27 Mar 2025 10:55:44 +0530 Subject: [PATCH 2/5] simulation slider --- app/src/components/icons/SimulationIcons.tsx | 73 ++++++++++ app/src/components/ui/Tools.tsx | 10 +- .../ui/simulation/simulationPlayer.tsx | 133 ++++++++++++++++++ app/src/pages/Project.tsx | 2 + .../components/simulation/simulation.scss | 114 +++++++++++++++ app/src/styles/main.scss | 5 +- 6 files changed, 332 insertions(+), 5 deletions(-) create mode 100644 app/src/components/ui/simulation/simulationPlayer.tsx create mode 100644 app/src/styles/components/simulation/simulation.scss diff --git a/app/src/components/icons/SimulationIcons.tsx b/app/src/components/icons/SimulationIcons.tsx index 5bc3296..e3c4540 100644 --- a/app/src/components/icons/SimulationIcons.tsx +++ b/app/src/components/icons/SimulationIcons.tsx @@ -93,3 +93,76 @@ export function SimulationIcon({ isActive }: { isActive: boolean }) { ); } + +// simulation player icons + +export function ResetIcon() { + return ( + + + + + + ); +} + +export function PlayStopIcon() { + return ( + + + + ); +} + +export function ExitIcon() { + return ( + + + + ); +} diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index 9d06df9..abe7ba8 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -285,9 +285,13 @@ const Tools: React.FC = () => {
) : ( -
setIsPlaying(false)}> - X -
+ <> + {activeModule !== "simulation" && ( +
setIsPlaying(false)}> + X +
+ )} + )} ); diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx new file mode 100644 index 0000000..85f2c54 --- /dev/null +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -0,0 +1,133 @@ +import React, { useState, useRef, useEffect } from "react"; +import { ExitIcon, PlayStopIcon, ResetIcon } from "../../icons/SimulationIcons"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; + +const SimulationPlayer: React.FC = () => { + const [speed, setSpeed] = useState(1); + const [playSimulation, setPlaySimulation] = useState(false); + const { setIsPlaying } = usePlayButtonStore(); + const sliderRef = useRef(null); + const isDragging = useRef(false); + + // Button functions + const handleReset = () => { + setSpeed(1); + }; + const handlePlayStop = () => { + setPlaySimulation(!playSimulation); + }; + const handleExit = () => { + setPlaySimulation(false); + setIsPlaying(false); + }; + + // Slider functions starts + const handleSpeedChange = (event: React.ChangeEvent) => { + setSpeed(parseFloat(event.target.value)); + }; + + const calculateHandlePosition = () => { + return ((speed - 0.5) / (50 - 0.5)) * 100; + }; + + const handleMouseDown = () => { + isDragging.current = true; + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + }; + + const handleMouseMove = (e: MouseEvent) => { + if (!isDragging.current || !sliderRef.current) return; + + const sliderRect = sliderRef.current.getBoundingClientRect(); + const offsetX = e.clientX - sliderRect.left; + const percentage = Math.min(Math.max(offsetX / sliderRect.width, 0), 1); + const newValue = 0.5 + percentage * (50 - 0.5); + setSpeed(parseFloat(newValue.toFixed(1))); + }; + + const handleMouseUp = () => { + isDragging.current = false; + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + useEffect(() => { + return () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + }, []); + // Slider function ends + + return ( +
+
+
+
{ + handleReset(); + }} + > + + Reset +
+
{ + handlePlayStop(); + }} + > + + {playSimulation ? "Play" : "Stop"} +
+
{ + handleExit(); + }} + > + + Exit +
+
+
+
0.5x
+
+
+
+
+
+
+
+
+
+
+
+
+ {speed.toFixed(1)}x +
+ +
+
+
50x
+
+
+
+ ); +}; + +export default SimulationPlayer; diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 95da878..ade4d6f 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -18,6 +18,7 @@ import { useNavigate } from "react-router-dom"; import { usePlayButtonStore } from "../store/usePlayButtonStore"; import SimulationUI from "../modules/simulation/simulationUI"; import MarketPlace from "../modules/market/MarketPlace"; +import SimulationPlayer from "../components/ui/simulation/simulationPlayer"; const Project: React.FC = () => { let navigate = useNavigate(); @@ -61,6 +62,7 @@ const Project: React.FC = () => { {activeModule !== "market" && } {/* */} + {isPlaying && activeModule === "simulation" && }
); }; diff --git a/app/src/styles/components/simulation/simulation.scss b/app/src/styles/components/simulation/simulation.scss new file mode 100644 index 0000000..eb109c2 --- /dev/null +++ b/app/src/styles/components/simulation/simulation.scss @@ -0,0 +1,114 @@ +@use "../../abstracts/variables" as *; +@use "../../abstracts/mixins" as *; + +.simulation-player-wrapper { + position: fixed; + bottom: 32px; + left: 50%; + z-index: 2; + transform: translate(-50%, 0); + .simulation-player-container { + .controls-container { + @include flex-center; + gap: 12px; + margin-bottom: 4px; + .simulation-button-container { + @include flex-center; + gap: 2px; + padding: 6px 8px; + min-width: 64px; + background-color: var(--background-color); + border-radius: #{$border-radius-small}; + cursor: pointer; + &:hover { + background-color: var(--highlight-accent-color); + color: var(--accent-color); + path { + stroke: var(--accent-color); + } + } + } + } + .speed-control-container { + @include flex-center; + gap: 18px; + padding: 5px 16px; + background: var(--background-color); + border-radius: #{$border-radius-medium}; + box-sizing: #{$box-shadow-medium}; + .min-value, + .max-value { + font-weight: var(--font-weight-bold); + } + .slider-container { + width: 580px; + max-width: 80vw; + height: 28px; + background: var(--background-color-gray); + border-radius: #{$border-radius-small}; + position: relative; + padding: 4px 26px; + .custom-slider { + height: 100%; + width: 100%; + position: relative; + .slider-input { + position: absolute; + width: 100%; + height: 100%; + opacity: 0; + z-index: 3; + cursor: pointer; + } + .slider-handle { + position: absolute; + width: 42px; + line-height: 20px; + text-align: center; + background: var(--accent-color); + color: var(--primary-color); + border-radius: #{$border-radius-small}; + transform: translateX(-50%); + cursor: pointer; + z-index: 2; + } + } + .marker{ + position: absolute; + background-color: var(--text-disabled); + width: 2px; + height: 12px; + border-radius: 1px; + top: 8px; + } + .marker.marker-10{ + left: 10%; + } + .marker.marker-20{ + left: 20%; + } + .marker.marker-30{ + left: 30%; + } + .marker.marker-40{ + left: 40%; + } + .marker.marker-50{ + left: 50%; + } + .marker.marker-60{ + left: 60%; + } + .marker.marker-70{ + left: 70%; + } + .marker.marker-80{ + left: 80%; + } + .marker.marker-90{ + left: 90%; + } + } + } + } +} diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index fa6ce51..34c0074 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -20,8 +20,9 @@ @use 'components/tools'; @use 'components/visualization/floating/energyConsumed'; @use 'components/visualization/ui/styledWidgets'; -@use './components/visualization/floating/common'; -@use './components/marketPlace/marketPlace.scss'; +@use 'components/visualization/floating/common'; +@use 'components/marketPlace/marketPlace'; +@use 'components/simulation/simulation'; // layout @use 'layout/loading'; From 1bbb21078612a404b56f8588e5dc3496a90ec85a Mon Sep 17 00:00:00 2001 From: Vishnu Date: Thu, 27 Mar 2025 12:14:19 +0530 Subject: [PATCH 3/5] Refactor components and styles for consistency and clarity --- app/src/components/icons/3dChartIcons.tsx | 4 +- .../components/icons/ExportCommonIcons.tsx | 24 ++++---- app/src/components/icons/SimulationIcons.tsx | 56 ++++++++++++++++--- app/src/components/icons/marketPlaceIcons.tsx | 40 ++++++------- .../components/ui/componets/DisplayZone.tsx | 23 ++------ app/src/components/ui/list/List.tsx | 1 - app/src/styles/base/reset.scss | 17 ++++-- app/src/styles/pages/realTimeViz.scss | 38 ++++--------- 8 files changed, 110 insertions(+), 93 deletions(-) diff --git a/app/src/components/icons/3dChartIcons.tsx b/app/src/components/icons/3dChartIcons.tsx index 320b018..d6dcb26 100644 --- a/app/src/components/icons/3dChartIcons.tsx +++ b/app/src/components/icons/3dChartIcons.tsx @@ -8,8 +8,8 @@ export function ThroughputIcon() { xmlns="http://www.w3.org/2000/svg" > diff --git a/app/src/components/icons/ExportCommonIcons.tsx b/app/src/components/icons/ExportCommonIcons.tsx index 4386198..d2c9970 100644 --- a/app/src/components/icons/ExportCommonIcons.tsx +++ b/app/src/components/icons/ExportCommonIcons.tsx @@ -556,8 +556,8 @@ export const DublicateIcon = () => { xmlns="http://www.w3.org/2000/svg" > @@ -577,32 +577,32 @@ export const DeleteIcon = () => { ); diff --git a/app/src/components/icons/SimulationIcons.tsx b/app/src/components/icons/SimulationIcons.tsx index e3c4540..28ce559 100644 --- a/app/src/components/icons/SimulationIcons.tsx +++ b/app/src/components/icons/SimulationIcons.tsx @@ -108,22 +108,22 @@ export function ResetIcon() { ); @@ -141,8 +141,8 @@ export function PlayStopIcon() { ); @@ -160,6 +160,44 @@ export function ExitIcon() { + + ); +} + +export function MoveArrowRight() { + return ( + + + + ); +} + +export function MoveArrowLeft() { + return ( + + diff --git a/app/src/components/icons/marketPlaceIcons.tsx b/app/src/components/icons/marketPlaceIcons.tsx index 76dedd3..f0c1cc1 100644 --- a/app/src/components/icons/marketPlaceIcons.tsx +++ b/app/src/components/icons/marketPlaceIcons.tsx @@ -10,8 +10,8 @@ export function StarsIcon() { ); @@ -26,14 +26,14 @@ export function DownloadIcon() { xmlns="http://www.w3.org/2000/svg" > @@ -53,14 +53,14 @@ export function EyeIconBig() { ); @@ -77,8 +77,8 @@ export function CommentsIcon() { > @@ -102,8 +102,8 @@ export function VerifiedIcon() { xmlns="http://www.w3.org/2000/svg" > @@ -123,9 +123,9 @@ export function StarsIconSmall() { ); @@ -144,9 +144,9 @@ export function FiileedStarsIconSmall() { d="M5.07372 1.61354C5.20877 1.31056 5.27632 1.15908 5.37035 1.11243C5.45202 1.0719 5.54791 1.0719 5.62958 1.11243C5.72362 1.15908 5.79117 1.31056 5.92621 1.61354L7.00187 4.02675C7.04183 4.11631 7.06178 4.16109 7.0927 4.19539C7.12 4.22573 7.15342 4.25 7.1907 4.26662C7.23287 4.2854 7.28164 4.29055 7.37917 4.30084L10.0067 4.57816C10.3366 4.61297 10.5015 4.63038 10.5749 4.70539C10.6387 4.77054 10.6683 4.86177 10.655 4.95198C10.6397 5.05581 10.5165 5.16682 10.2701 5.3889L8.30737 7.15768C8.23457 7.22331 8.19811 7.25615 8.17507 7.29611C8.15466 7.33152 8.14188 7.37077 8.13762 7.41137C8.13278 7.45728 8.14293 7.50523 8.16329 7.60119L8.71151 10.1858C8.78034 10.5103 8.81476 10.6725 8.76611 10.7655C8.72381 10.8463 8.64623 10.9027 8.55634 10.9179C8.45286 10.9354 8.30918 10.8526 8.02183 10.6868L5.73312 9.36677C5.64819 9.31777 5.60572 9.29332 5.56057 9.2837C5.52062 9.27524 5.47931 9.27524 5.43936 9.2837C5.39421 9.29332 5.35174 9.31777 5.26681 9.36677L2.97811 10.6868C2.69077 10.8526 2.5471 10.9354 2.44361 10.9179C2.35372 10.9027 2.27611 10.8463 2.23385 10.7655C2.18521 10.6725 2.21962 10.5103 2.28845 10.1858L2.83664 7.60119C2.85699 7.50523 2.86716 7.45728 2.86233 7.41137C2.85805 7.37077 2.8453 7.33152 2.82488 7.29611C2.80181 7.25615 2.76539 7.22331 2.69254 7.15768L0.72985 5.3889C0.483444 5.16682 0.360238 5.05581 0.34492 4.95198C0.33162 4.86177 0.361259 4.77054 0.425041 4.70539C0.498471 4.63038 0.663408 4.61297 0.993283 4.57816L3.62079 4.30084C3.71831 4.29055 3.76706 4.2854 3.80923 4.26662C3.84653 4.25 3.87993 4.22573 3.90727 4.19539C3.93815 4.16109 3.95812 4.11631 3.99804 4.02675L5.07372 1.61354Z" fill="#F3A50C" stroke="#F3A50C" - stroke-width="0.7" - stroke-linecap="round" - stroke-linejoin="round" + strokeWidth="0.7" + strokeLinecap="round" + strokeLinejoin="round" /> ); diff --git a/app/src/components/ui/componets/DisplayZone.tsx b/app/src/components/ui/componets/DisplayZone.tsx index ea6f62a..e86915d 100644 --- a/app/src/components/ui/componets/DisplayZone.tsx +++ b/app/src/components/ui/componets/DisplayZone.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useRef, useState, useCallback } from "react"; import { Widget } from "../../../store/useWidgetStore"; +import { MoveArrowLeft, MoveArrowRight } from "../../icons/SimulationIcons"; // Define the type for `Side` type Side = "top" | "bottom" | "left" | "right"; @@ -134,27 +135,16 @@ const DisplayZone: React.FC = ({ }; return ( -
+
{/* Left Arrow */} {showLeftArrow && ( )} {/* Zones Wrapper */} -
+
{Object.keys(zonesData).map((zoneName, index) => (
= ({ }`} onClick={() => { console.log("zoneName: ", zoneName); - setSelectedZone({ zoneName, activeSides: zonesData[zoneName].activeSides || [], @@ -181,11 +170,11 @@ const DisplayZone: React.FC = ({ {/* Right Arrow */} {showRightArrow && ( )}
); }; -export default DisplayZone; \ No newline at end of file +export default DisplayZone; diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index bfb731e..54b710d 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -9,7 +9,6 @@ interface ListProps { } const List: React.FC = ({ items = [], remove }) => { - console.log("items: ", items); return ( <> {items.length > 0 ? ( diff --git a/app/src/styles/base/reset.scss b/app/src/styles/base/reset.scss index b37f940..82d286e 100644 --- a/app/src/styles/base/reset.scss +++ b/app/src/styles/base/reset.scss @@ -1,7 +1,14 @@ * { - margin: 0; - padding: 0; - box-sizing: border-box; - user-select: none; - font-size: var(--font-size-regular); + margin: 0; + padding: 0; + box-sizing: border-box; + user-select: none; + font-size: var(--font-size-regular); +} + +input[type="password"]::-ms-reveal, /* For Microsoft Edge */ +input[type="password"]::-ms-clear, /* For Edge clear button */ +input[type="password"]::-webkit-clear-button, /* For Chrome/Safari clear button */ +input[type="password"]::-webkit-inner-spin-button { /* Just in case */ + display: none; } diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index 00d54f7..5bae256 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -36,7 +36,7 @@ } } - .zoon-wrapper { + .zone-wrapper { display: flex; background-color: var(--background-color); position: absolute; @@ -55,11 +55,16 @@ } .arrow { - background-color: var(--accent-color); + background-color: var(--highlight-accent-color); color: var(--background-color); } .zones-wrapper { + padding: 6px; + display: flex; + gap: 6px; + border-radius: #{$border-radius-medium}; + overflow-x: auto; &::-webkit-scrollbar { display: none; } @@ -82,28 +87,10 @@ } } - .zoon-wrapper.bottom { + .zone-wrapper.bottom { bottom: 210px; } - @media (max-width: 1024px) { - width: 80%; // Increase width to take more space on smaller screens - height: 500px; // Reduce height to fit smaller screens - left: 50%; // Center horizontally - - .main-container { - margin: 0 15px; // Reduce margin for better spacing - } - - .zoon-wrapper { - bottom: 5px; // Adjust position for smaller screens - - &.bottom { - bottom: 150px; // Adjust for bottom placement - } - } - } - .content-container { display: flex; height: 100vh; @@ -118,7 +105,7 @@ margin: 0 30px; transition: height 0.3s ease, margin 0.3s ease; - .zoon-wrapper { + .zone-wrapper { display: flex; background-color: rgba(224, 223, 255, 0.5); position: absolute; @@ -309,7 +296,7 @@ } .playingFlase { - .zoon-wrapper.bottom { + .zone-wrapper.bottom { bottom: 300px; } } @@ -501,13 +488,10 @@ position: absolute; top: 50%; transform: translateY(-50%); - background-color: rgba(0, 0, 0, 0.5); - color: white; border: none; cursor: pointer; - font-size: 20px; - padding: 6px; z-index: 10; + height: 100%; } .left-arrow { From f1a72e4afb0acf29d27da74d1f6f849a5f4b6fa3 Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Thu, 27 Mar 2025 12:16:20 +0530 Subject: [PATCH 4/5] added dublicate widget function --- .../components/ui/componets/AddButtons.tsx | 22 ++- .../ui/componets/DraggableWidget.tsx | 156 ++++++++---------- app/src/components/ui/componets/Panel.tsx | 4 +- .../components/marketPlace/marketPlace.scss | 4 +- app/src/styles/pages/realTimeViz.scss | 18 +- 5 files changed, 103 insertions(+), 101 deletions(-) diff --git a/app/src/components/ui/componets/AddButtons.tsx b/app/src/components/ui/componets/AddButtons.tsx index ee3092e..1545729 100644 --- a/app/src/components/ui/componets/AddButtons.tsx +++ b/app/src/components/ui/componets/AddButtons.tsx @@ -109,7 +109,7 @@ const AddButtons: React.FC = ({ }; // Update the selectedZone state - console.log('updatedZone: ', updatedZone); + console.log("updatedZone: ", updatedZone); setSelectedZone(updatedZone); } else { // If the panel is not active, activate it @@ -122,7 +122,7 @@ const AddButtons: React.FC = ({ }; // Update the selectedZone state - console.log('updatedZone: ', updatedZone); + console.log("updatedZone: ", updatedZone); setSelectedZone(updatedZone); } }; @@ -148,8 +148,9 @@ const AddButtons: React.FC = ({
{/* Hide Panel */}
= ({ {/* Lock/Unlock Panel */}
= ({ } onClick={() => toggleLockPanel(side)} > - +
)} diff --git a/app/src/components/ui/componets/DraggableWidget.tsx b/app/src/components/ui/componets/DraggableWidget.tsx index d25c8ec..743549f 100644 --- a/app/src/components/ui/componets/DraggableWidget.tsx +++ b/app/src/components/ui/componets/DraggableWidget.tsx @@ -10,7 +10,7 @@ import { DublicateIcon, KebabIcon, } from "../../icons/ExportCommonIcons"; -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; type Side = "top" | "bottom" | "left" | "right"; @@ -24,7 +24,7 @@ interface Widget { export const DraggableWidget = ({ widget, - hiddenPanels, // Add this prop to track hidden panels + hiddenPanels, index, onReorder, openKebabId, @@ -49,111 +49,108 @@ export const DraggableWidget = ({ }> >; widget: any; - hiddenPanels: string[]; // Array of hidden panel names + hiddenPanels: string[]; index: number; onReorder: (fromIndex: number, toIndex: number) => void; - openKebabId: string | null; // ID of the widget with an open kebab menu - setOpenKebabId: (id: string | null) => void; // Function to update the open kebab menu + openKebabId: string | null; + setOpenKebabId: (id: string | null) => void; }) => { const { selectedChartId, setSelectedChartId } = useWidgetStore(); - + const [panelDimensions, setPanelDimensions] = useState<{ + [side in Side]?: { width: number; height: number }; + }>({}); const handlePointerDown = () => { if (selectedChartId?.id !== widget.id) { setSelectedChartId(widget); } }; - // Determine if the widget's panel is hidden const isPanelHidden = hiddenPanels.includes(widget.panel); - const handleDragStart = (event: React.DragEvent) => { - event.dataTransfer.setData("text/plain", index.toString()); // Store the index of the dragged widget - }; - const handleDragEnter = (event: React.DragEvent) => { - event.preventDefault(); // Allow drop - }; - - const handleDragOver = (event: React.DragEvent) => { - event.preventDefault(); // Allow drop - }; - - const handleDrop = (event: React.DragEvent) => { - event.preventDefault(); - const fromIndex = parseInt(event.dataTransfer.getData("text/plain"), 10); // Get the dragged widget's index - const toIndex = index; // The index of the widget where the drop occurred - if (fromIndex !== toIndex) { - onReorder(fromIndex, toIndex); // Call the reorder function passed as a prop - } - }; - // Handle kebab icon click to toggle kebab options - const handleKebabClick = (event: React.MouseEvent) => { - event.stopPropagation(); // Prevent click from propagating to parent elements - if (openKebabId === widget.id) { - // If the current kebab is already open, close it - setOpenKebabId(null); - } else { - // Open the kebab for the clicked widget and close others - setOpenKebabId(widget.id); - } - }; - const deleteSelectedChart = () => { - // Filter out the widget to be deleted const updatedWidgets = selectedZone.widgets.filter( (w: Widget) => w.id !== widget.id ); - // Update the selectedZone state setSelectedZone((prevZone: any) => ({ ...prevZone, - widgets: updatedWidgets, // Replace the widgets array with the updated one + widgets: updatedWidgets, })); - // Close the kebab menu after deletion setOpenKebabId(null); }; + + const getCurrentWidgetCount = (panel: Side) => + selectedZone.widgets.filter((w) => w.panel === panel).length; + + const calculatePanelCapacity = (panel: Side) => { + const CHART_WIDTH = 150; + const CHART_HEIGHT = 150; + const FALLBACK_HORIZONTAL_CAPACITY = 5; + const FALLBACK_VERTICAL_CAPACITY = 3; + + const dimensions = panelDimensions[panel]; + if (!dimensions) { + return panel === "top" || panel === "bottom" + ? FALLBACK_HORIZONTAL_CAPACITY + : FALLBACK_VERTICAL_CAPACITY; + } + + return panel === "top" || panel === "bottom" + ? Math.floor(dimensions.width / CHART_WIDTH) + : Math.floor(dimensions.height / CHART_HEIGHT); + }; + + const isPanelFull = (panel: Side) => { + const currentWidgetCount = getCurrentWidgetCount(panel); + const panelCapacity = calculatePanelCapacity(panel); + return currentWidgetCount >= panelCapacity; + }; + + const duplicateWidget = () => { + const duplicatedWidget: Widget = { + ...widget, + id: `${widget.id}-copy-${Date.now()}`, + }; + + setSelectedZone((prevZone: any) => ({ + ...prevZone, + widgets: [...prevZone.widgets, duplicatedWidget], + })); + + setOpenKebabId(null); + + console.log("Duplicated widget with ID:", duplicatedWidget.id); + }; + + const handleKebabClick = (event: React.MouseEvent) => { + event.stopPropagation(); + if (openKebabId === widget.id) { + setOpenKebabId(null); + } else { + setOpenKebabId(widget.id); + } + }; + const widgetRef = useRef(null); - // Handle click outside to close the kebab menu useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( widgetRef.current && !widgetRef.current.contains(event.target as Node) ) { - setOpenKebabId(null); // Close the kebab menu if the click is outside + setOpenKebabId(null); } }; document.addEventListener("mousedown", handleClickOutside); - // Cleanup event listener on component unmount return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [setOpenKebabId]); - - const duplicateWidget = () => { - // Create a copy of the current widget with a new unique ID - const duplicatedWidget: Widget = { - ...widget, - id: `${widget.id}-copy-${Date.now()}`, // Generate a unique ID using a timestamp - }; - - // Add the duplicated widget to the selectedZone's widgets array - setSelectedZone((prevZone: any) => ({ - ...prevZone, - widgets: [...prevZone.widgets, duplicatedWidget], - })); - - // Close the kebab menu after duplication - setOpenKebabId(null); - - console.log("Duplicated widget with ID:", duplicatedWidget.id); - }; - - return ( <>