Merge branch 'rc187-live-fix-frontend' into rc186-merge2
This commit is contained in:
commit
5202b5236d
13 files changed
+123
-55
No files matched your search
@@ -28,7 +28,7 @@ import _ from "lodash";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
|
||||
import StatusBar from "./components/StatusBar";
|
||||
import StatusBar, { StatusBarSpacer } from "./components/StatusBar";
|
||||
import ErrorMessage from "./components/ErrorMessage";
|
||||
import DataProcessingPlots from "./components/DataProcessingPlots";
|
||||
import MeasurementStatistics from "./components/MeasurementStatistics";
|
||||
@@ -164,7 +164,7 @@ function App() {
|
||||
<DataProcessingPlots height={550} type={plot_type.BKG_ESTIMATE}/>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<MeasurementStatistics s={s.measurement ?? {}}/>
|
||||
<MeasurementStatistics s={s.measurement ?? {}} measuring={isMeasuring}/>
|
||||
</Grid>
|
||||
<RugnuxStatus/>
|
||||
<Grid item xs={12} lg={8}>
|
||||
@@ -270,7 +270,7 @@ function App() {
|
||||
sx={{ width: drawerWidth, flexShrink: 0,
|
||||
'& .MuiDrawer-paper': { width: drawerWidth, boxSizing: 'border-box',
|
||||
bgcolor: drawer.bg, borderRight: 'none' } }}>
|
||||
<Toolbar/>
|
||||
<StatusBarSpacer/>
|
||||
<Box sx={{ overflow: 'auto', pb: 9 }}>
|
||||
{sections.map((sec, i) => (
|
||||
<List key={i} subheader={sec.subheader
|
||||
@@ -299,7 +299,7 @@ function App() {
|
||||
</Drawer>
|
||||
)}
|
||||
<Box component="main" sx={{ flexGrow: 1, p: 3, pb: '84px', minWidth: 0 }}>
|
||||
<Toolbar/>
|
||||
<StatusBarSpacer/>
|
||||
<ErrorMessage s={s.broker}/>
|
||||
<Box sx={{ maxWidth: activeItem.wide ? 3400 : 1300, mx: 'auto', mt: 2 }}>
|
||||
{activeItem.render?.()}
|
||||
|
||||
@@ -94,7 +94,7 @@ function DataCollection({frame_time_us}: MyProps) {
|
||||
value={tokens}
|
||||
sx={{width:"90%"}}
|
||||
/>
|
||||
<Stack direction="row" spacing={2} sx={{width: '90%'}}>
|
||||
<Stack direction={{xs: "column", sm: "row"}} spacing={2} sx={{width: '90%'}}>
|
||||
<NumberTextField
|
||||
label={"Number of images per trigger"}
|
||||
callback={(val: number, err: boolean) => {
|
||||
@@ -137,7 +137,7 @@ function DataCollection({frame_time_us}: MyProps) {
|
||||
default={s.images_per_file}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={2} sx={{width: '90%'}}>
|
||||
<Stack direction={{xs: "column", sm: "row"}} spacing={2} sx={{width: '90%'}}>
|
||||
<NumberTextField
|
||||
label={"Beam X [pxl]"}
|
||||
callback={(val: number, err: boolean) => {
|
||||
@@ -196,7 +196,7 @@ function DataCollection({frame_time_us}: MyProps) {
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
{mode === 'rotation' && (
|
||||
<Stack direction="row" spacing={2} sx={{width: '90%'}}>
|
||||
<Stack direction={{xs: "column", sm: "row"}} spacing={2} sx={{width: '90%'}}>
|
||||
<NumberTextField
|
||||
label="Start angle"
|
||||
callback={(val: number) => setGoniometer(prev => ({...prev, start: val}))}
|
||||
@@ -220,7 +220,7 @@ function DataCollection({frame_time_us}: MyProps) {
|
||||
</Stack>
|
||||
)}
|
||||
{mode === 'grid' && (
|
||||
<Stack direction="row" spacing={2} sx={{width: '90%'}}>
|
||||
<Stack direction={{xs: "column", sm: "row"}} spacing={2} sx={{width: '90%'}}>
|
||||
<NumberTextField
|
||||
label="Grid elements (fast dim.)"
|
||||
callback={(val: number) => setGrid(prev => ({...prev, n_fast: val}))}
|
||||
|
||||
@@ -3,11 +3,20 @@ import {ReactNode} from 'react';
|
||||
import {useQuery} from "@tanstack/react-query";
|
||||
import ShowChartIcon from "@mui/icons-material/ShowChart";
|
||||
import {azint_unit, plot_type, plot_unit_x} from "../client";
|
||||
import {getPreviewPlotOptions} from "../client/@tanstack/react-query.gen";
|
||||
import {getConfigRugnuxOptions, getPreviewPlotOptions, getRugnuxStatusOptions} from "../client/@tanstack/react-query.gen";
|
||||
import MultiLinePlotWrapper from "./MultiLinePlotWrapper";
|
||||
import EmptyState from "./EmptyState";
|
||||
import {useTokenRequired} from "../auth";
|
||||
|
||||
// Whether rugnux live is on a run: the per-image values then come with its first pass over the images,
|
||||
// not image by image. The same queries the rugnux card polls, so this costs no request of its own.
|
||||
function useRugnuxRunning() {
|
||||
const {data: settings} = useQuery({...getConfigRugnuxOptions(), refetchInterval: 2000});
|
||||
const enabled = settings?.enable === true;
|
||||
const {data: s} = useQuery({...getRugnuxStatusOptions(), refetchInterval: 1000, enabled});
|
||||
return enabled && s?.state === "Running";
|
||||
}
|
||||
|
||||
type MyProps = {
|
||||
type: plot_type;
|
||||
binning: number;
|
||||
@@ -108,6 +117,7 @@ function DataProcessingPlot({type, binning, angle, azint}: MyProps) {
|
||||
});
|
||||
|
||||
const tokenRequired = useTokenRequired();
|
||||
const rugnuxRunning = useRugnuxRunning();
|
||||
|
||||
if (isError
|
||||
|| (plots === undefined)
|
||||
@@ -116,6 +126,11 @@ function DataProcessingPlot({type, binning, angle, azint}: MyProps) {
|
||||
return <EmptyState icon={<ShowChartIcon fontSize="inherit"/>}
|
||||
text={tokenRequired ? "Dataset token required (key button in the top bar)" : "No plots available"}/>;
|
||||
|
||||
// a plot with no points (an image without a value is left out): nothing measured yet
|
||||
if (plots.plot.every(d => d.y.length === 0))
|
||||
return <EmptyState icon={<ShowChartIcon fontSize="inherit"/>}
|
||||
text={rugnuxRunning ? "Per-image values come with rugnux's first pass over the images" : "No values yet"}/>;
|
||||
|
||||
let data: PlotlyData = [];
|
||||
if ((plots.plot[0].z !== undefined)
|
||||
&& (plots.plot[0].z.length > 0)) {
|
||||
|
||||
@@ -31,11 +31,12 @@ function DataProcessingPlots({type: initialType, height}: MyProps) {
|
||||
setBinning(String(event.target.value).toString());
|
||||
};
|
||||
|
||||
return <Paper style={{textAlign: 'center'}} sx={{ height: height, width: "100%" }}>
|
||||
<Toolbar>
|
||||
// On a phone the toolbar wraps, the select above the three controls, and the card takes the height it needs
|
||||
return <Paper style={{textAlign: 'center'}} sx={{ height: {xs: "auto", md: height}, width: "100%" }}>
|
||||
<Toolbar sx={{ flexWrap: {xs: "wrap", md: "nowrap"} }}>
|
||||
|
||||
<Grid container sx={{ minWidth: 500 }} >
|
||||
<FormControl variant="standard" sx={{ m: 1, minWidth: 500 }}>
|
||||
<Grid container sx={{ minWidth: {md: 500} }} >
|
||||
<FormControl variant="standard" sx={{ m: 1, minWidth: {xs: 200, md: 500} }}>
|
||||
|
||||
<Select
|
||||
value={tab}
|
||||
@@ -124,7 +125,7 @@ function DataProcessingPlots({type: initialType, height}: MyProps) {
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Toolbar>
|
||||
<Box sx={{width:"95%", height: height - 150}} >
|
||||
<Box sx={{width:"95%", height: {xs: 320, md: height - 150}}} >
|
||||
<DataProcessingPlot type={type} binning={Number(binning)} angle={angleUnits}
|
||||
azint={azintUnit}/>
|
||||
</Box>
|
||||
|
||||
@@ -7,9 +7,14 @@ import SettingsPanel from "./SettingsPanel";
|
||||
|
||||
type MyProps = {
|
||||
s: measurement_statistics,
|
||||
measuring?: boolean,
|
||||
};
|
||||
|
||||
function MeasurementStatistics({s}: MyProps) {
|
||||
function MeasurementStatistics({s, measuring}: MyProps) {
|
||||
// the efficiency is over the images expected, so while the collection runs it is its progress
|
||||
const efficiency = s.collection_efficiency === undefined ? "-"
|
||||
: measuring ? `${(100 * s.collection_efficiency).toFixed(0)} % so far`
|
||||
: (Math.floor(s.collection_efficiency * 1000.0) / 1000.0).toFixed(3);
|
||||
return (
|
||||
<SettingsPanel title="Measurement statistics" sx={{ minHeight: 550 }}>
|
||||
<TableContainer component={Paper} style={{marginLeft: "auto", marginRight: "auto"}}>
|
||||
@@ -51,9 +56,8 @@ function MeasurementStatistics({s}: MyProps) {
|
||||
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row"> Data acquisition efficiency: </TableCell>
|
||||
<TableCell align="right">{(s.collection_efficiency !== undefined)
|
||||
? (Math.floor(s.collection_efficiency * 1000.0) / 1000.0).toFixed(3) : "-"}</TableCell>
|
||||
<TableCell component="th" scope="row"> {measuring ? "Data acquisition progress:" : "Data acquisition efficiency:"} </TableCell>
|
||||
<TableCell align="right">{efficiency}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row"> Indexing rate: </TableCell>
|
||||
|
||||
@@ -4,9 +4,25 @@
|
||||
// mapbox, geo, ...) which dominates the JS payload.
|
||||
// Not using TypeScript, as plotly is not TypeScript :(
|
||||
|
||||
import {useEffect, useRef} from "react";
|
||||
import createPlotlyComponent from "react-plotly.js/factory";
|
||||
import Plotly from "plotly.js-cartesian-dist-min";
|
||||
|
||||
const Plot = createPlotlyComponent(Plotly);
|
||||
const PlotlyPlot = createPlotlyComponent(Plotly);
|
||||
|
||||
// Plotly's `responsive` config follows the window only; a plot's own box also changes when the
|
||||
// drawer opens or closes, so the plot is redrawn to its box whenever that resizes.
|
||||
function Plot(props) {
|
||||
const observer = useRef(null);
|
||||
useEffect(() => () => observer.current?.disconnect(), []);
|
||||
const onInitialized = (figure, graphDiv) => {
|
||||
observer.current?.disconnect();
|
||||
// a hidden plot cannot be resized: Plotly rejects, and the redraw comes with the next data
|
||||
observer.current = new ResizeObserver(() => Plotly.Plots.resize(graphDiv).catch(() => {}));
|
||||
observer.current.observe(graphDiv);
|
||||
props.onInitialized?.(figure, graphDiv);
|
||||
};
|
||||
return <PlotlyPlot {...props} onInitialized={onInitialized}/>;
|
||||
}
|
||||
|
||||
export default Plot;
|
||||
@@ -282,7 +282,7 @@ function PreviewImage({measuring, min_image_number, max_image_number}: MyProps)
|
||||
|
||||
<Grid container spacing={3}>
|
||||
|
||||
<Grid item xs={8}>
|
||||
<Grid item xs={12} md={8}>
|
||||
<Paper sx={{width: "100%", height: "100%", p: 1,
|
||||
display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
{(!connectionError && (sUrl !== null)) ?
|
||||
@@ -302,7 +302,7 @@ function PreviewImage({measuring, min_image_number, max_image_number}: MyProps)
|
||||
}
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={12} md={4}>
|
||||
<Paper sx={{width: "100%", height: "100%"}}>
|
||||
<br/>
|
||||
<Stack spacing={2} direction="column" sx={{
|
||||
|
||||
@@ -41,6 +41,12 @@ function RugnuxCollectionPlots({s, height}: { s: rugnux_live_status, height: num
|
||||
const indexing = useCollectionPlot(plot_type.INDEXING_RATE, s);
|
||||
const scales = s.frame_scales;
|
||||
const images = Math.max(s.images_expected, s.images_received, 1);
|
||||
// Before the first point: the images are in but not yet looked at - a serial run holds them for the
|
||||
// pre-pass, a rotation run's per-image values come with rugnux's first pass
|
||||
const noPoints = s.state !== "Running" ? "no images analysed"
|
||||
: s.images_received === 0 ? "waiting for images"
|
||||
: s.stills ? `${s.images_received.toLocaleString()} images held for the pre-pass`
|
||||
: `${s.images_received.toLocaleString()} images in · spots and background with rugnux's first pass`;
|
||||
|
||||
const common = {paper_bgcolor: "rgba(0,0,0,0)", plot_bgcolor: "rgba(0,0,0,0)", font, height, margin: PLOT_MARGIN,
|
||||
showlegend: false, xaxis: {...axis, range: [0, images - 1], title: {text: "image", standoff: 2, font: {size: size - 1}}}};
|
||||
@@ -58,7 +64,7 @@ function RugnuxCollectionPlots({s, height}: { s: rugnux_live_status, height: num
|
||||
}));
|
||||
|
||||
return (
|
||||
<Box sx={{display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1.5}}>
|
||||
<Box sx={{display: "grid", gridTemplateColumns: {xs: "1fr", sm: "1fr 1fr"}, gap: 1.5}}>
|
||||
<Box sx={panelSx}>
|
||||
{spots || bkg
|
||||
? <Plot style={{width: "100%", height}} config={config}
|
||||
@@ -72,7 +78,7 @@ function RugnuxCollectionPlots({s, height}: { s: rugnux_live_status, height: num
|
||||
yaxis: {...axis, rangemode: "tozero", title: title("spots", pal.traces[0])},
|
||||
yaxis2: {overlaying: "y", side: "right", rangemode: "tozero", showgrid: false, tickfont: {size: size - 1},
|
||||
title: title("background", pal.traces[1])}}}/>
|
||||
: <Empty text="waiting for images"/>}
|
||||
: <Empty text={noPoints}/>}
|
||||
</Box>
|
||||
<Box sx={panelSx}>
|
||||
{s.stills
|
||||
|
||||
@@ -118,7 +118,8 @@ function ModelTile({s, modelLoaded}: { s: rugnux_live_status, modelLoaded: boole
|
||||
return <Tile value="—" caption="Model" tone="plain" tip="The run ended without placing the model - the steps and warnings say why."/>;
|
||||
}
|
||||
|
||||
// column: the tiles as a rail, top to bottom, sharing the rail's height
|
||||
// column: the tiles as a rail, top to bottom, sharing the rail's height (two or three to a row on a phone,
|
||||
// where the rail is the width of the screen)
|
||||
function RugnuxHero({s, modelLoaded, column}: { s: rugnux_live_status, modelLoaded: boolean, column?: boolean }) {
|
||||
// the numbers are of a merge that exists: rotation - the run is over; serial - a provisional merge is in
|
||||
const merged = s.d_min !== undefined || (s.shells !== undefined && s.shells.length > 0);
|
||||
@@ -133,7 +134,8 @@ function RugnuxHero({s, modelLoaded, column}: { s: rugnux_live_status, modelLoad
|
||||
|
||||
return (
|
||||
<Box sx={{display: "grid", gap: 1, minHeight: 0, ...(column
|
||||
? {gridTemplateColumns: "1fr", gridAutoRows: "minmax(0, 1fr)", flexGrow: 1, "& > *": {display: "flex", flexDirection: "column", justifyContent: "center"}}
|
||||
? {gridTemplateColumns: {xs: "repeat(2, minmax(0, 1fr))", sm: "repeat(3, minmax(0, 1fr))", md: "1fr"}, gridAutoRows: {md: "minmax(0, 1fr)"}, flexGrow: 1,
|
||||
"& > *": {display: "flex", flexDirection: "column", justifyContent: "center"}}
|
||||
: {gridTemplateColumns: {xs: "repeat(3, minmax(0, 1fr))", md: "repeat(9, minmax(0, 1fr))"}})}}>
|
||||
<Tile value={num(o.d_min, x => `${x.toFixed(2)} Å`)} caption="Resolution" tone={pending}
|
||||
tip="High-resolution limit of the merge (d_min)."/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Alert, Box, Button, Collapse, Paper, Stack, Typography, useTheme} from "@mui/material";
|
||||
import {Alert, Box, Button, Collapse, Paper, Stack, Typography, useMediaQuery, useTheme} from "@mui/material";
|
||||
import {keyframes} from "@mui/system";
|
||||
import {useQuery} from "@tanstack/react-query";
|
||||
import {getConfigRugnuxOptions, getRugnuxStatusOptions} from "../client/@tanstack/react-query.gen";
|
||||
@@ -47,7 +47,7 @@ function phase(s: rugnux_live_status, pal: LivePalette): { label: string, colour
|
||||
return lastImageTime(s) === undefined
|
||||
? {label: "Collecting", colour: pal.collecting} : {label: "Processing", colour: pal.processing};
|
||||
if (s.state === "Done")
|
||||
return s.verdict === "OK" ? {label: "Done", colour: pal.ok}
|
||||
return s.verdict === "OK" || !s.verdict ? {label: "Done", colour: pal.ok}
|
||||
: s.verdict === "WARNINGS" ? {label: "Done · warnings", colour: pal.attention}
|
||||
: {label: `Done · ${s.verdict.toLowerCase()}`, colour: pal.serious};
|
||||
if (s.state === "Failed")
|
||||
@@ -173,6 +173,9 @@ const RAIL = "clamp(200px, 12vw, 420px)";
|
||||
|
||||
function Dashboard({s, deadline_s, modelLoaded, measuring, pal}:
|
||||
{ s: rugnux_live_status, deadline_s: number, modelLoaded: boolean, measuring: boolean, pal: LivePalette }) {
|
||||
// Only on the fixed-height stage (xl) do the plots take their height from their box; below it the
|
||||
// stage flows and the boxes take their height from the plots, so a measured height would feed back.
|
||||
const stage = useMediaQuery(useTheme().breakpoints.up("xl"));
|
||||
const [plotsRef, plotsHeight] = useHeight();
|
||||
const [stripRef, stripHeight] = useHeight();
|
||||
const later = s.stills ? "after the first merge" : "after the run";
|
||||
@@ -196,11 +199,11 @@ function Dashboard({s, deadline_s, modelLoaded, measuring, pal}:
|
||||
<Division sx={{flex: "0 1 auto", maxHeight: "40%", overflowY: "auto", "& > *": {flexShrink: 0}}}>
|
||||
{s.triage && s.triage.length > 0 ? <RugnuxTriage items={s.triage}/> : <Later text={later}/>}
|
||||
</Division>
|
||||
<Division sx={{flex: "1 1 0"}} label={<>By resolution{s.stills && s.shells_merge && ` · ${s.shells_merge}`}</>}>
|
||||
<Box ref={plotsRef} sx={{flexGrow: 1, minHeight: {xs: 400, xl: 0}, display: "flex"}}>
|
||||
<Division sx={{flex: {xs: "none", xl: "1 1 0"}}} label={<>By resolution{s.stills && s.shells_merge && ` · ${s.shells_merge}`}</>}>
|
||||
<Box ref={plotsRef} sx={{flexGrow: 1, minHeight: {xs: 200, xl: 0}, display: "flex"}}>
|
||||
{s.shells && s.shells.length > 0
|
||||
? <Box sx={{width: "100%"}}>
|
||||
<RugnuxShellPlots shells={s.shells} height={Math.max(150, (plotsHeight - 16) / 2)}/>
|
||||
<RugnuxShellPlots shells={s.shells} height={stage ? Math.max(150, (plotsHeight - 16) / 2) : 220}/>
|
||||
</Box>
|
||||
: <Later text={later}/>}
|
||||
</Box>
|
||||
@@ -208,7 +211,7 @@ function Dashboard({s, deadline_s, modelLoaded, measuring, pal}:
|
||||
</Box>
|
||||
<Division label="Collection" sx={{gridColumn: {md: "1 / 3", xl: "2 / 4"}, gridRow: {xl: 2}, ...hairline, minHeight: {xs: 200, xl: 0}}}>
|
||||
<Box ref={stripRef} sx={{flexGrow: 1, minHeight: 0}}>
|
||||
<RugnuxCollectionPlots s={s} height={Math.max(130, stripHeight)}/>
|
||||
<RugnuxCollectionPlots s={s} height={stage ? Math.max(130, stripHeight) : 160}/>
|
||||
</Box>
|
||||
</Division>
|
||||
</Box>
|
||||
|
||||
@@ -124,8 +124,9 @@ function RugnuxProgress({s, deadline_s}: { s: rugnux_live_status, deadline_s: nu
|
||||
kinds.push({tag: tags[i], titles: [st.title], seconds: duration, open});
|
||||
});
|
||||
|
||||
// On a phone the details go under the bars, which take the whole width
|
||||
return (
|
||||
<Box sx={{display: "grid", gridTemplateColumns: "72px minmax(0, 1fr) auto", columnGap: 2, alignItems: "start"}}>
|
||||
<Box sx={{display: "grid", gridTemplateColumns: {xs: "72px minmax(0, 1fr)", sm: "72px minmax(0, 1fr) auto"}, columnGap: 2, alignItems: "start"}}>
|
||||
{/* captions */}
|
||||
<Box sx={{display: "grid", rowGap: `${GAP}px`}}>
|
||||
<Typography sx={{...LABEL_SX, color: pal.collecting}}>Detector</Typography>
|
||||
@@ -186,7 +187,7 @@ function RugnuxProgress({s, deadline_s}: { s: rugnux_live_status, deadline_s: nu
|
||||
</Box>
|
||||
|
||||
{/* details */}
|
||||
<Box sx={{display: "grid", rowGap: `${GAP}px`, textAlign: "right"}}>
|
||||
<Box sx={{display: "grid", rowGap: `${GAP}px`, textAlign: "right", gridColumn: {xs: "1 / 3", sm: "auto"}}}>
|
||||
<Typography noWrap sx={{fontSize: "0.85rem", lineHeight: `${BAR}px`, color: "text.secondary", fontVariantNumeric: "tabular-nums"}}>
|
||||
{detectorDetail}
|
||||
</Typography>
|
||||
@@ -197,7 +198,7 @@ function RugnuxProgress({s, deadline_s}: { s: rugnux_live_status, deadline_s: nu
|
||||
|
||||
{/* the kinds of step, each with its time */}
|
||||
{kinds.length > 0 &&
|
||||
<Box sx={{gridColumn: "2 / 4", display: "flex", flexWrap: "wrap", gap: 0.75, mt: 0.25}}>
|
||||
<Box sx={{gridColumn: {xs: "1 / 3", sm: "2 / 4"}, display: "flex", flexWrap: "wrap", gap: 0.75, mt: 0.25}}>
|
||||
{kinds.map((k, i) => (
|
||||
<Tooltip key={i} arrow title={k.titles.join(" · ")}>
|
||||
<Box sx={{display: "inline-flex", alignItems: "center", gap: 0.75, px: 1, height: 22, borderRadius: "11px",
|
||||
|
||||
@@ -5,12 +5,15 @@ import { SxProps, Theme } from "@mui/material/styles";
|
||||
import { SystemStyleObject } from "@mui/system";
|
||||
import { TypographyProps } from "@mui/material/Typography";
|
||||
import { UploadSnackbar } from "./useUpload";
|
||||
import { drawerColours } from "../theme";
|
||||
|
||||
/** The panel title row: a light-blue (sidebar) band with a lime left accent, echoing
|
||||
* the selected sidebar entry so the cards feel part of the same system. Exported so
|
||||
* the selected sidebar entry so the cards feel part of the same system; at night the
|
||||
* selected entry's indigo tint, which the light text reads on. Exported so
|
||||
* DetectorSettings (which doesn't use SettingsPanel) can match. */
|
||||
export const panelHeaderSx = {
|
||||
px: 3, py: 1.75, bgcolor: indigo[50], borderLeft: "4px solid", borderColor: "secondary.main",
|
||||
px: 3, py: 1.75, borderLeft: "4px solid", borderColor: "secondary.main",
|
||||
bgcolor: (t: Theme) => t.palette.mode === "dark" ? drawerColours("dark").selected : indigo[50],
|
||||
};
|
||||
|
||||
/** Uniform look for the inline <b> section labels used inside the config panels:
|
||||
|
||||
@@ -132,6 +132,18 @@ function TokenButton() {
|
||||
</>;
|
||||
}
|
||||
|
||||
// On a phone the three action buttons do not fit beside the status pill: they get a second, dense row of
|
||||
// the bar, and the wordmark goes. The spacer below reserves the bar's height for the page.
|
||||
const phoneRow = {xs: "flex", sm: "none"} as const;
|
||||
const desktopRow = {xs: "none", sm: "flex"} as const;
|
||||
|
||||
export function StatusBarSpacer() {
|
||||
return <>
|
||||
<Toolbar/>
|
||||
<Toolbar variant="dense" sx={{display: phoneRow}}/>
|
||||
</>;
|
||||
}
|
||||
|
||||
function StatusBar({s, onMenuClick, mode, onModeClick}: MyProps) {
|
||||
// Pick each button's tier from the current state, so the one action that makes
|
||||
// sense right now is lime and the rest recede.
|
||||
@@ -142,6 +154,27 @@ function StatusBar({s, onMenuClick, mode, onModeClick}: MyProps) {
|
||||
: (st === 'Idle') ? 'active' : 'off';
|
||||
const pedestalActive = st === 'Idle';
|
||||
|
||||
const actions = (
|
||||
<Stack direction="row" spacing={1}>
|
||||
<ButtonWithSnackbar
|
||||
text={"Cancel"}
|
||||
path={"/cancel"}
|
||||
{...TIER[cancelTier]}
|
||||
/>
|
||||
<ButtonWithSnackbar
|
||||
text={"Calibrate"}
|
||||
path={"/pedestal"}
|
||||
disabled={!pedestalActive}
|
||||
{...(pedestalActive ? TIER.active : TIER.off)}
|
||||
/>
|
||||
<ButtonWithSnackbar
|
||||
text={"Initialize"}
|
||||
path={"/initialize"}
|
||||
{...TIER[initTier]}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
return <AppBar sx={{zIndex: (theme) => theme.zIndex.drawer + 1}}>
|
||||
<Toolbar>
|
||||
{onMenuClick && (
|
||||
@@ -150,7 +183,7 @@ function StatusBar({s, onMenuClick, mode, onModeClick}: MyProps) {
|
||||
</IconButton>
|
||||
)}
|
||||
<Typography variant="h6" component="div" noWrap
|
||||
sx={{flexGrow: 0.5, fontWeight: 500, letterSpacing: '.02em'}}>
|
||||
sx={{flexGrow: 0.5, fontWeight: 500, letterSpacing: '.02em', display: {xs: 'none', sm: 'block'}}}>
|
||||
Jungfraujoch
|
||||
</Typography>
|
||||
|
||||
@@ -167,25 +200,9 @@ function StatusBar({s, onMenuClick, mode, onModeClick}: MyProps) {
|
||||
</Tooltip>
|
||||
)}
|
||||
<TokenButton/>
|
||||
<Stack direction="row" spacing={1} sx={{ml: 1}}>
|
||||
<ButtonWithSnackbar
|
||||
text={"Cancel"}
|
||||
path={"/cancel"}
|
||||
{...TIER[cancelTier]}
|
||||
/>
|
||||
<ButtonWithSnackbar
|
||||
text={"Calibrate"}
|
||||
path={"/pedestal"}
|
||||
disabled={!pedestalActive}
|
||||
{...(pedestalActive ? TIER.active : TIER.off)}
|
||||
/>
|
||||
<ButtonWithSnackbar
|
||||
text={"Initialize"}
|
||||
path={"/initialize"}
|
||||
{...TIER[initTier]}
|
||||
/>
|
||||
</Stack>
|
||||
<Box sx={{ml: 1, display: desktopRow}}>{actions}</Box>
|
||||
</Toolbar>
|
||||
<Toolbar variant="dense" sx={{display: phoneRow, justifyContent: 'center', pb: 0.5}}>{actions}</Toolbar>
|
||||
</AppBar>
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user