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';