frontend: PSI logo in footer + wider, shorter settings pages
Add PSI branding and use the horizontal space on the expert pages, which previously stacked panels into one tall narrow column. - Footer carries the official PSI logo (one of four variants picked at page load), standalone with clear space, linking to psi.ch. Top bar is a clean "Jungfraujoch" wordmark. - PanelColumns masonry lays the processing/output/streaming/masks panels into responsive columns; ROI stays full width. - DetectorSettings regrouped from a fixed-height single column into a responsive two-column form (layout only; controls unchanged). - Softer corners (theme borderRadius 4 -> 8). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
+48
-12
@@ -66,13 +66,40 @@ const drawerWidth = 240;
|
||||
const drawerBg = indigo[50];
|
||||
const drawerSelectedBg = indigo[100];
|
||||
|
||||
// PSI branding sits alone in the footer credit bar with clear space (never merged
|
||||
// with the app wordmark or menu icon). The four official logos differ only in the
|
||||
// diffused spots; pick one at module load so it is fixed per page load and never
|
||||
// re-randomises on render. BASE_URL keeps the path correct under the /frontend/ base.
|
||||
// The assets are the white "negative" logos; `brightness(0)` renders them as the
|
||||
// approved solid-black logo for the light footer.
|
||||
const PSI_LOGOS = [1, 2, 3, 4].map(i => `${import.meta.env.BASE_URL}psi_0${i}_sn.png`);
|
||||
const PSI_LOGO = PSI_LOGOS[Math.floor(Math.random() * PSI_LOGOS.length)];
|
||||
|
||||
const jfjoch_theme = createTheme({
|
||||
palette: {
|
||||
primary: indigo,
|
||||
secondary: lime,
|
||||
},
|
||||
// Slightly rounder corners than the MUI default (4px) give panels, inputs and
|
||||
// buttons a softer, more modern feel without changing any component's behaviour.
|
||||
shape: { borderRadius: 8 },
|
||||
});
|
||||
|
||||
/** Flow a page's settings panels into responsive columns (masonry) so a page of
|
||||
* small panels reads wide-and-short instead of as one tall single-file column.
|
||||
* Panels never split across a column break. */
|
||||
function PanelColumns({ columns = 2, children }: { columns?: number; children: ReactNode }) {
|
||||
return (
|
||||
<Box sx={{
|
||||
columnCount: { xs: 1, md: columns },
|
||||
columnGap: 2,
|
||||
'& > *': { breakInside: 'avoid', mb: 2, display: 'block' },
|
||||
}}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// An item either renders a panel inside the app (render) or links out to an
|
||||
// external page in a new tab (href), e.g. the source repository.
|
||||
type NavItem = { id: string; label: string; icon: ReactNode; disabled?: boolean;
|
||||
@@ -173,31 +200,34 @@ function App() {
|
||||
<DetectorSelection s={s.detector_list}/>
|
||||
)},
|
||||
{ id: 'masks', label: 'Masks', icon: <GridOnIcon/>, render: () => (
|
||||
<Stack spacing={2}>
|
||||
<PanelColumns>
|
||||
<PixelMask s={s.pixel_mask}/>
|
||||
<DarkMaskSettings s={s.dark_mask}/>
|
||||
</Stack>
|
||||
</PanelColumns>
|
||||
)},
|
||||
{ id: 'processing', label: 'On-the-fly processing', icon: <BoltIcon/>, render: () => (
|
||||
<Stack spacing={2}>
|
||||
<IndexingSettings s={s.indexing} status={s.broker}/>
|
||||
<BraggIntegrationSettings s={s.bragg_integration}/>
|
||||
<AzIntSettings s={s.az_int}/>
|
||||
<PanelColumns>
|
||||
<IndexingSettings s={s.indexing} status={s.broker}/>
|
||||
<BraggIntegrationSettings s={s.bragg_integration}/>
|
||||
<AzIntSettings s={s.az_int}/>
|
||||
</PanelColumns>
|
||||
{/* ROI holds three wide side-by-side tables — keep it full width. */}
|
||||
<ROI s={s.roi}/>
|
||||
</Stack>
|
||||
)},
|
||||
{ id: 'output', label: 'Output', icon: <SaveIcon/>, render: () => (
|
||||
<Stack spacing={2}>
|
||||
<PanelColumns>
|
||||
<FileWriterSettings s={s.file_writer_settings}/>
|
||||
<ImageFormatSettings s={s.image_format_settings}/>
|
||||
<InstrumentMetadata s={s.instrument_metadata}/>
|
||||
</Stack>
|
||||
</PanelColumns>
|
||||
)},
|
||||
{ id: 'streaming', label: 'Streaming', icon: <WindPowerIcon/>, render: () => (
|
||||
<Stack spacing={2}>
|
||||
<PanelColumns>
|
||||
<ZeroMQPreview s={s.zeromq_preview}/>
|
||||
<ImagePusherStatus s={s.image_pusher}/>
|
||||
</Stack>
|
||||
</PanelColumns>
|
||||
)},
|
||||
{ id: 'calibration', label: 'JUNGFRAU calibration', icon: <StraightenIcon/>,
|
||||
disabled: _.isEmpty(s.calibration), render: () => (
|
||||
@@ -238,7 +268,7 @@ function App() {
|
||||
'& .MuiDrawer-paper': { width: drawerWidth, boxSizing: 'border-box',
|
||||
bgcolor: drawerBg, borderRight: 'none' } }}>
|
||||
<Toolbar/>
|
||||
<Box sx={{ overflow: 'auto', pb: 6 }}>
|
||||
<Box sx={{ overflow: 'auto', pb: 9 }}>
|
||||
{sections.map((sec, i) => (
|
||||
<List key={i} subheader={sec.subheader
|
||||
? <ListSubheader sx={{ bgcolor: 'transparent', color: 'primary.dark',
|
||||
@@ -265,7 +295,7 @@ function App() {
|
||||
</Box>
|
||||
</Drawer>
|
||||
)}
|
||||
<Box component="main" sx={{ flexGrow: 1, p: 3, pb: '64px', minWidth: 0 }}>
|
||||
<Box component="main" sx={{ flexGrow: 1, p: 3, pb: '84px', minWidth: 0 }}>
|
||||
<Toolbar/>
|
||||
<ErrorMessage s={s.broker}/>
|
||||
<Box sx={{ maxWidth: 1300, mx: 'auto', mt: 2 }}>
|
||||
@@ -277,8 +307,14 @@ function App() {
|
||||
position: 'fixed', bottom: 0, left: 0, right: 0,
|
||||
zIndex: (t) => t.zIndex.drawer + 1,
|
||||
bgcolor: 'background.paper', borderTop: '1px solid', borderColor: 'divider',
|
||||
py: 0.5, px: 2, textAlign: 'center', fontSize: '0.8rem',
|
||||
py: 1.5, px: 2, textAlign: 'center', fontSize: '0.8rem',
|
||||
}}>
|
||||
<Link href="https://www.psi.ch" target="_blank" rel="noopener" aria-label="Paul Scherrer Institute"
|
||||
sx={{ position: 'absolute', left: 24, top: '50%', transform: 'translateY(-50%)',
|
||||
display: 'inline-flex' }}>
|
||||
<Box component="img" src={PSI_LOGO} alt="PSI"
|
||||
sx={{ height: 34, width: 'auto', filter: 'brightness(0)', opacity: 0.8 }}/>
|
||||
</Link>
|
||||
Developed at{' '}
|
||||
<Link href="https://www.psi.ch" target="_blank" rel="noopener">Paul Scherrer Institute</Link>
|
||||
{' '}(2019–2026). Main author:{' '}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {ChangeEvent, memo, useEffect, useState} from 'react';
|
||||
|
||||
import {Box, Checkbox, FormControlLabel, FormGroup, Grid, List, ListItem, Switch, Tooltip} from "@mui/material";
|
||||
import {Box, Checkbox, FormControlLabel, FormGroup, Grid, List, ListItem, Stack, Switch, Tooltip} from "@mui/material";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import FormControl from "@mui/material/FormControl";
|
||||
import InputLabel from "@mui/material/InputLabel";
|
||||
@@ -146,295 +146,251 @@ function DetectorSettings({s: serverS}: MyProps) {
|
||||
|
||||
const dirty = !_.isEqual(s, lastDownloadedS);
|
||||
|
||||
return <Paper style={{textAlign: 'center'}} sx={{ height: 1150, width: '100%' }}>
|
||||
// Two responsive columns keep this large form wide-and-short instead of one
|
||||
// very tall centred strip: acquisition/timing on the left, JUNGFRAU + EIGER on
|
||||
// the right, with the title on top and the action buttons spanning the bottom.
|
||||
return <Paper style={{textAlign: 'center'}} sx={{ p: 3, width: '100%' }}>
|
||||
<PanelTitle title="Detector settings" dirty={dirty}/>
|
||||
|
||||
<Grid container>
|
||||
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={10}>
|
||||
<br/><PanelTitle title="Detector settings" dirty={dirty}/>
|
||||
<br/><br/><br/>
|
||||
<NumberTextField start_val={s.frame_time_us}
|
||||
label={"Internal frame time"}
|
||||
units={"us"}
|
||||
min={450}
|
||||
fullWidth
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setFrameTimeErr(err);
|
||||
setS(prev => ({...prev, frame_time_us: val}));
|
||||
}}/>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={10}>
|
||||
<Box sx={{display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 1, my: 2}}>
|
||||
<Switch onChange={countTimeToggle}
|
||||
checked={s.count_time_us !== undefined}/>
|
||||
<NumberTextField
|
||||
start_val={s.count_time_us}
|
||||
disabled={s.count_time_us === undefined}
|
||||
label={"Count time"}
|
||||
units={"us"}
|
||||
min={0}
|
||||
max={1980}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setCountTimeErr(err);
|
||||
setS(prev => ({...prev, count_time_us: val}));
|
||||
}}/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={10}>
|
||||
<NumberTextField start_val={s.detector_trigger_delay_ns}
|
||||
label={"Detector delay"}
|
||||
units={"ns"}
|
||||
default={0}
|
||||
min={0}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setDetectorTriggerDelayErr(err);
|
||||
setS(prev => ({...prev, detector_trigger_delay_ns: val}));
|
||||
}}
|
||||
sx={{width: 120}}
|
||||
/>
|
||||
<FormControl>
|
||||
<InputLabel id="timing-select-label">Timing mode</InputLabel>
|
||||
<Select
|
||||
sx={{width: 120}}
|
||||
labelId="timing-select-label"
|
||||
id="timing-select"
|
||||
value={timingModeListValue}
|
||||
label="Storage cell count"
|
||||
onChange={handleTimingChange}
|
||||
>
|
||||
<MenuItem value={"trigger"}>Trigger</MenuItem>
|
||||
<MenuItem value={"auto"}>Auto</MenuItem>
|
||||
<MenuItem value={"burst"}>Burst (EIGER)</MenuItem>
|
||||
<MenuItem value={"gated"}>Gated (EIGER)</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<br/><br/>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={10}>
|
||||
<b>Internal image generator (FPGA) </b><br/>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={3}/>
|
||||
<Grid item xs={6}>
|
||||
<br/>
|
||||
<List>
|
||||
<Tooltip title="Internal generator allows to test Jungfraujoch without having detector connected.">
|
||||
<ListItem>
|
||||
<Checkbox onChange={internalFrameGeneratorToggle}
|
||||
checked={s.internal_frame_generator === true}/>
|
||||
<FormControl fullWidth>
|
||||
<NumberTextField start_val={s.internal_frame_generator_images}
|
||||
label={"Images"}
|
||||
min={1}
|
||||
default={1}
|
||||
max={128}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setInternalFrameGeneratorImagesErr(err);
|
||||
setS(prev => ({...prev, internal_frame_generator_images: val}));
|
||||
}}
|
||||
disabled={!s.internal_frame_generator}
|
||||
fullWidth/>
|
||||
</FormControl>
|
||||
</ListItem>
|
||||
</Tooltip>
|
||||
</List>
|
||||
</Grid>
|
||||
<Grid item xs={3}/>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={10}>
|
||||
<b>JUNGFRAU settings </b><br/>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={3}/>
|
||||
<Grid item xs={6}>
|
||||
<FormGroup>
|
||||
<FormControlLabel control={
|
||||
<Checkbox onChange={useGainHG0Toggle} checked={s.jungfrau_use_gain_hg0 === true}/>
|
||||
} label="HG0"/>
|
||||
<FormControlLabel control={
|
||||
<Checkbox onChange={fixedGainG1Toggle} checked={s.jungfrau_fixed_gain_g1 === true}/>
|
||||
} label="Fixed G1"/>
|
||||
</FormGroup><br/>
|
||||
</Grid>
|
||||
<Grid item xs={3}/>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={10}>
|
||||
<b>JUNGFRAU storage cells </b><br/><br/>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={10}>
|
||||
<FormControl>
|
||||
<InputLabel id="demo-simple-select-label">Count</InputLabel>
|
||||
<Select
|
||||
sx={{width: 120}}
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={storageCellListValue}
|
||||
label="Storage cell count"
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value={"1"}>1</MenuItem>
|
||||
<MenuItem value={"2"}>2</MenuItem>
|
||||
<MenuItem value={"4"}>4</MenuItem>
|
||||
<MenuItem value={"8"}>8</MenuItem>
|
||||
<MenuItem value={"15"}>15</MenuItem>
|
||||
<MenuItem value={"16"}>16</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<NumberTextField start_val={s.jungfrau_storage_cell_delay_ns}
|
||||
label={"Delay"}
|
||||
units={"ns"}
|
||||
min={2100}
|
||||
default={2100}
|
||||
counter={downloadCounter}
|
||||
sx={{ width: "140px" }}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setStorageCellDelayErr(err);
|
||||
setS(prev => ({...prev, jungfrau_storage_cell_delay_ns: val}));
|
||||
}}
|
||||
disabled={storageCellListValue === "1"}
|
||||
fullWidth/>
|
||||
<br/><br/>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={10}>
|
||||
<b>JUNGFRAU Pedestal</b><br/><br/>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={10}>
|
||||
<NumberTextField start_val={s.jungfrau_pedestal_g0_frames}
|
||||
label={"G0"}
|
||||
min={0}
|
||||
default={2000}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setPedestalG0FramesErr(err);
|
||||
setS(prev => ({...prev, jungfrau_pedestal_g0_frames: val}));
|
||||
}}
|
||||
sx={{width: "80px"}}/>
|
||||
<NumberTextField start_val={s.jungfrau_pedestal_g1_frames}
|
||||
label={"G1"}
|
||||
min={0}
|
||||
default={300}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setPedestalG1FramesErr(err);
|
||||
setS(prev => ({...prev, jungfrau_pedestal_g1_frames: val}));
|
||||
}}
|
||||
sx={{width: "80px"}}/>
|
||||
<NumberTextField start_val={s.jungfrau_pedestal_g2_frames}
|
||||
label={"G2"}
|
||||
min={0}
|
||||
default={300}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setPedestalG2FramesErr(err);
|
||||
setS(prev => ({...prev, jungfrau_pedestal_g2_frames: val}));
|
||||
}}
|
||||
sx={{width: "80px"}}/>
|
||||
<br/><br/>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={1}/>
|
||||
|
||||
<Grid item xs={10}>
|
||||
<NumberTextField start_val={s.jungfrau_pedestal_min_image_count}
|
||||
label={"Min. image count"}
|
||||
min={0}
|
||||
default={128}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setPedestalMinImageCountErr(err);
|
||||
setS(prev => ({...prev, jungfrau_pedestal_min_image_count: val}));
|
||||
}}
|
||||
sx={{width: "120px"}}/><br/><br/>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={10}>
|
||||
<b>EIGER settings </b><br/><br/>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={10}>
|
||||
<ListItem>
|
||||
<Checkbox onChange={eigerThresholdToggle}
|
||||
checked={s.eiger_threshold_keV !== undefined}/>
|
||||
<FormControl fullWidth>
|
||||
<Grid container spacing={4} sx={{ mt: 1 }}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Stack spacing={3} alignItems="center">
|
||||
<NumberTextField start_val={s.frame_time_us}
|
||||
label={"Internal frame time"}
|
||||
units={"us"}
|
||||
min={450}
|
||||
fullWidth
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setFrameTimeErr(err);
|
||||
setS(prev => ({...prev, frame_time_us: val}));
|
||||
}}/>
|
||||
<Box sx={{display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 1}}>
|
||||
<Switch onChange={countTimeToggle}
|
||||
checked={s.count_time_us !== undefined}/>
|
||||
<NumberTextField
|
||||
default={eigerThresholdKeVOld}
|
||||
start_val={s.eiger_threshold_keV}
|
||||
label={"Energy threshold"}
|
||||
min={1.0}
|
||||
max={100.0}
|
||||
units={"keV"}
|
||||
float={true}
|
||||
start_val={s.count_time_us}
|
||||
disabled={s.count_time_us === undefined}
|
||||
label={"Count time"}
|
||||
units={"us"}
|
||||
min={0}
|
||||
max={1980}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
if (!err)
|
||||
setEigerThresholdKeVOld(val);
|
||||
setEigerThresholdErr(err);
|
||||
setS(prev => ({...prev, eiger_threshold_keV: val}));
|
||||
}}
|
||||
disabled={s.eiger_threshold_keV === undefined}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<InputLabel id="demo-simple-select-label">Read-out</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
label="Detector"
|
||||
value={bitWidthSelection}
|
||||
onChange={handleBitWidthChange}
|
||||
sx={{minWidth:106}}
|
||||
>
|
||||
<MenuItem value={"a"}>Auto</MenuItem>
|
||||
<MenuItem value={"8"}>8-bit</MenuItem>
|
||||
<MenuItem value={"16"}>16-bit</MenuItem>
|
||||
<MenuItem value={"32"}>32-bit</MenuItem>
|
||||
</Select>
|
||||
</FormControl><br/><br/>
|
||||
</ListItem><br/>
|
||||
setCountTimeErr(err);
|
||||
setS(prev => ({...prev, count_time_us: val}));
|
||||
}}/>
|
||||
</Box>
|
||||
<Box sx={{display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 1}}>
|
||||
<NumberTextField start_val={s.detector_trigger_delay_ns}
|
||||
label={"Detector delay"}
|
||||
units={"ns"}
|
||||
default={0}
|
||||
min={0}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setDetectorTriggerDelayErr(err);
|
||||
setS(prev => ({...prev, detector_trigger_delay_ns: val}));
|
||||
}}
|
||||
sx={{width: 120}}
|
||||
/>
|
||||
<FormControl>
|
||||
<InputLabel id="timing-select-label">Timing mode</InputLabel>
|
||||
<Select
|
||||
sx={{width: 120}}
|
||||
labelId="timing-select-label"
|
||||
id="timing-select"
|
||||
value={timingModeListValue}
|
||||
label="Storage cell count"
|
||||
onChange={handleTimingChange}
|
||||
>
|
||||
<MenuItem value={"trigger"}>Trigger</MenuItem>
|
||||
<MenuItem value={"auto"}>Auto</MenuItem>
|
||||
<MenuItem value={"burst"}>Burst (EIGER)</MenuItem>
|
||||
<MenuItem value={"gated"}>Gated (EIGER)</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<b>Internal image generator (FPGA)</b>
|
||||
<List>
|
||||
<Tooltip title="Internal generator allows to test Jungfraujoch without having detector connected.">
|
||||
<ListItem>
|
||||
<Checkbox onChange={internalFrameGeneratorToggle}
|
||||
checked={s.internal_frame_generator === true}/>
|
||||
<FormControl fullWidth>
|
||||
<NumberTextField start_val={s.internal_frame_generator_images}
|
||||
label={"Images"}
|
||||
min={1}
|
||||
default={1}
|
||||
max={128}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setInternalFrameGeneratorImagesErr(err);
|
||||
setS(prev => ({...prev, internal_frame_generator_images: val}));
|
||||
}}
|
||||
disabled={!s.internal_frame_generator}
|
||||
fullWidth/>
|
||||
</FormControl>
|
||||
</ListItem>
|
||||
</Tooltip>
|
||||
</List>
|
||||
</Box>
|
||||
<b>EIGER settings</b>
|
||||
<ListItem>
|
||||
<Checkbox onChange={eigerThresholdToggle}
|
||||
checked={s.eiger_threshold_keV !== undefined}/>
|
||||
<FormControl fullWidth>
|
||||
<NumberTextField
|
||||
default={eigerThresholdKeVOld}
|
||||
start_val={s.eiger_threshold_keV}
|
||||
label={"Energy threshold"}
|
||||
min={1.0}
|
||||
max={100.0}
|
||||
units={"keV"}
|
||||
float={true}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
if (!err)
|
||||
setEigerThresholdKeVOld(val);
|
||||
setEigerThresholdErr(err);
|
||||
setS(prev => ({...prev, eiger_threshold_keV: val}));
|
||||
}}
|
||||
disabled={s.eiger_threshold_keV === undefined}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<InputLabel id="demo-simple-select-label">Read-out</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
label="Detector"
|
||||
value={bitWidthSelection}
|
||||
onChange={handleBitWidthChange}
|
||||
sx={{minWidth:106}}
|
||||
>
|
||||
<MenuItem value={"a"}>Auto</MenuItem>
|
||||
<MenuItem value={"8"}>8-bit</MenuItem>
|
||||
<MenuItem value={"16"}>16-bit</MenuItem>
|
||||
<MenuItem value={"32"}>32-bit</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</ListItem>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={1}/>
|
||||
<Grid item xs={10}>
|
||||
<Button color={dirty ? "warning" : "primary"} variant="contained" disableElevation
|
||||
onClick={() => submit(s)}
|
||||
disabled={pending
|
||||
|| countTimeErr
|
||||
|| frameTimeErr
|
||||
|| internalFrameGeneratorImagesErr
|
||||
|| detectorTriggerDelayErr
|
||||
|| storageCellDelayErr
|
||||
|| pedestalG0FramesErr
|
||||
|| pedestalG1FramesErr
|
||||
|| pedestalG2FramesErr
|
||||
|| pedestalMinImageCountErr}>Upload</Button>
|
||||
<ButtonWithSnackbar
|
||||
color={"primary"}
|
||||
path={"/deactivate"}
|
||||
text={"Deactivate"}
|
||||
/>
|
||||
<br/><br/>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
<Stack spacing={3} alignItems="center">
|
||||
<Box>
|
||||
<b>JUNGFRAU settings</b>
|
||||
<FormGroup>
|
||||
<FormControlLabel control={
|
||||
<Checkbox onChange={useGainHG0Toggle} checked={s.jungfrau_use_gain_hg0 === true}/>
|
||||
} label="HG0"/>
|
||||
<FormControlLabel control={
|
||||
<Checkbox onChange={fixedGainG1Toggle} checked={s.jungfrau_fixed_gain_g1 === true}/>
|
||||
} label="Fixed G1"/>
|
||||
</FormGroup>
|
||||
</Box>
|
||||
<Box sx={{display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 1}}>
|
||||
<FormControl>
|
||||
<InputLabel id="demo-simple-select-label">Count</InputLabel>
|
||||
<Select
|
||||
sx={{width: 120}}
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={storageCellListValue}
|
||||
label="Storage cell count"
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value={"1"}>1</MenuItem>
|
||||
<MenuItem value={"2"}>2</MenuItem>
|
||||
<MenuItem value={"4"}>4</MenuItem>
|
||||
<MenuItem value={"8"}>8</MenuItem>
|
||||
<MenuItem value={"15"}>15</MenuItem>
|
||||
<MenuItem value={"16"}>16</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<NumberTextField start_val={s.jungfrau_storage_cell_delay_ns}
|
||||
label={"Delay"}
|
||||
units={"ns"}
|
||||
min={2100}
|
||||
default={2100}
|
||||
counter={downloadCounter}
|
||||
sx={{ width: "140px" }}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setStorageCellDelayErr(err);
|
||||
setS(prev => ({...prev, jungfrau_storage_cell_delay_ns: val}));
|
||||
}}
|
||||
disabled={storageCellListValue === "1"}
|
||||
fullWidth/>
|
||||
</Box>
|
||||
<b>JUNGFRAU Pedestal</b>
|
||||
<Stack direction="row" spacing={1} justifyContent="center">
|
||||
<NumberTextField start_val={s.jungfrau_pedestal_g0_frames}
|
||||
label={"G0"}
|
||||
min={0}
|
||||
default={2000}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setPedestalG0FramesErr(err);
|
||||
setS(prev => ({...prev, jungfrau_pedestal_g0_frames: val}));
|
||||
}}
|
||||
sx={{width: "80px"}}/>
|
||||
<NumberTextField start_val={s.jungfrau_pedestal_g1_frames}
|
||||
label={"G1"}
|
||||
min={0}
|
||||
default={300}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setPedestalG1FramesErr(err);
|
||||
setS(prev => ({...prev, jungfrau_pedestal_g1_frames: val}));
|
||||
}}
|
||||
sx={{width: "80px"}}/>
|
||||
<NumberTextField start_val={s.jungfrau_pedestal_g2_frames}
|
||||
label={"G2"}
|
||||
min={0}
|
||||
default={300}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setPedestalG2FramesErr(err);
|
||||
setS(prev => ({...prev, jungfrau_pedestal_g2_frames: val}));
|
||||
}}
|
||||
sx={{width: "80px"}}/>
|
||||
</Stack>
|
||||
<NumberTextField start_val={s.jungfrau_pedestal_min_image_count}
|
||||
label={"Min. image count"}
|
||||
min={0}
|
||||
default={128}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setPedestalMinImageCountErr(err);
|
||||
setS(prev => ({...prev, jungfrau_pedestal_min_image_count: val}));
|
||||
}}
|
||||
sx={{width: "120px"}}/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
</Grid>
|
||||
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<Button color={dirty ? "warning" : "primary"} variant="contained" disableElevation
|
||||
onClick={() => submit(s)}
|
||||
disabled={pending
|
||||
|| countTimeErr
|
||||
|| frameTimeErr
|
||||
|| internalFrameGeneratorImagesErr
|
||||
|| detectorTriggerDelayErr
|
||||
|| storageCellDelayErr
|
||||
|| pedestalG0FramesErr
|
||||
|| pedestalG1FramesErr
|
||||
|| pedestalG2FramesErr
|
||||
|| pedestalMinImageCountErr}>Upload</Button>
|
||||
<ButtonWithSnackbar
|
||||
color={"primary"}
|
||||
path={"/deactivate"}
|
||||
text={"Deactivate"}
|
||||
/>
|
||||
</Box>
|
||||
<UploadSnackbarView snackbar={snackbar}/>
|
||||
</Paper>
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Box from '@mui/material/Box';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import {broker_status} from "../client";
|
||||
import ButtonWithSnackbar from "./ButtonWithSnackbar";
|
||||
@@ -40,16 +41,16 @@ function StatusBar({s, onMenuClick}: MyProps) {
|
||||
<MenuIcon/>
|
||||
</IconButton>
|
||||
)}
|
||||
<Typography variant="h6" style={{flexGrow: 0.5}}>
|
||||
PSI Jungfraujoch
|
||||
<Typography variant="h6" component="div" noWrap
|
||||
sx={{flexGrow: 0.5, fontWeight: 500, letterSpacing: '.02em'}}>
|
||||
Jungfraujoch
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6" style={{flexGrow: 2.0}}>
|
||||
<Typography variant="h6" component="div" sx={{flexGrow: 2.0, textAlign: 'center'}}>
|
||||
{statusDescription()}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6" style={{flexGrow: 2.0}}>
|
||||
</Typography>
|
||||
<Box sx={{flexGrow: 2.0}}/>
|
||||
<ButtonWithSnackbar
|
||||
text={"Pedestal"}
|
||||
path={"/pedestal"}
|
||||
|
||||
Reference in New Issue
Block a user