v2 #74
@@ -125,21 +125,21 @@ export function ResetIcon() {
|
|||||||
>
|
>
|
||||||
<path
|
<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"
|
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(--icon-default-color-active)"
|
stroke="var(--text-color)"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<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"
|
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(--icon-default-color-active)"
|
fill="var(--text-color)"
|
||||||
stroke="var(--icon-default-color-active)"
|
stroke="var(--text-color)"
|
||||||
strokeWidth="0.571429"
|
strokeWidth="0.571429"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M4.78125 4.10407H2.49554V1.81836"
|
d="M4.78125 4.10407H2.49554V1.81836"
|
||||||
stroke="var(--icon-default-color-active)"
|
stroke="var(--text-color)"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
@@ -158,7 +158,7 @@ export function PlayStopIcon() {
|
|||||||
>
|
>
|
||||||
<path
|
<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"
|
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(--icon-default-color-active)"
|
stroke="var(--text-color)"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
@@ -177,7 +177,7 @@ export function ExitIcon() {
|
|||||||
>
|
>
|
||||||
<path
|
<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"
|
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(--icon-default-color-active)"
|
stroke="var(--text-color)"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import {
|
|||||||
import useToggleStore from "../../store/useUIToggleStore";
|
import useToggleStore from "../../store/useUIToggleStore";
|
||||||
import {
|
import {
|
||||||
use3DWidget,
|
use3DWidget,
|
||||||
useDroppedObjectsStore,
|
|
||||||
useFloatingWidget,
|
useFloatingWidget,
|
||||||
} from "../../store/visualization/useDroppedObjectsStore";
|
} from "../../store/visualization/useDroppedObjectsStore";
|
||||||
|
|
||||||
@@ -57,20 +56,18 @@ const Tools: React.FC = () => {
|
|||||||
|
|
||||||
const { widgets3D } = use3DWidget();
|
const { widgets3D } = use3DWidget();
|
||||||
|
|
||||||
const zones = useDroppedObjectsStore((state) => state.zones);
|
|
||||||
|
|
||||||
// wall options
|
// wall options
|
||||||
const { toggleView, setToggleView } = useToggleView();
|
const { toggleView, setToggleView } = useToggleView();
|
||||||
const { setDeleteTool } = useDeleteTool();
|
const { setDeleteTool } = useDeleteTool();
|
||||||
const { setAddAction } = useAddAction();
|
const { setAddAction } = useAddAction();
|
||||||
const { setSelectedWallItem } = useSelectedWallItem();
|
const { setSelectedWallItem } = useSelectedWallItem();
|
||||||
|
|
||||||
const { transformMode, setTransformMode } = useTransformMode();
|
const { setTransformMode } = useTransformMode();
|
||||||
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
const { setDeletePointOrLine } = useDeletePointOrLine();
|
||||||
const { movePoint, setMovePoint } = useMovePoint();
|
const { setMovePoint } = useMovePoint();
|
||||||
const { toolMode, setToolMode } = useToolMode();
|
const { setToolMode } = useToolMode();
|
||||||
const { activeTool, setActiveTool } = useActiveTool();
|
const { activeTool, setActiveTool } = useActiveTool();
|
||||||
const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
|
const { setRefTextUpdate } = useRefTextUpdate();
|
||||||
|
|
||||||
// Reset activeTool whenever activeModule changes
|
// Reset activeTool whenever activeModule changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -209,268 +206,266 @@ const Tools: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isPlaying ? (
|
{!isPlaying ? (
|
||||||
<>
|
<div className="tools-container">
|
||||||
<div className="tools-container">
|
<div className="drop-down-icons">
|
||||||
<div className="drop-down-icons">
|
<div className="activeDropicon">
|
||||||
<div className="activeDropicon">
|
{activeSubTool == "cursor" && (
|
||||||
{activeSubTool == "cursor" && (
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "cursor" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("cursor");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CursorIcon isActive={activeTool === "cursor"} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{activeSubTool == "free-hand" && (
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "free-hand" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("free-hand");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FreeMoveIcon isActive={activeTool === "free-hand"} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{activeSubTool == "delete" && (
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "delete" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("delete");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DeleteIcon isActive={activeTool === "delete"} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{activeModule !== "visualization" && (
|
|
||||||
<div
|
|
||||||
className="drop-down-option-button"
|
|
||||||
ref={dropdownRef}
|
|
||||||
onClick={() => {
|
|
||||||
setOpenDrop(!openDrop);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowIcon />
|
|
||||||
{openDrop && (
|
|
||||||
<div className="drop-down-container">
|
|
||||||
<div
|
|
||||||
className="option-list"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenDrop(false);
|
|
||||||
setActiveTool("cursor");
|
|
||||||
setActiveSubTool("cursor");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="active-option">
|
|
||||||
{activeSubTool === "cursor" && <TickIcon />}
|
|
||||||
</div>
|
|
||||||
<CursorIcon isActive={false} />
|
|
||||||
<div className="option">Cursor</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="option-list"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenDrop(false);
|
|
||||||
setActiveTool("free-hand");
|
|
||||||
setActiveSubTool("free-hand");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="active-option">
|
|
||||||
{activeSubTool === "free-hand" && <TickIcon />}
|
|
||||||
</div>
|
|
||||||
<FreeMoveIcon isActive={false} />
|
|
||||||
<div className="option">Free Hand</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="option-list"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenDrop(false);
|
|
||||||
setActiveTool("delete");
|
|
||||||
setActiveSubTool("delete");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="active-option">
|
|
||||||
{activeSubTool === "delete" && <TickIcon />}
|
|
||||||
</div>
|
|
||||||
<DeleteIcon isActive={false} />
|
|
||||||
<div className="option">Delete</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{!toggleThreeD && activeModule === "builder" && (
|
|
||||||
<>
|
|
||||||
<div className="split"></div>
|
|
||||||
<div className="draw-tools">
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "draw-wall" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("draw-wall");
|
|
||||||
}}
|
|
||||||
title="Wall"
|
|
||||||
>
|
|
||||||
<WallIcon isActive={activeTool === "draw-wall"} />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "draw-zone" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("draw-zone");
|
|
||||||
}}
|
|
||||||
title="Zone"
|
|
||||||
>
|
|
||||||
<ZoneIcon isActive={activeTool === "draw-zone"} />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "draw-aisle" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("draw-aisle");
|
|
||||||
}}
|
|
||||||
title="Aisle"
|
|
||||||
>
|
|
||||||
<AsileIcon isActive={activeTool === "draw-aisle"} />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "draw-floor" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("draw-floor");
|
|
||||||
}}
|
|
||||||
title="Floor"
|
|
||||||
>
|
|
||||||
<FloorIcon isActive={activeTool === "draw-floor"} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{activeModule === "builder" && (
|
|
||||||
<>
|
|
||||||
<div className="split"></div>
|
|
||||||
<div className="draw-tools">
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "measure" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("measure");
|
|
||||||
}}
|
|
||||||
title="Measure"
|
|
||||||
>
|
|
||||||
<MeasureToolIcon isActive={activeTool === "measure"} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{activeModule === "simulation" && (
|
|
||||||
<>
|
|
||||||
<div className="split"></div>
|
|
||||||
<div className="draw-tools">
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "pen" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("pen");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PenIcon isActive={activeTool === "pen"} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{activeModule === "visualization" && (
|
|
||||||
<>
|
|
||||||
<div className="split"></div>
|
|
||||||
<div className="draw-tools">
|
|
||||||
<div
|
|
||||||
className={`tool-button`}
|
|
||||||
onClick={() => {
|
|
||||||
handleSaveTemplate({
|
|
||||||
addTemplate,
|
|
||||||
floatingWidget,
|
|
||||||
widgets3D,
|
|
||||||
selectedZone,
|
|
||||||
templates,
|
|
||||||
visualizationSocket,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SaveTemplateIcon isActive={false} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<div className="split"></div>
|
|
||||||
<div className="general-options">
|
|
||||||
<div
|
|
||||||
className={`tool-button ${
|
|
||||||
activeTool === "comment" ? "active" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveTool("comment");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CommentIcon isActive={activeTool === "comment"} />
|
|
||||||
</div>
|
|
||||||
{toggleThreeD && (
|
|
||||||
<div
|
<div
|
||||||
className={`tool-button ${
|
className={`tool-button ${
|
||||||
activeTool === "play" ? "active" : ""
|
activeTool === "cursor" ? "active" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsPlaying(!isPlaying);
|
setActiveTool("cursor");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PlayIcon isActive={activeTool === "play"} />
|
<CursorIcon isActive={activeTool === "cursor"} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{activeSubTool == "free-hand" && (
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "free-hand" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("free-hand");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FreeMoveIcon isActive={activeTool === "free-hand"} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{activeSubTool == "delete" && (
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "delete" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("delete");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon isActive={activeTool === "delete"} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{activeModule !== "visualization" && (
|
||||||
|
<div
|
||||||
|
className="drop-down-option-button"
|
||||||
|
ref={dropdownRef}
|
||||||
|
onClick={() => {
|
||||||
|
setOpenDrop(!openDrop);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowIcon />
|
||||||
|
{openDrop && (
|
||||||
|
<div className="drop-down-container">
|
||||||
|
<div
|
||||||
|
className="option-list"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenDrop(false);
|
||||||
|
setActiveTool("cursor");
|
||||||
|
setActiveSubTool("cursor");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="active-option">
|
||||||
|
{activeSubTool === "cursor" && <TickIcon />}
|
||||||
|
</div>
|
||||||
|
<CursorIcon isActive={false} />
|
||||||
|
<div className="option">Cursor</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="option-list"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenDrop(false);
|
||||||
|
setActiveTool("free-hand");
|
||||||
|
setActiveSubTool("free-hand");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="active-option">
|
||||||
|
{activeSubTool === "free-hand" && <TickIcon />}
|
||||||
|
</div>
|
||||||
|
<FreeMoveIcon isActive={false} />
|
||||||
|
<div className="option">Free Hand</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="option-list"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenDrop(false);
|
||||||
|
setActiveTool("delete");
|
||||||
|
setActiveSubTool("delete");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="active-option">
|
||||||
|
{activeSubTool === "delete" && <TickIcon />}
|
||||||
|
</div>
|
||||||
|
<DeleteIcon isActive={false} />
|
||||||
|
<div className="option">Delete</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{activeModule === "builder" && (
|
</div>
|
||||||
<>
|
{!toggleThreeD && activeModule === "builder" && (
|
||||||
<div className="split"></div>
|
<>
|
||||||
|
<div className="split"></div>
|
||||||
|
<div className="draw-tools">
|
||||||
<div
|
<div
|
||||||
className={`toggle-threed-button${
|
className={`tool-button ${
|
||||||
toggleThreeD ? " toggled" : ""
|
activeTool === "draw-wall" ? "active" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={toggleSwitch}
|
onClick={() => {
|
||||||
|
setActiveTool("draw-wall");
|
||||||
|
}}
|
||||||
|
title="Wall"
|
||||||
>
|
>
|
||||||
<div
|
<WallIcon isActive={activeTool === "draw-wall"} />
|
||||||
className={`toggle-option${!toggleThreeD ? " active" : ""}`}
|
|
||||||
>
|
|
||||||
2d
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`toggle-option${toggleThreeD ? " active" : ""}`}
|
|
||||||
>
|
|
||||||
3d
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "draw-zone" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("draw-zone");
|
||||||
|
}}
|
||||||
|
title="Zone"
|
||||||
|
>
|
||||||
|
<ZoneIcon isActive={activeTool === "draw-zone"} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "draw-aisle" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("draw-aisle");
|
||||||
|
}}
|
||||||
|
title="Aisle"
|
||||||
|
>
|
||||||
|
<AsileIcon isActive={activeTool === "draw-aisle"} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "draw-floor" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("draw-floor");
|
||||||
|
}}
|
||||||
|
title="Floor"
|
||||||
|
>
|
||||||
|
<FloorIcon isActive={activeTool === "draw-floor"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{activeModule === "builder" && (
|
||||||
|
<>
|
||||||
|
<div className="split"></div>
|
||||||
|
<div className="draw-tools">
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "measure" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("measure");
|
||||||
|
}}
|
||||||
|
title="Measure"
|
||||||
|
>
|
||||||
|
<MeasureToolIcon isActive={activeTool === "measure"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{activeModule === "simulation" && (
|
||||||
|
<>
|
||||||
|
<div className="split"></div>
|
||||||
|
<div className="draw-tools">
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "pen" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("pen");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PenIcon isActive={activeTool === "pen"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{activeModule === "visualization" && (
|
||||||
|
<>
|
||||||
|
<div className="split"></div>
|
||||||
|
<div className="draw-tools">
|
||||||
|
<div
|
||||||
|
className={`tool-button`}
|
||||||
|
onClick={() => {
|
||||||
|
handleSaveTemplate({
|
||||||
|
addTemplate,
|
||||||
|
floatingWidget,
|
||||||
|
widgets3D,
|
||||||
|
selectedZone,
|
||||||
|
templates,
|
||||||
|
visualizationSocket,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SaveTemplateIcon isActive={false} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<div className="split"></div>
|
||||||
|
<div className="general-options">
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "comment" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTool("comment");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CommentIcon isActive={activeTool === "comment"} />
|
||||||
|
</div>
|
||||||
|
{toggleThreeD && (
|
||||||
|
<div
|
||||||
|
className={`tool-button ${
|
||||||
|
activeTool === "play" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setIsPlaying(!isPlaying);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlayIcon isActive={activeTool === "play"} />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
{activeModule === "builder" && (
|
||||||
|
<>
|
||||||
|
<div className="split"></div>
|
||||||
|
<div
|
||||||
|
className={`toggle-threed-button${
|
||||||
|
toggleThreeD ? " toggled" : ""
|
||||||
|
}`}
|
||||||
|
onClick={toggleSwitch}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`toggle-option${!toggleThreeD ? " active" : ""}`}
|
||||||
|
>
|
||||||
|
2d
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`toggle-option${toggleThreeD ? " active" : ""}`}
|
||||||
|
>
|
||||||
|
3d
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{activeModule !== "simulation" && (
|
{activeModule !== "simulation" && (
|
||||||
<div className="exitPlay" onClick={() => setIsPlaying(false)}>
|
<button className="exitPlay" onClick={() => setIsPlaying(false)}>
|
||||||
X
|
X
|
||||||
</div>
|
</button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import {
|
|||||||
} from "../../icons/ExportCommonIcons";
|
} from "../../icons/ExportCommonIcons";
|
||||||
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
||||||
import { useSubModuleStore } from "../../../store/useModuleStore";
|
import { useSubModuleStore } from "../../../store/useModuleStore";
|
||||||
|
import ProductionCapacity from "../analysis/ProductionCapacity";
|
||||||
|
import ThroughputSummary from "../analysis/ThroughputSummary";
|
||||||
|
import ROISummary from "../analysis/ROISummary";
|
||||||
|
|
||||||
const SimulationPlayer: React.FC = () => {
|
const SimulationPlayer: React.FC = () => {
|
||||||
const MAX_SPEED = 8; // Maximum speed
|
const MAX_SPEED = 8; // Maximum speed
|
||||||
@@ -162,224 +165,236 @@ const SimulationPlayer: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="simulation-player-wrapper">
|
<>
|
||||||
<div className={`simulation-player-container ${expand ? "open" : ""}`}>
|
<div className="simulation-player-wrapper">
|
||||||
<div className="controls-container">
|
<div className={`simulation-player-container ${expand ? "open" : ""}`}>
|
||||||
{subModule === "analysis" && (
|
<div className="controls-container">
|
||||||
<div className="production-details">
|
|
||||||
{/* hourlySimulation */}
|
|
||||||
<div className="hourly-wrapper production-wrapper">
|
|
||||||
<div className="header">
|
|
||||||
<div className="icon">
|
|
||||||
<HourlySimulationIcon />
|
|
||||||
</div>
|
|
||||||
<div className="label">Hourly Simulation</div>
|
|
||||||
</div>
|
|
||||||
<div className="progress-wrapper">
|
|
||||||
<div
|
|
||||||
className="progress"
|
|
||||||
style={{ width: hourlySimulation }}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* dailyProduction */}
|
|
||||||
<div className="dailyProduction-wrapper production-wrapper">
|
|
||||||
<div className="header">
|
|
||||||
<div className="icon">
|
|
||||||
<DailyProductionIcon />
|
|
||||||
</div>
|
|
||||||
<div className="label">Daily Production</div>
|
|
||||||
</div>
|
|
||||||
<div className="progress-wrapper">
|
|
||||||
<div
|
|
||||||
className="progress"
|
|
||||||
style={{ width: dailyProduction }}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* monthlyROI */}
|
|
||||||
<div className="monthlyROI-wrapper production-wrapper">
|
|
||||||
<div className="header">
|
|
||||||
<div className="icon">
|
|
||||||
<MonthlyROI />
|
|
||||||
</div>
|
|
||||||
<div className="label">Monthly ROI</div>
|
|
||||||
</div>
|
|
||||||
<div className="progress-wrapper">
|
|
||||||
<div className="progress" style={{ width: monthlyROI }}></div>
|
|
||||||
</div>{" "}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{subModule === "simulations" && (
|
|
||||||
<div className="header">
|
|
||||||
<InfoIcon />
|
|
||||||
{playSimulation
|
|
||||||
? "Paused - system idle."
|
|
||||||
: "Running simulation..."}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="controls-wrapper">
|
|
||||||
<button
|
|
||||||
className="simulation-button-container"
|
|
||||||
onClick={() => {
|
|
||||||
handleReset();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ResetIcon />
|
|
||||||
Reset
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="simulation-button-container"
|
|
||||||
onClick={() => {
|
|
||||||
handlePlayStop();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PlayStopIcon />
|
|
||||||
{playSimulation ? "Play" : "Stop"}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="simulation-button-container"
|
|
||||||
onClick={() => {
|
|
||||||
handleExit();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ExitIcon />
|
|
||||||
Exit
|
|
||||||
</button>
|
|
||||||
{subModule === "analysis" && (
|
{subModule === "analysis" && (
|
||||||
<button
|
<div className="production-details">
|
||||||
className="expand-icon-container"
|
{/* hourlySimulation */}
|
||||||
onClick={() => setExpand(!expand)}
|
<div className="hourly-wrapper production-wrapper">
|
||||||
>
|
<div className="header">
|
||||||
<ExpandIcon isActive={!expand} />
|
<div className="icon">
|
||||||
</button>
|
<HourlySimulationIcon />
|
||||||
)}
|
</div>
|
||||||
</div>
|
<div className="label">Hourly Simulation</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="progresser-wrapper">
|
<div className="progress-wrapper">
|
||||||
<div className="time-displayer">
|
|
||||||
<div className="start-time-wrappper">
|
|
||||||
<div className="icon">
|
|
||||||
<StartIcon />
|
|
||||||
</div>
|
|
||||||
<div className="time-wrapper">
|
|
||||||
<div className="date">23 April ,25</div>
|
|
||||||
<div className="time">04:41 PM</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="time-progresser">
|
|
||||||
<div className="timeline">
|
|
||||||
{intervals.map((label, index) => {
|
|
||||||
const segmentProgress = (index / totalSegments) * 100;
|
|
||||||
const isFilled = progress >= segmentProgress;
|
|
||||||
return (
|
|
||||||
<React.Fragment key={index}>
|
|
||||||
<div className="label-dot-wrapper">
|
|
||||||
<div className="label">{label} mins</div>
|
|
||||||
<div
|
|
||||||
className={`dot ${isFilled ? "filled" : ""}`}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
{index < intervals.length - 1 && (
|
|
||||||
<div
|
|
||||||
className={`line ${
|
|
||||||
progress >= ((index + 1) / totalSegments) * 100
|
|
||||||
? "filled"
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="end-time-wrappper">
|
|
||||||
<div className="time-wrapper">
|
|
||||||
<div className="time">00:10:20</div>
|
|
||||||
</div>
|
|
||||||
<div className="icon">
|
|
||||||
<EndIcon />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="speed-control-container">
|
|
||||||
<div className="min-value">
|
|
||||||
<div className="icon">
|
|
||||||
<SpeedIcon />
|
|
||||||
</div>
|
|
||||||
Speed
|
|
||||||
</div>
|
|
||||||
<div className="slider-container" ref={sliderRef}>
|
|
||||||
<div className="speed-label mix-value">0.5X</div>
|
|
||||||
<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">
|
|
||||||
<button
|
|
||||||
className={`slider-handle ${isDragging ? "dragging" : ""}`}
|
|
||||||
style={{ left: `${calculateHandlePosition()}%` }}
|
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
>
|
|
||||||
{speed.toFixed(1)}x
|
|
||||||
</button>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="0.5"
|
|
||||||
max={MAX_SPEED}
|
|
||||||
step="0.1"
|
|
||||||
value={speed}
|
|
||||||
onChange={handleSpeedChange}
|
|
||||||
className="slider-input"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="speed-label max-value">4x</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{subModule === "analysis" && (
|
|
||||||
<div className="processDisplayer">
|
|
||||||
<div className="start-displayer timmer">00:00</div>
|
|
||||||
<div className="end-displayer timmer">24:00</div>
|
|
||||||
<div
|
|
||||||
className="process-wrapper"
|
|
||||||
style={{ padding: expand ? "0px" : "5px 35px" }}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="process-container"
|
|
||||||
ref={processWrapperRef}
|
|
||||||
onMouseDown={handleProcessMouseDown}
|
|
||||||
>
|
|
||||||
{process.map((item, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="process"
|
|
||||||
style={{
|
|
||||||
width: `${item.completed}%`,
|
|
||||||
backgroundColor: getAvatarColor(index),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="process-player"
|
className="progress"
|
||||||
ref={processPlayerRef}
|
style={{ width: hourlySimulation }}
|
||||||
style={{ left: playerPosition, position: "absolute" }}
|
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
|
{/* dailyProduction */}
|
||||||
|
<div className="dailyProduction-wrapper production-wrapper">
|
||||||
|
<div className="header">
|
||||||
|
<div className="icon">
|
||||||
|
<DailyProductionIcon />
|
||||||
|
</div>
|
||||||
|
<div className="label">Daily Production</div>
|
||||||
|
</div>
|
||||||
|
<div className="progress-wrapper">
|
||||||
|
<div
|
||||||
|
className="progress"
|
||||||
|
style={{ width: dailyProduction }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* monthlyROI */}
|
||||||
|
<div className="monthlyROI-wrapper production-wrapper">
|
||||||
|
<div className="header">
|
||||||
|
<div className="icon">
|
||||||
|
<MonthlyROI />
|
||||||
|
</div>
|
||||||
|
<div className="label">Monthly ROI</div>
|
||||||
|
</div>
|
||||||
|
<div className="progress-wrapper">
|
||||||
|
<div
|
||||||
|
className="progress"
|
||||||
|
style={{ width: monthlyROI }}
|
||||||
|
></div>
|
||||||
|
</div>{" "}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{subModule === "simulations" && (
|
||||||
|
<div className="header">
|
||||||
|
<InfoIcon />
|
||||||
|
{playSimulation
|
||||||
|
? "Paused - system idle."
|
||||||
|
: "Running simulation..."}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="controls-wrapper">
|
||||||
|
<button
|
||||||
|
className="simulation-button-container"
|
||||||
|
onClick={() => {
|
||||||
|
handleReset();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ResetIcon />
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="simulation-button-container"
|
||||||
|
onClick={() => {
|
||||||
|
handlePlayStop();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlayStopIcon />
|
||||||
|
{playSimulation ? "Play" : "Stop"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="simulation-button-container"
|
||||||
|
onClick={() => {
|
||||||
|
handleExit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ExitIcon />
|
||||||
|
Exit
|
||||||
|
</button>
|
||||||
|
{subModule === "analysis" && (
|
||||||
|
<button
|
||||||
|
className="expand-icon-container"
|
||||||
|
onClick={() => setExpand(!expand)}
|
||||||
|
>
|
||||||
|
<ExpandIcon isActive={!expand} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="progresser-wrapper">
|
||||||
|
<div className="time-displayer">
|
||||||
|
<div className="start-time-wrappper">
|
||||||
|
<div className="icon">
|
||||||
|
<StartIcon />
|
||||||
|
</div>
|
||||||
|
<div className="time-wrapper">
|
||||||
|
<div className="date">23 April ,25</div>
|
||||||
|
<div className="time">04:41 PM</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="time-progresser">
|
||||||
|
<div className="timeline">
|
||||||
|
{intervals.map((label, index) => {
|
||||||
|
const segmentProgress = (index / totalSegments) * 100;
|
||||||
|
const isFilled = progress >= segmentProgress;
|
||||||
|
return (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
<div className="label-dot-wrapper">
|
||||||
|
<div className="label">{label} mins</div>
|
||||||
|
<div
|
||||||
|
className={`dot ${isFilled ? "filled" : ""}`}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
{index < intervals.length - 1 && (
|
||||||
|
<div
|
||||||
|
className={`line ${
|
||||||
|
progress >= ((index + 1) / totalSegments) * 100
|
||||||
|
? "filled"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="end-time-wrappper">
|
||||||
|
<div className="time-wrapper">
|
||||||
|
<div className="time">00:10:20</div>
|
||||||
|
</div>
|
||||||
|
<div className="icon">
|
||||||
|
<EndIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="speed-control-container">
|
||||||
|
<div className="min-value">
|
||||||
|
<div className="icon">
|
||||||
|
<SpeedIcon />
|
||||||
|
</div>
|
||||||
|
Speed
|
||||||
|
</div>
|
||||||
|
<div className="slider-container" ref={sliderRef}>
|
||||||
|
<div className="speed-label mix-value">0.5X</div>
|
||||||
|
<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">
|
||||||
|
<button
|
||||||
|
className={`slider-handle ${isDragging ? "dragging" : ""}`}
|
||||||
|
style={{ left: `${calculateHandlePosition()}%` }}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
>
|
||||||
|
{speed.toFixed(1)}x
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0.5"
|
||||||
|
max={MAX_SPEED}
|
||||||
|
step="0.1"
|
||||||
|
value={speed}
|
||||||
|
onChange={handleSpeedChange}
|
||||||
|
className="slider-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="speed-label max-value">4x</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{subModule === "analysis" && (
|
||||||
|
<div className="processDisplayer">
|
||||||
|
<div className="start-displayer timmer">00:00</div>
|
||||||
|
<div className="end-displayer timmer">24:00</div>
|
||||||
|
<div
|
||||||
|
className="process-wrapper"
|
||||||
|
style={{ padding: expand ? "0px" : "5px 35px" }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="process-container"
|
||||||
|
ref={processWrapperRef}
|
||||||
|
onMouseDown={handleProcessMouseDown}
|
||||||
|
>
|
||||||
|
{process.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="process"
|
||||||
|
style={{
|
||||||
|
width: `${item.completed}%`,
|
||||||
|
backgroundColor: getAvatarColor(index),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="process-player"
|
||||||
|
ref={processPlayerRef}
|
||||||
|
style={{ left: playerPosition, position: "absolute" }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="analysis">
|
||||||
|
<div className="analysis-wrapper">
|
||||||
|
<ProductionCapacity />
|
||||||
|
<ThroughputSummary />
|
||||||
|
</div>
|
||||||
|
<ROISummary />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -143,109 +143,109 @@ const RealTimeVisulization: React.FC = () => {
|
|||||||
});
|
});
|
||||||
}, [selectedZone]);
|
}, [selectedZone]);
|
||||||
|
|
||||||
const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
|
// const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
try {
|
// try {
|
||||||
const email = localStorage.getItem("email") ?? "";
|
// const email = localStorage.getItem("email") ?? "";
|
||||||
const organization = email?.split("@")[1]?.split(".")[0];
|
// const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
|
|
||||||
const data = event.dataTransfer.getData("text/plain");
|
// const data = event.dataTransfer.getData("text/plain");
|
||||||
if (widgetSubOption === "3D") return;
|
// if (widgetSubOption === "3D") return;
|
||||||
if (!data || selectedZone.zoneName === "") return;
|
// if (!data || selectedZone.zoneName === "") return;
|
||||||
|
|
||||||
const droppedData = JSON.parse(data);
|
// const droppedData = JSON.parse(data);
|
||||||
const canvasElement = document.getElementById("real-time-vis-canvas");
|
// const canvasElement = document.getElementById("real-time-vis-canvas");
|
||||||
if (!canvasElement) throw new Error("Canvas element not found");
|
// if (!canvasElement) throw new Error("Canvas element not found");
|
||||||
|
|
||||||
const rect = canvasElement.getBoundingClientRect();
|
// const rect = canvasElement.getBoundingClientRect();
|
||||||
const relativeX = event.clientX - rect.left;
|
// const relativeX = event.clientX - rect.left;
|
||||||
const relativeY = event.clientY - rect.top;
|
// const relativeY = event.clientY - rect.top;
|
||||||
|
|
||||||
// Widget dimensions
|
// // Widget dimensions
|
||||||
const widgetWidth = droppedData.width ?? 125;
|
// const widgetWidth = droppedData.width ?? 125;
|
||||||
const widgetHeight = droppedData.height ?? 100;
|
// const widgetHeight = droppedData.height ?? 100;
|
||||||
|
|
||||||
// Center the widget at cursor
|
// // Center the widget at cursor
|
||||||
const centerOffsetX = widgetWidth / 2;
|
// const centerOffsetX = widgetWidth / 2;
|
||||||
const centerOffsetY = widgetHeight / 2;
|
// const centerOffsetY = widgetHeight / 2;
|
||||||
|
|
||||||
const adjustedX = relativeX - centerOffsetX;
|
// const adjustedX = relativeX - centerOffsetX;
|
||||||
const adjustedY = relativeY - centerOffsetY;
|
// const adjustedY = relativeY - centerOffsetY;
|
||||||
|
|
||||||
const finalPosition = determinePosition(rect, adjustedX, adjustedY);
|
// const finalPosition = determinePosition(rect, adjustedX, adjustedY);
|
||||||
const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
|
// const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
|
||||||
|
|
||||||
let finalY = 0;
|
// let finalY = 0;
|
||||||
let finalX = 0;
|
// let finalX = 0;
|
||||||
|
|
||||||
if (activeProp1 === "top") {
|
// if (activeProp1 === "top") {
|
||||||
finalY = adjustedY;
|
// finalY = adjustedY;
|
||||||
} else {
|
// } else {
|
||||||
finalY = rect.height - (adjustedY + widgetHeight);
|
// finalY = rect.height - (adjustedY + widgetHeight);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (activeProp2 === "left") {
|
// if (activeProp2 === "left") {
|
||||||
finalX = adjustedX;
|
// finalX = adjustedX;
|
||||||
} else {
|
// } else {
|
||||||
finalX = rect.width - (adjustedX + widgetWidth);
|
// finalX = rect.width - (adjustedX + widgetWidth);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Clamp to boundaries
|
// // Clamp to boundaries
|
||||||
finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX));
|
// finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX));
|
||||||
finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY));
|
// finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY));
|
||||||
|
|
||||||
const boundedPosition = {
|
// const boundedPosition = {
|
||||||
...finalPosition,
|
// ...finalPosition,
|
||||||
[activeProp1]: finalY,
|
// [activeProp1]: finalY,
|
||||||
[activeProp2]: finalX,
|
// [activeProp2]: finalX,
|
||||||
[activeProp1 === "top" ? "bottom" : "top"]: "auto",
|
// [activeProp1 === "top" ? "bottom" : "top"]: "auto",
|
||||||
[activeProp2 === "left" ? "right" : "left"]: "auto",
|
// [activeProp2 === "left" ? "right" : "left"]: "auto",
|
||||||
};
|
// };
|
||||||
|
|
||||||
const newObject = {
|
// const newObject = {
|
||||||
...droppedData,
|
// ...droppedData,
|
||||||
id: generateUniqueId(),
|
// id: generateUniqueId(),
|
||||||
position: boundedPosition,
|
// position: boundedPosition,
|
||||||
};
|
// };
|
||||||
|
|
||||||
const existingZone =
|
// const existingZone =
|
||||||
useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
|
// useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
|
||||||
if (!existingZone) {
|
// if (!existingZone) {
|
||||||
useDroppedObjectsStore
|
// useDroppedObjectsStore
|
||||||
.getState()
|
// .getState()
|
||||||
.setZone(selectedZone.zoneName, selectedZone.zoneId);
|
// .setZone(selectedZone.zoneName, selectedZone.zoneId);
|
||||||
}
|
// }
|
||||||
|
|
||||||
const addFloatingWidget = {
|
// const addFloatingWidget = {
|
||||||
organization,
|
// organization,
|
||||||
widget: newObject,
|
// widget: newObject,
|
||||||
zoneId: selectedZone.zoneId,
|
// zoneId: selectedZone.zoneId,
|
||||||
};
|
// };
|
||||||
|
|
||||||
if (visualizationSocket) {
|
// if (visualizationSocket) {
|
||||||
visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
|
// visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
|
||||||
}
|
// }
|
||||||
|
|
||||||
useDroppedObjectsStore
|
// useDroppedObjectsStore
|
||||||
.getState()
|
// .getState()
|
||||||
.addObject(selectedZone.zoneName, newObject);
|
// .addObject(selectedZone.zoneName, newObject);
|
||||||
|
|
||||||
const droppedObjectsStore = useDroppedObjectsStore.getState();
|
// const droppedObjectsStore = useDroppedObjectsStore.getState();
|
||||||
const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
|
// const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
|
||||||
|
|
||||||
if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
|
// if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
|
||||||
console.log(
|
// console.log(
|
||||||
`Objects for Zone ${selectedZone.zoneId}:`,
|
// `Objects for Zone ${selectedZone.zoneId}:`,
|
||||||
currentZone.objects
|
// currentZone.objects
|
||||||
);
|
// );
|
||||||
setFloatingWidget(currentZone.objects);
|
// setFloatingWidget(currentZone.objects);
|
||||||
} else {
|
// } else {
|
||||||
console.warn("Zone not found or zoneId mismatch");
|
// console.warn("Zone not found or zoneId mismatch");
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Error in handleDrop:", error);
|
// console.error("Error in handleDrop:", error);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
@@ -302,16 +302,7 @@ const RealTimeVisulization: React.FC = () => {
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
</style>
|
</style>
|
||||||
<div
|
<div ref={containerRef} className="realTime-viz">
|
||||||
ref={containerRef}
|
|
||||||
id="real-time-vis-canvas"
|
|
||||||
className={`realTime-viz canvas ${isPlaying ? "playingFlase" : ""}`}
|
|
||||||
style={{
|
|
||||||
height: isPlaying || activeModule !== "visualization" ? "100vh" : "",
|
|
||||||
width: isPlaying || activeModule !== "visualization" ? "100vw" : "",
|
|
||||||
left: isPlaying || activeModule !== "visualization" ? "0%" : "",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="realTime-viz-wrapper">
|
<div className="realTime-viz-wrapper">
|
||||||
{openConfirmationPopup && (
|
{openConfirmationPopup && (
|
||||||
<RenderOverlay>
|
<RenderOverlay>
|
||||||
@@ -322,20 +313,6 @@ const RealTimeVisulization: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</RenderOverlay>
|
</RenderOverlay>
|
||||||
)}
|
)}
|
||||||
<div
|
|
||||||
className="scene-container"
|
|
||||||
style={{
|
|
||||||
height: "100%",
|
|
||||||
width: "100%",
|
|
||||||
borderRadius:
|
|
||||||
isPlaying || activeModule !== "visualization" ? "" : "6px",
|
|
||||||
}}
|
|
||||||
role="application"
|
|
||||||
onDrop={(event) => handleDrop(event)}
|
|
||||||
onDragOver={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
<Scene />
|
|
||||||
</div>
|
|
||||||
{activeModule === "visualization" && selectedZone.zoneName !== "" && (
|
{activeModule === "visualization" && selectedZone.zoneName !== "" && (
|
||||||
<DroppedObjects />
|
<DroppedObjects />
|
||||||
)}
|
)}
|
||||||
|
|||||||
122
app/src/modules/visualization/functions/handleUiDrop.ts
Normal file
122
app/src/modules/visualization/functions/handleUiDrop.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import { generateUniqueId } from "../../../functions/generateUniqueId";
|
||||||
|
import { useDroppedObjectsStore } from "../../../store/visualization/useDroppedObjectsStore";
|
||||||
|
import { determinePosition } from "./determinePosition";
|
||||||
|
import { getActiveProperties } from "./getActiveProperties";
|
||||||
|
|
||||||
|
interface HandleDropProps {
|
||||||
|
widgetSubOption: any;
|
||||||
|
visualizationSocket: any;
|
||||||
|
selectedZone: any;
|
||||||
|
setFloatingWidget: (value: any) => void;
|
||||||
|
event: React.DragEvent<HTMLDivElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createHandleDrop = ({
|
||||||
|
widgetSubOption,
|
||||||
|
visualizationSocket,
|
||||||
|
selectedZone,
|
||||||
|
setFloatingWidget,
|
||||||
|
event,
|
||||||
|
}: HandleDropProps) => {
|
||||||
|
event.preventDefault();
|
||||||
|
try {
|
||||||
|
const email = localStorage.getItem("email") ?? "";
|
||||||
|
const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
|
|
||||||
|
const data = event.dataTransfer.getData("text/plain");
|
||||||
|
if (widgetSubOption === "3D") return;
|
||||||
|
if (!data || selectedZone.zoneName === "") return;
|
||||||
|
|
||||||
|
const droppedData = JSON.parse(data);
|
||||||
|
const canvasElement = document.getElementById("real-time-vis-canvas");
|
||||||
|
if (!canvasElement) throw new Error("Canvas element not found");
|
||||||
|
|
||||||
|
const rect = canvasElement.getBoundingClientRect();
|
||||||
|
const relativeX = event.clientX - rect.left;
|
||||||
|
const relativeY = event.clientY - rect.top;
|
||||||
|
|
||||||
|
// Widget dimensions
|
||||||
|
const widgetWidth = droppedData.width ?? 125;
|
||||||
|
const widgetHeight = droppedData.height ?? 100;
|
||||||
|
|
||||||
|
// Center the widget at cursor
|
||||||
|
const centerOffsetX = widgetWidth / 2;
|
||||||
|
const centerOffsetY = widgetHeight / 2;
|
||||||
|
|
||||||
|
const adjustedX = relativeX - centerOffsetX;
|
||||||
|
const adjustedY = relativeY - centerOffsetY;
|
||||||
|
|
||||||
|
const finalPosition = determinePosition(rect, adjustedX, adjustedY);
|
||||||
|
const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
|
||||||
|
|
||||||
|
let finalY = 0;
|
||||||
|
let finalX = 0;
|
||||||
|
|
||||||
|
if (activeProp1 === "top") {
|
||||||
|
finalY = adjustedY;
|
||||||
|
} else {
|
||||||
|
finalY = rect.height - (adjustedY + widgetHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeProp2 === "left") {
|
||||||
|
finalX = adjustedX;
|
||||||
|
} else {
|
||||||
|
finalX = rect.width - (adjustedX + widgetWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp to boundaries
|
||||||
|
finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX));
|
||||||
|
finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY));
|
||||||
|
|
||||||
|
const boundedPosition = {
|
||||||
|
...finalPosition,
|
||||||
|
[activeProp1]: finalY,
|
||||||
|
[activeProp2]: finalX,
|
||||||
|
[activeProp1 === "top" ? "bottom" : "top"]: "auto",
|
||||||
|
[activeProp2 === "left" ? "right" : "left"]: "auto",
|
||||||
|
};
|
||||||
|
|
||||||
|
const newObject = {
|
||||||
|
...droppedData,
|
||||||
|
id: generateUniqueId(),
|
||||||
|
position: boundedPosition,
|
||||||
|
};
|
||||||
|
|
||||||
|
const existingZone =
|
||||||
|
useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
|
||||||
|
if (!existingZone) {
|
||||||
|
useDroppedObjectsStore
|
||||||
|
.getState()
|
||||||
|
.setZone(selectedZone.zoneName, selectedZone.zoneId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addFloatingWidget = {
|
||||||
|
organization,
|
||||||
|
widget: newObject,
|
||||||
|
zoneId: selectedZone.zoneId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (visualizationSocket) {
|
||||||
|
visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
useDroppedObjectsStore
|
||||||
|
.getState()
|
||||||
|
.addObject(selectedZone.zoneName, newObject);
|
||||||
|
|
||||||
|
const droppedObjectsStore = useDroppedObjectsStore.getState();
|
||||||
|
const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
|
||||||
|
|
||||||
|
if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
|
||||||
|
console.log(
|
||||||
|
`Objects for Zone ${selectedZone.zoneId}:`,
|
||||||
|
currentZone.objects
|
||||||
|
);
|
||||||
|
setFloatingWidget(currentZone.objects);
|
||||||
|
} else {
|
||||||
|
console.warn("Zone not found or zoneId mismatch");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in handleDrop:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
useWallItems,
|
useWallItems,
|
||||||
useZones,
|
useZones,
|
||||||
useLoadingProgress,
|
useLoadingProgress,
|
||||||
|
useWidgetSubOption,
|
||||||
} from "../store/store";
|
} from "../store/store";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { usePlayButtonStore } from "../store/usePlayButtonStore";
|
import { usePlayButtonStore } from "../store/usePlayButtonStore";
|
||||||
@@ -22,9 +23,10 @@ import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
|
|||||||
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
|
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
|
||||||
import { useSelectedUserStore } from "../store/useCollabStore";
|
import { useSelectedUserStore } from "../store/useCollabStore";
|
||||||
import FollowPerson from "../components/templates/FollowPerson";
|
import FollowPerson from "../components/templates/FollowPerson";
|
||||||
import ProductionCapacity from "../components/ui/analysis/ProductionCapacity";
|
import Scene from "../modules/scene/scene";
|
||||||
import ThroughputSummary from "../components/ui/analysis/ThroughputSummary";
|
import { createHandleDrop } from "../modules/visualization/functions/handleUiDrop";
|
||||||
import ROISummary from "../components/ui/analysis/ROISummary";
|
import { useSelectedZoneStore } from "../store/visualization/useZoneStore";
|
||||||
|
import { useFloatingWidget } from "../store/visualization/useDroppedObjectsStore";
|
||||||
|
|
||||||
const Project: React.FC = () => {
|
const Project: React.FC = () => {
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
@@ -53,38 +55,67 @@ const Project: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
navigate("/");
|
navigate("/");
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { isPlaying } = usePlayButtonStore();
|
// global store
|
||||||
const { toggleThreeD } = useThreeDStore();
|
const { toggleThreeD } = useThreeDStore();
|
||||||
|
|
||||||
|
// simulation store
|
||||||
|
const { isPlaying } = usePlayButtonStore();
|
||||||
|
|
||||||
|
// collaboration store
|
||||||
const { selectedUser } = useSelectedUserStore();
|
const { selectedUser } = useSelectedUserStore();
|
||||||
|
|
||||||
|
// real-time visualization store
|
||||||
|
const { widgetSubOption } = useWidgetSubOption();
|
||||||
|
const { visualizationSocket } = useSocketStore();
|
||||||
|
const { selectedZone } = useSelectedZoneStore();
|
||||||
|
const { setFloatingWidget } = useFloatingWidget();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="project-main">
|
<div className="project-main">
|
||||||
{/* <div className="analysis">
|
{!selectedUser && (
|
||||||
<div className="analysis-wrapper">
|
|
||||||
<ProductionCapacity />
|
|
||||||
<ThroughputSummary />
|
|
||||||
</div>
|
|
||||||
<ROISummary />
|
|
||||||
</div> */}
|
|
||||||
<KeyPressListener />
|
|
||||||
{loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
|
|
||||||
{!isPlaying && (
|
|
||||||
<>
|
<>
|
||||||
{toggleThreeD && <ModuleToggle />}
|
<KeyPressListener />
|
||||||
<SideBarLeft />
|
{loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
|
||||||
<SideBarRight />
|
{!isPlaying && (
|
||||||
|
<>
|
||||||
|
{toggleThreeD && <ModuleToggle />}
|
||||||
|
<SideBarLeft />
|
||||||
|
<SideBarRight />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<RealTimeVisulization />
|
||||||
|
{activeModule === "market" && <MarketPlace />}
|
||||||
|
{activeModule !== "market" && <Tools />}
|
||||||
|
{isPlaying && activeModule === "simulation" && <SimulationPlayer />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{/* <RenderOverlay>
|
<div
|
||||||
<MenuBar setOpenMenu={setOpenMenu} />
|
className="scene-container"
|
||||||
</RenderOverlay> */}
|
id="real-time-vis-canvas"
|
||||||
{activeModule === "market" && <MarketPlace />}
|
style={{
|
||||||
<RealTimeVisulization />
|
height: isPlaying || activeModule !== "visualization" ? "100vh" : "",
|
||||||
{activeModule !== "market" && <Tools />}
|
width: isPlaying || activeModule !== "visualization" ? "100vw" : "",
|
||||||
{isPlaying && activeModule === "simulation" && <SimulationPlayer />}
|
left: isPlaying || activeModule !== "visualization" ? "0%" : "",
|
||||||
{/* {<SimulationPlayer />} */}
|
borderRadius:
|
||||||
|
isPlaying || activeModule !== "visualization" ? "" : "6px",
|
||||||
|
}}
|
||||||
|
role="application"
|
||||||
|
onDrop={(event) =>
|
||||||
|
createHandleDrop({
|
||||||
|
widgetSubOption,
|
||||||
|
visualizationSocket,
|
||||||
|
selectedZone,
|
||||||
|
setFloatingWidget,
|
||||||
|
event,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onDragOver={(event) => event.preventDefault()}
|
||||||
|
>
|
||||||
|
<Scene />
|
||||||
|
</div>
|
||||||
{selectedUser && <FollowPerson />}
|
{selectedUser && <FollowPerson />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { addingFloatingWidgets } from "../../services/visulization/zone/addFloatingWidgets";
|
|
||||||
import { useSocketStore } from "../store";
|
import { useSocketStore } from "../store";
|
||||||
import useChartStore from "./useChartStore";
|
import useChartStore from "./useChartStore";
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,29 @@
|
|||||||
@use "../abstracts/variables" as *;
|
@use "../abstracts/variables" as *;
|
||||||
@use "../abstracts/mixins" as *;
|
@use "../abstracts/mixins" as *;
|
||||||
|
|
||||||
section, .section{
|
section,
|
||||||
padding: 4px;
|
.section {
|
||||||
outline: 1px solid var(--border-color);
|
padding: 4px;
|
||||||
outline-offset: -1px;
|
outline: 1px solid var(--border-color);
|
||||||
border-radius: #{$border-radius-large};
|
outline-offset: -1px;
|
||||||
background: var(--background-color);
|
border-radius: #{$border-radius-large};
|
||||||
margin: 4px 0;
|
background: var(--background-color);
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scene-container {
|
||||||
|
width: calc(100% - (320px + 270px + 90px));
|
||||||
|
height: calc(100% - (250px));
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: calc(270px + 45px);
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: $box-shadow-medium;
|
||||||
|
canvas {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,3 @@ input[type="password"]::-webkit-clear-button, /* For Chrome/Safari clear button
|
|||||||
input[type="password"]::-webkit-inner-spin-button { /* Just in case */
|
input[type="password"]::-webkit-inner-spin-button { /* Just in case */
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
button{
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
background: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
@use "../../abstracts/variables" as *;
|
||||||
|
@use "../../abstracts/mixins" as *;
|
||||||
|
|
||||||
.roiSummary-container {
|
.roiSummary-container {
|
||||||
.roiSummary-wrapper {
|
.roiSummary-wrapper {
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
@@ -64,7 +67,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid #00FF56;
|
border: 1px solid #00FF56;
|
||||||
background: #436D51;
|
background: #17eb5d65;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
@@ -223,12 +226,11 @@
|
|||||||
background: none;
|
background: none;
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
background-color: var(--accent-color);
|
color: var(--text-button-color);
|
||||||
color: var(--background-color);
|
background: var(--background-color-button);
|
||||||
padding: 4px 6px;
|
padding: 4px 12px;
|
||||||
border-radius: 5px;
|
border-radius: #{$border-radius-large};
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 14px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,7 +246,6 @@
|
|||||||
height: 250px;
|
height: 250px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: background 0.5s ease;
|
|
||||||
}
|
}
|
||||||
.progress-cover {
|
.progress-cover {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -252,7 +253,6 @@
|
|||||||
height: 75%;
|
height: 75%;
|
||||||
top: 12.5%;
|
top: 12.5%;
|
||||||
left: 12.5%;
|
left: 12.5%;
|
||||||
background: #000000cc;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,279 +1,269 @@
|
|||||||
.analysis {
|
.analysis {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: start;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
pointer-events: none;
|
||||||
|
padding: 10px;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
.analysis-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
align-items: start;
|
gap: 12px;
|
||||||
width: 100%;
|
}
|
||||||
height: 100vh;
|
.analysis-card {
|
||||||
// pointer-events: none;
|
|
||||||
z-index: 10000;
|
|
||||||
|
|
||||||
.analysis-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysis-card {
|
|
||||||
min-width: 333px;
|
min-width: 333px;
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
outline: 1px solid var(--border-color);
|
||||||
|
outline-offset: -1px;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
.analysis-card-wrapper {
|
.analysis-card-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14px;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.main-header {
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: var(--font-size-regular);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 14px;
|
|
||||||
|
|
||||||
.card-header {
|
.throughput-value {
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fbebd7;
|
||||||
|
|
||||||
|
.bar-fill {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: #fc9d2f;
|
||||||
|
border-radius: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-fill.full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
}
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.main-header {
|
.bar-fill.partial {
|
||||||
line-height: 20px;
|
width: 0; // inline style will override this
|
||||||
font-size: var(--font-size-regular);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.process-container {
|
.metrics-section {
|
||||||
display: flex;
|
padding-top: 16px;
|
||||||
flex-direction: column;
|
border-top: 1px solid var(--background-color-gray);
|
||||||
|
|
||||||
.throughput-value {
|
.metric {
|
||||||
font-size: 1rem;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
.value {
|
.label {
|
||||||
font-weight: bold;
|
color: var(--text-color);
|
||||||
font-size: 1.5rem;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar-wrapper {
|
.value {
|
||||||
display: flex;
|
font-weight: bold;
|
||||||
gap: 8px;
|
}
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
position: relative;
|
|
||||||
// width: 36px;
|
|
||||||
width: 100%;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 13px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #FBEBD7;
|
|
||||||
|
|
||||||
.bar-fill {
|
|
||||||
position: absolute;
|
|
||||||
height: 100%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background: #FC9D2F;
|
|
||||||
border-radius: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar-fill.full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar-fill.partial {
|
|
||||||
width: 0; // inline style will override this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.metrics-section {
|
|
||||||
padding-top: 16px;
|
|
||||||
border-top: 1px solid var(--background-color-gray);
|
|
||||||
|
|
||||||
.metric {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.throughoutSummary {
|
|
||||||
.throughoutSummary-wrapper {
|
.throughoutSummary-wrapper {
|
||||||
.process-container {
|
.process-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.throughput-value {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Let the text take available space */
|
||||||
|
}
|
||||||
|
|
||||||
|
.lineChart {
|
||||||
|
max-width: 200px;
|
||||||
|
height: 100px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.assetUsage {
|
||||||
|
text-align: right;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px; // Space between cards
|
||||||
|
margin-top: 24px;
|
||||||
|
|
||||||
|
.footer-card {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--background-color-gray);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
width: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-size: var(--font-size-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: end;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiftUtilization {
|
||||||
|
.value-container {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 16px;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.throughput-value {
|
.value {
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-xlarge);
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.value {
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Let the text take available space */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lineChart {
|
.progress-wrapper {
|
||||||
max-width: 200px;
|
width: 100%;
|
||||||
height: 100px;
|
display: flex;
|
||||||
position: relative;
|
gap: 6px;
|
||||||
|
|
||||||
.assetUsage {
|
.progress {
|
||||||
text-align: right;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px; // Space between cards
|
|
||||||
margin-top: 24px;
|
|
||||||
|
|
||||||
.footer-card {
|
|
||||||
width: 100%;
|
|
||||||
background: var(--background-color-gray);
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 8px;
|
height: 5px;
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
background: #f3c64d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
background: #67b3f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
background: #7981f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-indicator {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.shift-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 5px;
|
||||||
|
|
||||||
&:first-child {
|
/* Align items vertically */
|
||||||
width: 85%;
|
&:nth-child(1) {
|
||||||
|
.indicator {
|
||||||
|
background: #f3c64d;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
&:nth-child(2) {
|
||||||
font-size: var(--font-size-regular);
|
.indicator {
|
||||||
|
background: #67b3f4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.value-container {
|
&:nth-child(3) {
|
||||||
display: flex;
|
.indicator {
|
||||||
flex-direction: row;
|
background: #7981f5;
|
||||||
align-items: center;
|
}
|
||||||
justify-content: end;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
display: inline-block;
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.shiftUtilization {
|
|
||||||
.value-container {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
.value {
|
|
||||||
font-size: var(--font-size-xlarge);
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
.progress {
|
|
||||||
border-radius: 6px;
|
|
||||||
height: 5px;
|
|
||||||
|
|
||||||
&:nth-child(1) {
|
|
||||||
background: #F3C64D;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
background: #67B3F4;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(3) {
|
|
||||||
background: #7981F5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-indicator {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
.shift-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
/* Align items vertically */
|
|
||||||
&:nth-child(1) {
|
|
||||||
.indicator {
|
|
||||||
|
|
||||||
background: #F3C64D;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
.indicator {
|
|
||||||
|
|
||||||
background: #67B3F4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(3) {
|
|
||||||
.indicator {
|
|
||||||
|
|
||||||
background: #7981F5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: var(--font-size-small);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
|
||||||
display: inline-block;
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
border-radius: 50%;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
@use "../abstracts/variables" as *;
|
||||||
|
@use "../abstracts/mixins" as *;
|
||||||
|
|
||||||
|
.labeled-button-container {
|
||||||
|
@include flex-space-between;
|
||||||
|
padding: 6px 12px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 2px 32px;
|
||||||
|
border: none;
|
||||||
|
border-radius: #{$border-radius-large};
|
||||||
|
color: var(--text-button-color);
|
||||||
|
background: var(--background-color-button);
|
||||||
|
transition: all 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|||||||
@@ -639,21 +639,6 @@ input[type="number"] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.labeled-button-container {
|
|
||||||
@include flex-space-between;
|
|
||||||
padding: 6px 12px;
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 2px 32px;
|
|
||||||
border: none;
|
|
||||||
border-radius: #{$border-radius-large};
|
|
||||||
color: var(--text-button-color);
|
|
||||||
background: var(--background-color-button);
|
|
||||||
transition: all 0.2s;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.value-field-container {
|
.value-field-container {
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
transition: width 0.2s;
|
transition: width 0.2s;
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
z-index: #{$z-index-default};
|
z-index: 2;
|
||||||
outline: 1px solid var(--border-color);
|
outline: 1px solid var(--border-color);
|
||||||
outline-offset: -1px;
|
outline-offset: -1px;
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
// Main Container
|
// Main Container
|
||||||
.realTime-viz {
|
.realTime-viz {
|
||||||
background: #131313;
|
|
||||||
box-shadow: $box-shadow-medium;
|
|
||||||
width: calc(100% - (320px + 270px + 90px));
|
width: calc(100% - (320px + 270px + 90px));
|
||||||
height: calc(100% - (250px));
|
height: calc(100% - (250px));
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -12,8 +10,8 @@
|
|||||||
left: calc(270px + 45px);
|
left: calc(270px + 45px);
|
||||||
transform: translate(0, -50%);
|
transform: translate(0, -50%);
|
||||||
border-radius: #{$border-radius-medium};
|
border-radius: #{$border-radius-medium};
|
||||||
transition: all 0.2s;
|
z-index: 2;
|
||||||
z-index: #{$z-index-default};
|
pointer-events: none;
|
||||||
|
|
||||||
.realTime-viz-wrapper {
|
.realTime-viz-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -39,10 +37,6 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scene-container {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -74,6 +68,8 @@
|
|||||||
z-index: 3;
|
z-index: 3;
|
||||||
transform: translate(-50%, -10%);
|
transform: translate(-50%, -10%);
|
||||||
transition: transform 0.5s linear;
|
transition: transform 0.5s linear;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -367,6 +363,7 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
box-shadow: #{$box-shadow-medium};
|
box-shadow: #{$box-shadow-medium};
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--icon-default-color) !important;
|
stroke: var(--icon-default-color) !important;
|
||||||
@@ -428,8 +425,6 @@
|
|||||||
stroke: #f65648;
|
stroke: #f65648;
|
||||||
stroke-width: 1.3;
|
stroke-width: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -778,17 +773,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.panel-content {
|
.panel-content {
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* RIGHT */
|
/* RIGHT */
|
||||||
.panel-content.right-opening {
|
.panel-content.right-opening {
|
||||||
animation: rightExpand 0.5s ease-in-out forwards;
|
animation: rightExpand 0.5s ease-in-out forwards;
|
||||||
@@ -913,9 +901,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Add button
|
// Add button
|
||||||
|
|
||||||
.extra-Bs-addopening {
|
.extra-Bs-addopening {
|
||||||
@@ -926,7 +911,6 @@
|
|||||||
animation: slideUp 0.3s ease forwards;
|
animation: slideUp 0.3s ease forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes slideDown {
|
@keyframes slideDown {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user