feat: make experiment control buttons prettier
Use actual icons for the play, pause, skip, etc. buttons.
This commit is contained in:
22
src/App.module.css
Normal file
22
src/App.module.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/* Spread header items. Make home button centered until there is not enough space. */
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
|
||||
/* Center each */
|
||||
& > div {
|
||||
flex: 1;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
/* Except first and last */
|
||||
& > div:first-child {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
& > div:last-child {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import UserManual from './pages/Manuals/Manuals.tsx';
|
||||
import VisProg from "./pages/VisProgPage/VisProg.tsx";
|
||||
import {useState} from "react";
|
||||
import Logging from "./components/Logging/Logging.tsx";
|
||||
import styles from "./App.module.css";
|
||||
|
||||
|
||||
function App(){
|
||||
@@ -15,10 +16,10 @@ function App(){
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<span>© Utrecht University (ICS)</span>
|
||||
<Link to={"/"}>Home</Link>
|
||||
<button onClick={() => setShowLogs(!showLogs)}>Developer Logs</button>
|
||||
<header className={styles.header}>
|
||||
<div><span>© Utrecht University (ICS)</span></div>
|
||||
<div><Link to={"/"}>Home</Link></div>
|
||||
<div><button onClick={() => setShowLogs(!showLogs)}>Developer Logs</button></div>
|
||||
</header>
|
||||
<div className={"flex-row justify-center flex-1 min-height-0"}>
|
||||
<main className={"flex-col align-center flex-1 scroll-y"}>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export default function Redo({ fill }: { fill?: string }) {
|
||||
return <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill={fill ?? "canvas"}>
|
||||
<path d="M390.98-191.87q-98.44 0-168.77-65.27-70.34-65.27-70.34-161.43 0-96.15 70.34-161.54 70.33-65.39 168.77-65.39h244.11l-98.98-98.98 63.65-63.65L808.13-600 599.76-391.87l-63.65-63.65 98.98-98.98H390.98q-60.13 0-104.12 38.92-43.99 38.93-43.99 96.78 0 57.84 43.99 96.89 43.99 39.04 104.12 39.04h286.15v91H390.98Z"/>
|
||||
</svg>;
|
||||
}
|
||||
5
src/components/Icons/Stop.tsx
Normal file
5
src/components/Icons/Stop.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function Stop({ fill }: { fill?: string }) {
|
||||
return <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill={fill ?? "canvas"}>
|
||||
<path d="M240-240v-480h480v480H240Z"/>
|
||||
</svg>;
|
||||
}
|
||||
@@ -46,6 +46,7 @@ University within the Software Project course.
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,11 @@ import {
|
||||
RobotConnected
|
||||
} from './MonitoringPageComponents';
|
||||
import ExperimentLogs from "./components/ExperimentLogs.tsx";
|
||||
import Pause from "../../components/Icons/Pause.tsx";
|
||||
import Play from "../../components/Icons/Play.tsx";
|
||||
import Next from "../../components/Icons/Next.tsx";
|
||||
import Replay from "../../components/Icons/Replay.tsx";
|
||||
import Stop from "../../components/Icons/Stop.tsx";
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 1. State management
|
||||
@@ -238,34 +243,39 @@ function ControlPanel({
|
||||
<h3>Experiment Controls</h3>
|
||||
<div className={styles.controlsButtons}>
|
||||
<button
|
||||
aria-label="Pause"
|
||||
className={!isPlaying ? styles.pausePlayActive : styles.pausePlayInactive}
|
||||
onClick={() => onAction("pause")}
|
||||
disabled={loading}
|
||||
>❚❚</button>
|
||||
><Pause fill={"white"} /></button>
|
||||
|
||||
<button
|
||||
aria-label="Play"
|
||||
className={isPlaying ? styles.pausePlayActive : styles.pausePlayInactive}
|
||||
onClick={() => onAction("play")}
|
||||
disabled={loading}
|
||||
>▶</button>
|
||||
><Play fill={"white"} /></button>
|
||||
|
||||
<button
|
||||
aria-label="Next phase"
|
||||
className={styles.next}
|
||||
onClick={() => onAction("nextPhase")}
|
||||
disabled={loading}
|
||||
>⏭</button>
|
||||
><Next fill={"white"} /></button>
|
||||
|
||||
<button
|
||||
aria-label="Reset experiment"
|
||||
className={styles.restartExperiment}
|
||||
onClick={onReset}
|
||||
disabled={loading}
|
||||
>⟲</button>
|
||||
><Replay fill={"white"} /></button>
|
||||
|
||||
<button
|
||||
aria-label="Stop"
|
||||
className={styles.stop}
|
||||
onClick={() => onAction("stop")}
|
||||
disabled={loading}
|
||||
>⏹</button>
|
||||
><Stop fill={"white"} /></button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -122,8 +122,8 @@ describe('MonitoringPage', () => {
|
||||
describe('Control Buttons', () => {
|
||||
test('Pause calls API and updates UI', async () => {
|
||||
render(<MonitoringPage />);
|
||||
const pauseBtn = screen.getByText('❚❚');
|
||||
|
||||
const pauseBtn = screen.getByRole('button', { name: /pause/i });
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(pauseBtn);
|
||||
});
|
||||
@@ -134,8 +134,8 @@ describe('MonitoringPage', () => {
|
||||
|
||||
test('Play calls API and updates UI', async () => {
|
||||
render(<MonitoringPage />);
|
||||
const playBtn = screen.getByText('▶');
|
||||
|
||||
const playBtn = screen.getByRole('button', { name: /play/i });
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(playBtn);
|
||||
});
|
||||
@@ -146,35 +146,35 @@ describe('MonitoringPage', () => {
|
||||
test('Next Phase calls API', async () => {
|
||||
render(<MonitoringPage />);
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('⏭'));
|
||||
fireEvent.click(screen.getByRole('button', { name: /next phase/i }));
|
||||
});
|
||||
expect(MonitoringAPI.nextPhase).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Reset Experiment calls logic and resets state', async () => {
|
||||
render(<MonitoringPage />);
|
||||
|
||||
|
||||
// Mock graph reducer return
|
||||
(VisProg.graphReducer as jest.Mock).mockReturnValue([{ id: 'new-phase' }]);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('⟲'));
|
||||
fireEvent.click(screen.getByRole('button', { name: /reset experiment/i }));
|
||||
});
|
||||
|
||||
expect(VisProg.graphReducer).toHaveBeenCalled();
|
||||
expect(mockSetProgramState).toHaveBeenCalledWith({ phases: [{ id: 'new-phase' }] });
|
||||
expect(VisProg.runProgram).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
test('Reset Experiment handles errors gracefully', async () => {
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
(VisProg.runProgram as jest.Mock).mockRejectedValue(new Error('Fail'));
|
||||
|
||||
|
||||
render(<MonitoringPage />);
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('⟲'));
|
||||
fireEvent.click(screen.getByRole('button', { name: /reset experiment/i }));
|
||||
});
|
||||
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Failed to reset program:', expect.any(Error));
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user