diff --git a/frontend/public/psi_01_sn.png b/frontend/public/psi_01_sn.png new file mode 100644 index 000000000..d5ad912fd Binary files /dev/null and b/frontend/public/psi_01_sn.png differ diff --git a/frontend/public/psi_02_sn.png b/frontend/public/psi_02_sn.png new file mode 100644 index 000000000..f7183b7f4 Binary files /dev/null and b/frontend/public/psi_02_sn.png differ diff --git a/frontend/public/psi_03_sn.png b/frontend/public/psi_03_sn.png new file mode 100644 index 000000000..a21c6e4fc Binary files /dev/null and b/frontend/public/psi_03_sn.png differ diff --git a/frontend/public/psi_04_sn.png b/frontend/public/psi_04_sn.png new file mode 100644 index 000000000..597e61eb0 Binary files /dev/null and b/frontend/public/psi_04_sn.png differ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8a044e62b..0ac7c7ea3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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 ( + *': { breakInside: 'avoid', mb: 2, display: 'block' }, + }}> + {children} + + ); +} + // 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() { )}, { id: 'masks', label: 'Masks', icon: , render: () => ( - + - + )}, { id: 'processing', label: 'On-the-fly processing', icon: , render: () => ( - - - + + + + + + {/* ROI holds three wide side-by-side tables — keep it full width. */} )}, { id: 'output', label: 'Output', icon: , render: () => ( - + - + )}, { id: 'streaming', label: 'Streaming', icon: , render: () => ( - + - + )}, { id: 'calibration', label: 'JUNGFRAU calibration', icon: , disabled: _.isEmpty(s.calibration), render: () => ( @@ -238,7 +268,7 @@ function App() { '& .MuiDrawer-paper': { width: drawerWidth, boxSizing: 'border-box', bgcolor: drawerBg, borderRight: 'none' } }}> - + {sections.map((sec, i) => ( )} - + @@ -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', }}> + + + Developed at{' '} Paul Scherrer Institute {' '}(2019–2026). Main author:{' '} diff --git a/frontend/src/components/DetectorSettings.tsx b/frontend/src/components/DetectorSettings.tsx index b6aeef269..cfad21129 100644 --- a/frontend/src/components/DetectorSettings.tsx +++ b/frontend/src/components/DetectorSettings.tsx @@ -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 + // 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 + - - - - -
-


- { - setFrameTimeErr(err); - setS(prev => ({...prev, frame_time_us: val})); - }}/> -
- - - - - - { - setCountTimeErr(err); - setS(prev => ({...prev, count_time_us: val})); - }}/> - - - - - - { - setDetectorTriggerDelayErr(err); - setS(prev => ({...prev, detector_trigger_delay_ns: val})); - }} - sx={{width: 120}} - />   - - Timing mode - - -

-
- - - - Internal image generator (FPGA)
-
- - - -
- - - - - - { - setInternalFrameGeneratorImagesErr(err); - setS(prev => ({...prev, internal_frame_generator_images: val})); - }} - disabled={!s.internal_frame_generator} - fullWidth/> - - - - -
- - - - JUNGFRAU settings
-
- - - - - - } label="HG0"/> - - } label="Fixed G1"/> -
-
- - - - JUNGFRAU storage cells

-
- - - - - Count - -    - { - setStorageCellDelayErr(err); - setS(prev => ({...prev, jungfrau_storage_cell_delay_ns: val})); - }} - disabled={storageCellListValue === "1"} - fullWidth/> -

-
- - - - JUNGFRAU Pedestal

-
- - - - { - setPedestalG0FramesErr(err); - setS(prev => ({...prev, jungfrau_pedestal_g0_frames: val})); - }} - sx={{width: "80px"}}/>  - { - setPedestalG1FramesErr(err); - setS(prev => ({...prev, jungfrau_pedestal_g1_frames: val})); - }} - sx={{width: "80px"}}/>  - { - setPedestalG2FramesErr(err); - setS(prev => ({...prev, jungfrau_pedestal_g2_frames: val})); - }} - sx={{width: "80px"}}/> -  

-
- - - - - { - setPedestalMinImageCountErr(err); - setS(prev => ({...prev, jungfrau_pedestal_min_image_count: val})); - }} - sx={{width: "120px"}}/>

-
- - - - EIGER settings

-
- - - - - - + + + + { + setFrameTimeErr(err); + setS(prev => ({...prev, frame_time_us: val})); + }}/> + + { - if (!err) - setEigerThresholdKeVOld(val); - setEigerThresholdErr(err); - setS(prev => ({...prev, eiger_threshold_keV: val})); - }} - disabled={s.eiger_threshold_keV === undefined} - /> -   - - Read-out - -

-

+ setCountTimeErr(err); + setS(prev => ({...prev, count_time_us: val})); + }}/> +
+ + { + setDetectorTriggerDelayErr(err); + setS(prev => ({...prev, detector_trigger_delay_ns: val})); + }} + sx={{width: 120}} + /> + + Timing mode + + + + + Internal image generator (FPGA) + + + + + + { + setInternalFrameGeneratorImagesErr(err); + setS(prev => ({...prev, internal_frame_generator_images: val})); + }} + disabled={!s.internal_frame_generator} + fullWidth/> + + + + + + EIGER settings + + + + { + if (!err) + setEigerThresholdKeVOld(val); + setEigerThresholdErr(err); + setS(prev => ({...prev, eiger_threshold_keV: val})); + }} + disabled={s.eiger_threshold_keV === undefined} + /> +   + + Read-out + + + + - - - -    - -

+ + + + + JUNGFRAU settings + + + } label="HG0"/> + + } label="Fixed G1"/> + + + + + Count + + + { + setStorageCellDelayErr(err); + setS(prev => ({...prev, jungfrau_storage_cell_delay_ns: val})); + }} + disabled={storageCellListValue === "1"} + fullWidth/> + + JUNGFRAU Pedestal + + { + setPedestalG0FramesErr(err); + setS(prev => ({...prev, jungfrau_pedestal_g0_frames: val})); + }} + sx={{width: "80px"}}/> + { + setPedestalG1FramesErr(err); + setS(prev => ({...prev, jungfrau_pedestal_g1_frames: val})); + }} + sx={{width: "80px"}}/> + { + setPedestalG2FramesErr(err); + setS(prev => ({...prev, jungfrau_pedestal_g2_frames: val})); + }} + sx={{width: "80px"}}/> + + { + setPedestalMinImageCountErr(err); + setS(prev => ({...prev, jungfrau_pedestal_min_image_count: val})); + }} + sx={{width: "120px"}}/> + - + + +    + + } diff --git a/frontend/src/components/StatusBar.tsx b/frontend/src/components/StatusBar.tsx index 90852b0a7..40d524dea 100644 --- a/frontend/src/components/StatusBar.tsx +++ b/frontend/src/components/StatusBar.tsx @@ -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) { )} - - PSI Jungfraujoch + + Jungfraujoch - + {statusDescription()} - - +