simulation slider
This commit is contained in:
@@ -93,3 +93,76 @@ export function SimulationIcon({ isActive }: { isActive: boolean }) {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
// simulation player icons
|
||||
|
||||
export function ResetIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 13 13"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M2.54182 4.09637C3.33333 2.73422 4.80832 1.81836 6.49721 1.81836C9.02194 1.81836 11.0686 3.86506 11.0686 6.38979C11.0686 8.91452 9.02194 10.9612 6.49721 10.9612C3.97248 10.9612 1.92578 8.91452 1.92578 6.38979"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M7.64118 6.38895C7.64118 7.02013 7.12951 7.53181 6.49833 7.53181C5.86714 7.53181 5.35547 7.02013 5.35547 6.38895C5.35547 5.75777 5.86714 5.24609 6.49833 5.24609C7.12951 5.24609 7.64118 5.75777 7.64118 6.38895Z"
|
||||
fill="var(--text-color)"
|
||||
stroke="var(--text-color)"
|
||||
stroke-width="0.571429"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M4.78125 4.10407H2.49554V1.81836"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlayStopIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 13 13"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 2.88867V9.88867M11 2.88867V9.88867M2 3.99171V8.78562C2 9.28847 2 9.53987 2.09943 9.65627C2.1857 9.75732 2.31512 9.81092 2.44756 9.80047C2.60019 9.78847 2.77796 9.61072 3.13352 9.25517L5.5305 6.85817C5.69485 6.69382 5.777 6.61167 5.8078 6.51692C5.83485 6.43357 5.83485 6.34377 5.8078 6.26042C5.777 6.16567 5.69485 6.08352 5.5305 5.91917L3.13352 3.52219C2.77796 3.16664 2.60019 2.98886 2.44756 2.97685C2.31512 2.96643 2.1857 3.02004 2.09943 3.12105C2 3.23747 2 3.48888 2 3.99171Z"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function ExitIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 13 13"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M2.08203 6.34311L5.03647 3.38867M2.08203 6.34311L5.03647 9.29755M2.08203 6.34311H5.9228C7.22276 6.34334 9.34995 7.05241 10.7681 9.88867"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -285,9 +285,13 @@ const Tools: React.FC = () => {
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="exitPlay" onClick={() => setIsPlaying(false)}>
|
||||
X
|
||||
</div>
|
||||
<>
|
||||
{activeModule !== "simulation" && (
|
||||
<div className="exitPlay" onClick={() => setIsPlaying(false)}>
|
||||
X
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
133
app/src/components/ui/simulation/simulationPlayer.tsx
Normal file
133
app/src/components/ui/simulation/simulationPlayer.tsx
Normal file
@@ -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<number>(1);
|
||||
const [playSimulation, setPlaySimulation] = useState(false);
|
||||
const { setIsPlaying } = usePlayButtonStore();
|
||||
const sliderRef = useRef<HTMLDivElement>(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<HTMLInputElement>) => {
|
||||
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 (
|
||||
<div className="simulation-player-wrapper">
|
||||
<div className="simulation-player-container">
|
||||
<div className="controls-container">
|
||||
<div
|
||||
className="simulation-button-container"
|
||||
onClick={() => {
|
||||
handleReset();
|
||||
}}
|
||||
>
|
||||
<ResetIcon />
|
||||
Reset
|
||||
</div>
|
||||
<div
|
||||
className="simulation-button-container"
|
||||
onClick={() => {
|
||||
handlePlayStop();
|
||||
}}
|
||||
>
|
||||
<PlayStopIcon />
|
||||
{playSimulation ? "Play" : "Stop"}
|
||||
</div>
|
||||
<div
|
||||
className="simulation-button-container"
|
||||
onClick={() => {
|
||||
handleExit();
|
||||
}}
|
||||
>
|
||||
<ExitIcon />
|
||||
Exit
|
||||
</div>
|
||||
</div>
|
||||
<div className="speed-control-container">
|
||||
<div className="min-value">0.5x</div>
|
||||
<div className="slider-container" ref={sliderRef}>
|
||||
<div className="marker marker-10"></div>
|
||||
<div className="marker marker-20"></div>
|
||||
<div className="marker marker-30"></div>
|
||||
<div className="marker marker-40"></div>
|
||||
<div className="marker marker-50"></div>
|
||||
<div className="marker marker-60"></div>
|
||||
<div className="marker marker-70"></div>
|
||||
<div className="marker marker-80"></div>
|
||||
<div className="marker marker-90"></div>
|
||||
<div className="custom-slider">
|
||||
<div
|
||||
className={`slider-handle ${isDragging ? "dragging" : ""}`}
|
||||
style={{ left: `${calculateHandlePosition()}%` }}
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
{speed.toFixed(1)}x
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="0.5"
|
||||
max="50"
|
||||
step="0.1"
|
||||
value={speed}
|
||||
onChange={handleSpeedChange}
|
||||
className="slider-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-value">50x</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SimulationPlayer;
|
||||
@@ -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 = () => {
|
||||
<RealTimeVisulization />
|
||||
{activeModule !== "market" && <Tools />}
|
||||
{/* <SimulationUI /> */}
|
||||
{isPlaying && activeModule === "simulation" && <SimulationPlayer />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
114
app/src/styles/components/simulation/simulation.scss
Normal file
114
app/src/styles/components/simulation/simulation.scss
Normal file
@@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user