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()}
-
-
+