diff --git a/frontend/src/assets/styles/index.scss b/frontend/src/assets/styles/index.scss index e01fc37a..8bab8500 100644 --- a/frontend/src/assets/styles/index.scss +++ b/frontend/src/assets/styles/index.scss @@ -272,6 +272,8 @@ button { --action-button-hover-border: #2f7988; --color-splitter: #212125; --color-splitter-hover: #2b444d; + --switchwrapper-border-color: #444; + --switchindicator-background-color: #2ccbe599; --status-color-running: #3CDEF8; --status-color-finished: #00D59A; --status-color-error: #FF6173; diff --git a/frontend/src/common/ui-components/common/ToggleSwitch/ToggleSwitch.module.scss b/frontend/src/common/ui-components/common/ToggleSwitch/ToggleSwitch.module.scss index 8ffab88b..94d554d1 100644 --- a/frontend/src/common/ui-components/common/ToggleSwitch/ToggleSwitch.module.scss +++ b/frontend/src/common/ui-components/common/ToggleSwitch/ToggleSwitch.module.scss @@ -1,26 +1,35 @@ -.firstRowWrapper { - display: flex; - align-items: center; - justify-content: space-between; -} - .switchWrapper { - display: flex; - gap: 8px; - padding: 4px; + display: inline-flex; + border: 1px solid var(--switchwrapper-border-color); border-radius: 31px; - border: 1px solid var(--border, #42424C); + overflow: hidden; } .switchOption { - padding: 6px 12px; - border-radius: 16px; + position: relative; + flex: 1; + text-align: center; cursor: pointer; + white-space: nowrap; + overflow: hidden; + border-radius: 31px; transition: background 0.3s ease; - color: #b0b0b0; } -.selected { - background: rgb(44 203 229 / 60%); +.optionLabel { + position: relative; + display: inline-block; + padding: 4px 12px; +} + +.switchIndicator { + position: absolute; + left: 0; + width: 100%; + height: 100%; + background-color: var(--switchindicator-background-color); +} + +.active { color: white; } diff --git a/frontend/src/common/ui-components/common/ToggleSwitch/ToggleSwitch.tsx b/frontend/src/common/ui-components/common/ToggleSwitch/ToggleSwitch.tsx index b9c68e69..24eaf491 100644 --- a/frontend/src/common/ui-components/common/ToggleSwitch/ToggleSwitch.tsx +++ b/frontend/src/common/ui-components/common/ToggleSwitch/ToggleSwitch.tsx @@ -1,23 +1,24 @@ import styles from "./ToggleSwitch.module.scss"; interface IToggleSwitchProps { - title: string; activeTab: string; setActiveTab: (a: string) => void; + options: { label: string; value: string }[]; } -const ToggleSwitch = ({ title, activeTab, setActiveTab }: IToggleSwitchProps) => { +const ToggleSwitch = ({ activeTab, setActiveTab, options }: IToggleSwitchProps) => { return ( -
-

{title}

-
-
setActiveTab("live")}> - Live +
+ {options.map((option) => ( +
setActiveTab(option.value)} + > + {activeTab === option.value && } + {option.label}
-
setActiveTab("final")}> - Final -
-
+ ))}
); }; diff --git a/frontend/src/modules/Data/components/JSONEditor.module.scss b/frontend/src/modules/Data/components/JSONEditor.module.scss index f91cc254..b8069c56 100644 --- a/frontend/src/modules/Data/components/JSONEditor.module.scss +++ b/frontend/src/modules/Data/components/JSONEditor.module.scss @@ -14,6 +14,18 @@ line-height: normal; } +.titleRow { + display: flex; + align-items: center; + justify-content: space-between; + padding-top: 10px; + padding-bottom: 5px; +} + +.titleNoMargin { + margin: 0; +} + .switchWrapper { display: flex; vertical-align: middle; diff --git a/frontend/src/modules/Data/components/JSONEditor.tsx b/frontend/src/modules/Data/components/JSONEditor.tsx index d365ec85..2b870eb0 100644 --- a/frontend/src/modules/Data/components/JSONEditor.tsx +++ b/frontend/src/modules/Data/components/JSONEditor.tsx @@ -1,4 +1,6 @@ import React, { ChangeEvent, useEffect, useState } from "react"; +/* eslint-disable css-modules/no-unused-class */ +import styles from "./JSONEditor.module.scss"; import jp from "jsonpath"; import { defineDataType, JsonViewer, Path } from "@textea/json-viewer"; import InputField from "../../../common/ui-components/common/Input/InputField"; @@ -137,7 +139,19 @@ export const JSONEditor = ({ title, jsonDataProp, height, showSearch = true, tog }} > {!toggleSwitch &&

{title}

} - {toggleSwitch && } + {toggleSwitch && ( +
+

{title}

+ +
+ )} {showSearch && ( handleSearch(event.target.value, event)}> )} diff --git a/frontend/src/modules/GraphLibrary/GraphLibrary.module.scss b/frontend/src/modules/GraphLibrary/GraphLibrary.module.scss index cd169d52..a965233f 100644 --- a/frontend/src/modules/GraphLibrary/GraphLibrary.module.scss +++ b/frontend/src/modules/GraphLibrary/GraphLibrary.module.scss @@ -30,6 +30,14 @@ .buttonWrapper { align-content: center; + + &:hover { + opacity: 0.8; + } + + &:active { + opacity: 0.6; + } } .listWrapper { diff --git a/frontend/src/modules/GraphLibrary/components/GraphElement/GraphElement.tsx b/frontend/src/modules/GraphLibrary/components/GraphElement/GraphElement.tsx index 46af5c2e..6bd2b3da 100644 --- a/frontend/src/modules/GraphLibrary/components/GraphElement/GraphElement.tsx +++ b/frontend/src/modules/GraphLibrary/components/GraphElement/GraphElement.tsx @@ -39,7 +39,7 @@ export const GraphElement: React.FC = ({ calibrat setLastRunInfo, fetchWorkflowGraph, } = useGraphContext(); - const { openTab } = useFlexLayoutContext(); + const { openTab, setGraphTab } = useFlexLayoutContext(); const updateParameter = (paramKey: string, newValue: boolean | number | string, workflow?: NodeDTO | GraphWorkflow) => { const updatedParameters = { @@ -121,6 +121,7 @@ export const GraphElement: React.FC = ({ calibrat const response = await GraphLibraryApi.submitWorkflow(selectedWorkflowName, transformDataForSubmit()); if (response.isOk) { openTab("graph-status"); + setGraphTab("active"); } else { setErrorObject(response.error); } diff --git a/frontend/src/modules/GraphLibrary/index.tsx b/frontend/src/modules/GraphLibrary/index.tsx index 0eb620c9..736cc75b 100644 --- a/frontend/src/modules/GraphLibrary/index.tsx +++ b/frontend/src/modules/GraphLibrary/index.tsx @@ -5,7 +5,7 @@ import { GraphContextProvider, useGraphContext } from "./context/GraphContext"; import { GraphList } from "./components/GraphList"; import { SelectionContextProvider } from "../common/context/SelectionContext"; import { useFlexLayoutContext } from "../../routing/flexLayout/FlexLayoutContext"; -import BlueButton from "../../ui-lib/components/Button/BlueButton"; +import RefreshIcon from "../../ui-lib/Icons/RefreshIcon"; export const GraphLibrary = () => { const { fetchAllCalibrationGraphs } = useGraphContext(); @@ -13,7 +13,7 @@ export const GraphLibrary = () => { const GraphLibraryTopBarRefreshButton = () => { return (
- fetchAllCalibrationGraphs(true)}>Refresh +
); }; diff --git a/frontend/src/modules/Nodes/NodesPage.module.scss b/frontend/src/modules/Nodes/NodesPage.module.scss index c33e63ed..b0f2760b 100644 --- a/frontend/src/modules/Nodes/NodesPage.module.scss +++ b/frontend/src/modules/Nodes/NodesPage.module.scss @@ -57,6 +57,14 @@ display: flex; background-color: #28292F; align-self: center; + + &:hover { + opacity: 0.8; + } + + &:active { + opacity: 0.6; + } } .nodeElementListWrapper { diff --git a/frontend/src/modules/Nodes/index.tsx b/frontend/src/modules/Nodes/index.tsx index 23e038a9..f1fb5fe6 100644 --- a/frontend/src/modules/Nodes/index.tsx +++ b/frontend/src/modules/Nodes/index.tsx @@ -6,8 +6,8 @@ import { NodeElementList } from "./components/NodeElement/NodeElementList"; import { RunningJob } from "./components/RunningJob/RunningJob"; import { Results } from "./components/Results/Results"; import { SelectionContextProvider } from "../common/context/SelectionContext"; -import BlueButton from "../../ui-lib/components/Button/BlueButton"; import { useFlexLayoutContext } from "../../routing/flexLayout/FlexLayoutContext"; +import RefreshIcon from "../../ui-lib/Icons/RefreshIcon"; export const NodesPage = () => { const { allNodes, runningNodeInfo, fetchAllNodes } = useNodesContext(); @@ -15,7 +15,7 @@ export const NodesPage = () => { const NodeTopBarRefreshButton = () => { return (
- fetchAllNodes()}>Refresh +
); }; diff --git a/frontend/src/modules/SidebarMenu/SidebarMenu.tsx b/frontend/src/modules/SidebarMenu/SidebarMenu.tsx index 6de49f2f..f0ed1ab5 100644 --- a/frontend/src/modules/SidebarMenu/SidebarMenu.tsx +++ b/frontend/src/modules/SidebarMenu/SidebarMenu.tsx @@ -11,10 +11,12 @@ import QUAlibrateLogoIcon from "../../ui-lib/Icons/QUAlibrateLogoIcon"; import QUAlibrateLogoSmallIcon from "../../ui-lib/Icons/QualibrateLogoSmall"; import ExpandSideMenuIcon from "../../ui-lib/Icons/ExpandSideMenuIcon"; import CollapseSideMenuIcon from "../../ui-lib/Icons/CollapseSideMenuIcon"; +import { useFlexLayoutContext } from "../../routing/flexLayout/FlexLayoutContext"; const SidebarMenu: React.FunctionComponent = () => { const { pinSideMenu } = useContext(GlobalThemeContext) as GlobalThemeContextState; const [minify, setMinify] = useState(true); + const { openTab, graphTab } = useFlexLayoutContext(); const [selectedMenuItem, setSelectedMenuItem] = useState(NODES_KEY); const containerClassName = classNames(styles.sidebarMenu, minify ? styles.collapsed : styles.expanded); @@ -34,19 +36,28 @@ const SidebarMenu: React.FunctionComponent = () => { {minify ? : } -
-
- {menuItems.map((item) => ( +
+
+ {menuItems.filter((item) => !item.hidden).map((item) => { + const handleClick = () => { + setSelectedMenuItem(item.keyId); + if (item.keyId === "graph-library") { + openTab(graphTab === "run" ? "graph-library" : "graph-status"); + } + }; + + return ( setSelectedMenuItem(item.keyId)} + onClick={handleClick} isSelected={selectedMenuItem === item.keyId} data-testid={`menu-item-${item.keyId}`} /> - ))} -
+ ); + })} +
{bottomMenuItems.map((item) => { diff --git a/frontend/src/modules/TopbarMenu/TitleBarGraphCard/TitleBarGraphCard.tsx b/frontend/src/modules/TopbarMenu/TitleBarGraphCard/TitleBarGraphCard.tsx index d5aa7f2b..c32f6fc1 100644 --- a/frontend/src/modules/TopbarMenu/TitleBarGraphCard/TitleBarGraphCard.tsx +++ b/frontend/src/modules/TopbarMenu/TitleBarGraphCard/TitleBarGraphCard.tsx @@ -13,6 +13,7 @@ import { SnapshotsApi } from "../../Snapshots/api/SnapshotsApi"; import { GraphItem, useWebSocketData } from "../../../contexts/WebSocketContext"; const TitleBarGraphCard: React.FC = () => { + const { openTab, setGraphTab } = useFlexLayoutContext(); const { runStatus } = useWebSocketData(); const [node, setNode] = useState(fallbackNode); const [graph, setGraph] = useState(fallbackGraph); @@ -28,9 +29,12 @@ const TitleBarGraphCard: React.FC = () => { } }, [runStatus]); - const { openTab } = useFlexLayoutContext(); - const handleClick = () => openTab(graph.status === "pending" ? "graph-library" : "graph-status"); - + const handleClick = () => { + const isPending = graph.status === "pending"; + setGraphTab(isPending ? "run" : "active"); + openTab(isPending ? "graph-library" : "graph-status"); + }; + const renderElapsedTime = (time: number) => (
diff --git a/frontend/src/modules/TopbarMenu/TitleBarMenu.tsx b/frontend/src/modules/TopbarMenu/TitleBarMenu.tsx index abcc53be..b30bfbc9 100644 --- a/frontend/src/modules/TopbarMenu/TitleBarMenu.tsx +++ b/frontend/src/modules/TopbarMenu/TitleBarMenu.tsx @@ -4,13 +4,38 @@ import { useFlexLayoutContext } from "../../routing/flexLayout/FlexLayoutContext import modulesMap from "../../routing/ModulesRegistry"; import PageName from "../../common/ui-components/common/Page/PageName"; import TitleBarGraphCard from "./TitleBarGraphCard/TitleBarGraphCard"; +import ToggleSwitch from "../../common/ui-components/common/ToggleSwitch/ToggleSwitch"; const TitleBarMenu: React.FC = () => { - const { activeTab, topBarAdditionalComponents } = useFlexLayoutContext(); + const { activeTab, topBarAdditionalComponents, openTab, graphTab, setGraphTab } = useFlexLayoutContext(); + + const handleTabChange = (value: string) => { + setGraphTab(value); + if (value === "run") openTab("graph-library"); + else openTab("graph-status"); + }; + return (
- {modulesMap[activeTab ?? ""]?.menuItem?.title ?? ""} - {topBarAdditionalComponents && topBarAdditionalComponents[activeTab ?? ""]} +
+
+ {modulesMap[activeTab ?? ""]?.menuItem?.title ?? ""} + + {(activeTab === "graph-library" || activeTab === "graph-status") && ( + + )} + + {topBarAdditionalComponents && topBarAdditionalComponents[activeTab ?? ""]} +
+
+
diff --git a/frontend/src/modules/TopbarMenu/styles/TitleBarMenu.module.scss b/frontend/src/modules/TopbarMenu/styles/TitleBarMenu.module.scss index 1c8cf838..76ea19ee 100644 --- a/frontend/src/modules/TopbarMenu/styles/TitleBarMenu.module.scss +++ b/frontend/src/modules/TopbarMenu/styles/TitleBarMenu.module.scss @@ -14,11 +14,22 @@ .menuCardsWrapper { display: flex; + align-items: center; + justify-content: flex-end; flex-wrap: wrap; gap: 12px; - margin-left: auto; - justify-content: flex-end; padding-right: 25px; margin-right: 25px; } +.topBarRow { + display: flex; + flex-direction: column; + margin-bottom: 10px; +} + +.titleAndControls { + display: flex; + align-items: center; + gap: 12px; +} diff --git a/frontend/src/routing/ModulesRegistry.ts b/frontend/src/routing/ModulesRegistry.ts index 3abdde28..340061ea 100644 --- a/frontend/src/routing/ModulesRegistry.ts +++ b/frontend/src/routing/ModulesRegistry.ts @@ -52,6 +52,7 @@ export type Module = { path?: string; dataCy?: string; }; + hidden?: boolean; // onClick?: () => void; }; @@ -86,7 +87,7 @@ export const ModulesRegistry: Array = [ Component: CalibrationGraph, menuItem: { sideBarTitle: "Graph Library", - title: "Run calibration graph", + title: "Calibration graph", icon: GraphLibraryIcon, dataCy: cyKeys.CALIBRATION_TAB, }, @@ -97,10 +98,11 @@ export const ModulesRegistry: Array = [ Component: GraphStatus, menuItem: { sideBarTitle: "Graph Status", - title: "Graph Status", + title: "Calibration graph", icon: GraphStatusIcon, dataCy: cyKeys.NODES_TAB, }, + hidden: true, }, { keyId: DATA_KEY, diff --git a/frontend/src/routing/flexLayout/FlexLayoutContext.tsx b/frontend/src/routing/flexLayout/FlexLayoutContext.tsx index 0c575b91..d6681515 100644 --- a/frontend/src/routing/flexLayout/FlexLayoutContext.tsx +++ b/frontend/src/routing/flexLayout/FlexLayoutContext.tsx @@ -18,6 +18,8 @@ interface IFlexLayoutContext { setTopBarAdditionalComponents: (a: { [id: string]: React.JSX.Element } | undefined) => void; selectedPageName: ModuleKey | null; setSelectedPageName: (a: ModuleKey | null) => void; + graphTab: string; + setGraphTab: (tab: string) => void; } const FlexLayoutContext = React.createContext(null); @@ -38,6 +40,7 @@ export function FlexLayoutContextProvider(props: PropsWithChildren(undefined); const [selectedPageName, setSelectedPageName] = useState(null); + const [graphTab, setGraphTab] = useState("run"); useEffect(() => { // openTab("nodes"); @@ -47,10 +50,10 @@ export function FlexLayoutContextProvider(props: PropsWithChildren { if (tab === "graph-status") { setSelectedPageName("graph-status"); - } else if (tab === "nodes") { - setSelectedPageName("nodes"); } else if (tab === "graph-library") { setSelectedPageName("graph-library"); + } else if (tab === "nodes") { + setSelectedPageName("nodes"); } else if (tab === "data") { setSelectedPageName("data"); } else { @@ -108,6 +111,8 @@ export function FlexLayoutContextProvider(props: PropsWithChildren {children} diff --git a/frontend/src/ui-lib/Icons/GraphLibraryIcon.tsx b/frontend/src/ui-lib/Icons/GraphLibraryIcon.tsx index faab6479..ec0817ef 100644 --- a/frontend/src/ui-lib/Icons/GraphLibraryIcon.tsx +++ b/frontend/src/ui-lib/Icons/GraphLibraryIcon.tsx @@ -20,10 +20,6 @@ const GraphLibraryIcon: React.FC<{ width?: number; height?: number; className?: - diff --git a/frontend/src/ui-lib/Icons/RefreshIcon.tsx b/frontend/src/ui-lib/Icons/RefreshIcon.tsx new file mode 100644 index 00000000..b0cbc896 --- /dev/null +++ b/frontend/src/ui-lib/Icons/RefreshIcon.tsx @@ -0,0 +1,34 @@ +import React from "react"; + +const RefreshButton: React.FC<{ onClick?: () => void }> = ({ onClick }) => { + return ( + + ); +}; + +export default RefreshButton;