diff --git a/apps/visr/src/components/spectroscopy/ControlsDrawer.tsx b/apps/visr/src/components/spectroscopy/ControlsDrawer.tsx new file mode 100644 index 00000000..2090452e --- /dev/null +++ b/apps/visr/src/components/spectroscopy/ControlsDrawer.tsx @@ -0,0 +1,55 @@ +import { Drawer, Box, IconButton, Typography } from "@mui/material"; +import UnfoldLessIcon from "@mui/icons-material/UnfoldLess"; +import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore"; +import { SpectroscopyForm } from "./SpectroscopyForm"; + +interface ControlsDrawerProps { + open: boolean; + collapsedHeight: number; + onToggle: () => void; +} + +function ControlsDrawer({ + open, + collapsedHeight, + onToggle, +}: ControlsDrawerProps) { + return ( + + + Controls + + {open ? : } + + + + {open ? : } + + + ); +} + +export default ControlsDrawer; diff --git a/apps/visr/src/components/spectroscopy/PlotFrame.tsx b/apps/visr/src/components/spectroscopy/PlotFrame.tsx new file mode 100644 index 00000000..33f454ee --- /dev/null +++ b/apps/visr/src/components/spectroscopy/PlotFrame.tsx @@ -0,0 +1,111 @@ +import { Box } from "@mui/material"; +import { Children, type ReactNode } from "react"; + +const DEFAULT_GAP_PX = 8; +const TEST_BGCOLOR = "#ff000033"; + +export interface PlotFrameProps { + /** Drives the layout: `true` → contained 2x2 grid, `false` → single filling row. */ + expanded: boolean; + /** Plot elements to lay out. */ + children: ReactNode; + /** Cell aspectRatio ratio as a CSS `aspectRatio-ratio` string, e.g. `"4 / 3"`. */ + aspectRatio: string; + /** Gap between cells, in pixels. */ + gap?: number; + /** Cell background colour (any CSS colour). */ + bgcolor?: string; +} + +/** + * Lays out plot children in one of two states: + * - expanded: a contained 2x2 grid — every cell keeps aspectRatio ratio and the + * whole grid fits inside its parent, leaving dead space if needed. + * - collapsed: a single row that fills the width; the height follows the ratio + * and the root is content-height, so a sibling (e.g. a drawer) + * can take the remaining space. + * + * Layout-only and plot-agnostic: pass already-created plot elements as children. + */ +export default function PlotFrame({ + expanded, + children, + aspectRatio, + gap = DEFAULT_GAP_PX, + bgcolor = TEST_BGCOLOR, +}: PlotFrameProps) { + const [rw, rh] = aspectRatio.split("/").map(n => parseFloat(n)); + const ar = rw / rh; + + // (expanded only): clamp the cell by the width fit and the height fit. + const cellW = `min((100cqw - ${gap}px) / 2, ${ar} * (100cqh - ${gap}px) / 2)`; + const cellH = `min((100cqh - ${gap}px) / 2, (100cqw - ${gap}px) / (2 * ${ar}))`; + + const cellSizeSx = expanded + ? { width: cellW, height: cellH } + : { flex: 1, aspectRatio }; + + return ( + + + {Children.map(children, child => ( + // Outer cell: layout-sized only. Its size comes purely from cellSizeSx, + // never from the plot's content. + + {/* Absolute fill: matches the cell exactly and is out of flow, so the + h5web canvas inside can't feed its size back into the cell. */} + + {child} + + + ))} + + + ); +} diff --git a/apps/visr/src/components/spectroscopy/RawSpectroscopyData.tsx b/apps/visr/src/components/spectroscopy/RawSpectroscopyData.tsx index 639be16b..adc226cb 100644 --- a/apps/visr/src/components/spectroscopy/RawSpectroscopyData.tsx +++ b/apps/visr/src/components/spectroscopy/RawSpectroscopyData.tsx @@ -1,7 +1,9 @@ import { ImagePlot, type NDT } from "@diamondlightsource/davidia"; -import Box from "@mui/material/Box"; import ndarray from "ndarray"; import { useSpectroscopyData, type RGBColour } from "./useSpectroscopyData"; +import PlotFrame from "./PlotFrame"; + +import { useMemo, type ComponentProps } from "react"; type RGBColor = "red" | "green" | "blue" | "gray"; @@ -71,47 +73,47 @@ async function fetchMap( return toNDT(mapResponse.values, colour); } -function RawSpectroscopyData() { - const { data } = useSpectroscopyData(fetchMap); +const CHANNELS = [ + { key: "red", label: "Red channel" }, + { key: "green", label: "Green channel" }, + { key: "blue", label: "Blue channel" }, + { key: "red", label: "Alpha channel" }, // using red channel again for now to stop typing errors +] as const; + +type ChannelKey = (typeof CHANNELS)[number]["key"]; +type PlotValues = ComponentProps["values"]; +export type SpectroscopyData = Partial>; + +interface RawSpectroscopyDataProps { + expanded: boolean; + plotAspectRatio: string; +} + +function RawSpectroscopyData({ + expanded, + plotAspectRatio, +}: RawSpectroscopyDataProps) { + + const { data: channels } = useSpectroscopyData(fetchMap); + + const plots = useMemo( + () => + CHANNELS.map(({ key }, i) => ( + + )), + [channels], + ); + return ( - - - - - - - + + {plots} + ); } diff --git a/apps/visr/src/components/spectroscopy/SpectroscopyView.tsx b/apps/visr/src/components/spectroscopy/SpectroscopyView.tsx index e5855c1c..1f865fdf 100644 --- a/apps/visr/src/components/spectroscopy/SpectroscopyView.tsx +++ b/apps/visr/src/components/spectroscopy/SpectroscopyView.tsx @@ -1,11 +1,11 @@ import { Box } from "@mui/material"; import RawSpectroscopyData from "./RawSpectroscopyData"; -import { SpectroscopyForm } from "./SpectroscopyForm"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useScanEvents } from "../../hooks/scanEvents"; import { useSubmitWorkflow } from "../../hooks/useSubmitWorkflow"; import { useInstrumentSession } from "../../context/instrumentSession/useInstrumentSession"; import { visitTextToVisit } from "../../utils/common"; +import ControlsDrawer from "./ControlsDrawer"; export type SpectroscopyFormData = { total_number_of_scan_points: number; @@ -16,6 +16,8 @@ export type SpectroscopyFormData = { }; function SpectroscopyView() { + const [drawerOpen, setDrawerOpen] = useState(true); + // set off workflow when scan ends const scanEvent = useScanEvents(); const { instrumentSession } = useInstrumentSession(); @@ -36,16 +38,29 @@ function SpectroscopyView() { } }); + const DRAWER_COLLAPSED_HEIGHT = 64; + const NAVBAR_HEIGHT = 32; + const PLOT_ASPECT_RATIO = "4 / 3"; + return ( - - + + setDrawerOpen(prev => !prev)} + /> ); }