diff --git a/package-lock.json b/package-lock.json index a92f216..a03813e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,8 @@ "react-dom": "^18.2.0", "react-redux": "^8.1.2", "redux": "^4.2.1", - "three": "^0.155.0" + "three": "^0.155.0", + "zustand": "^4.4.1" }, "devDependencies": { "@types/react-redux": "^7.1.26", @@ -844,6 +845,29 @@ "react": "^16.3.0 || ^17.0.0" } }, + "node_modules/@h5web/lib/node_modules/zustand": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.7.tgz", + "integrity": "sha512-dY8ERwB9Nd21ellgkBZFhudER8KVlelZm8388B5nDAXhO/+FZDhYMuRnqDgu5SYyRgz/iaf8RKnbUs/cHfOGlQ==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "immer": ">=9.0", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", @@ -5734,9 +5758,9 @@ } }, "node_modules/zustand": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.7.tgz", - "integrity": "sha512-dY8ERwB9Nd21ellgkBZFhudER8KVlelZm8388B5nDAXhO/+FZDhYMuRnqDgu5SYyRgz/iaf8RKnbUs/cHfOGlQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.1.tgz", + "integrity": "sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==", "dependencies": { "use-sync-external-store": "1.2.0" }, @@ -5744,10 +5768,14 @@ "node": ">=12.7.0" }, "peerDependencies": { + "@types/react": ">=16.8", "immer": ">=9.0", "react": ">=16.8" }, "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, "immer": { "optional": true }, @@ -6266,6 +6294,14 @@ "prop-types": "^15.6.0", "teeny-tap": "^0.2.0" } + }, + "zustand": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.7.tgz", + "integrity": "sha512-dY8ERwB9Nd21ellgkBZFhudER8KVlelZm8388B5nDAXhO/+FZDhYMuRnqDgu5SYyRgz/iaf8RKnbUs/cHfOGlQ==", + "requires": { + "use-sync-external-store": "1.2.0" + } } } }, @@ -9694,9 +9730,9 @@ "dev": true }, "zustand": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.7.tgz", - "integrity": "sha512-dY8ERwB9Nd21ellgkBZFhudER8KVlelZm8388B5nDAXhO/+FZDhYMuRnqDgu5SYyRgz/iaf8RKnbUs/cHfOGlQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.1.tgz", + "integrity": "sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==", "requires": { "use-sync-external-store": "1.2.0" } diff --git a/package.json b/package.json index 748e92e..f5ac8c2 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "react-dom": "^18.2.0", "react-redux": "^8.1.2", "redux": "^4.2.1", - "three": "^0.155.0" + "three": "^0.155.0", + "zustand": "^4.4.1" }, "devDependencies": { "@types/react-redux": "^7.1.26", diff --git a/src/data-entry/beamstop.tsx b/src/data-entry/beamstop.tsx index e9683bb..5976eca 100644 --- a/src/data-entry/beamstop.tsx +++ b/src/data-entry/beamstop.tsx @@ -1,91 +1,119 @@ import { - Stack, - Typography, - FormControl, - Select, - MenuItem, - InputLabel, - Input, - Button, - ButtonGroup, - TextField, + Stack, + Typography, + FormControl, + Select, + MenuItem, + InputLabel, + Button, + TextField, } from "@mui/material"; -import { - DistanceUnits, -} from "../utils/units"; -import { useDispatch, useSelector } from "react-redux"; -import { - configSelector, - beamstopSelector, -} from "./configSlice"; -import { editUnits, unitSelector } from "./unitSlice"; -import RemoveIcon from "@mui/icons-material/Remove"; -import AddIcon from "@mui/icons-material/Add"; -import { ChangeEvent } from "react"; +import { DistanceUnits } from "../utils/units"; +import { useBeamstopStore } from "./beamstopStore"; export default function BeamStopDataEntry(): JSX.Element { - const beamstopDiameter = useSelector(beamstopSelector); - const units = useSelector(unitSelector); - const dispatch = useDispatch(); - const config = useSelector(configSelector); + const centre = useBeamstopStore((state) => state.centre); + const updateCentre = useBeamstopStore((state) => state.updateCentre); + const handleX = (event: React.ChangeEvent) => { + updateCentre({ + x: parseFloat(event.target.value) ? parseFloat(event.target.value) : null, + }); + }; + const handleY = (event: React.ChangeEvent) => { + updateCentre({ + y: parseFloat(event.target.value) ? parseFloat(event.target.value) : null, + }); + }; + + const diameter = useBeamstopStore((state) => { + if (state.diameterUnits === DistanceUnits.micrometre) { + return 1000 * state.diameter; + } + return state.diameter; + }); + + const diameterUnits = useBeamstopStore((state) => state.diameterUnits); + const updateUnits = useBeamstopStore((state) => state.updateUnits); + + const clearance = useBeamstopStore((state) => state.clearance); + const updateClearance = useBeamstopStore((state) => state.updateClearance); + const handleClearance = (event: React.ChangeEvent) => { + updateClearance( + parseFloat(event.target.value) ? parseFloat(event.target.value) : null, + ); + }; - return ( - - Beamstop - < Stack direction={"row"} > - Diameter: {beamstopDiameter} - < FormControl > - units - < Select - size="small" - value={units.beamstopDiameterUnits} - label="units" - onChange={(event) => - dispatch( - editUnits({ - beamstopDiameterUnits: event.target - .value as DistanceUnits, - }), - ) - } - > - - {DistanceUnits.millimetre} - - < MenuItem value={DistanceUnits.micrometre} > - {DistanceUnits.micrometre} - - - - - Position: - - x: - - px - - - - y: - - px - - - - - Clearance: - - - - - - pixels - - + return ( + + Beamstop + + Diameter: {diameter} + + units + + + + Position: + + x: + + + {" "} + px + + + + + y: + + + {" "} + px + + + + + + Clearance: + - ) + + + ); } diff --git a/src/data-entry/cameraTube.tsx b/src/data-entry/cameraTube.tsx index e69de29..fb71011 100644 --- a/src/data-entry/cameraTube.tsx +++ b/src/data-entry/cameraTube.tsx @@ -0,0 +1,92 @@ +import { + FormControl, + InputLabel, + MenuItem, + Select, + TextField, + Typography, +} from "@mui/material"; +import { Stack } from "@mui/system"; +import { useCameraTubeStore } from "./cameraTubeStore"; +import { DistanceUnits } from "../utils/units"; + +export default function CameraTubeDataEntry(): JSX.Element { + const centre = useCameraTubeStore((state) => state.centre); + const updateCentre = useCameraTubeStore((state) => state.updateCentre); + const handleX = (event: React.ChangeEvent) => { + updateCentre({ + x: parseFloat(event.target.value) ? parseFloat(event.target.value) : null, + }); + }; + const handleY = (event: React.ChangeEvent) => { + updateCentre({ + y: parseFloat(event.target.value) ? parseFloat(event.target.value) : null, + }); + }; + + const diameter = useCameraTubeStore((state) => { + if (state.diameterUnits === DistanceUnits.micrometre) { + return 1000 * state.diameter; + } + return state.diameter; + }); + + const diameterUnits = useCameraTubeStore((state) => state.diameterUnits); + const updateUnits = useCameraTubeStore((state) => state.updateUnits); + + return ( + + CameraTube + + Diameter: {diameter} + + units + + + + Position: + + x: + + + {" "} + px + + + + y: + + + {" "} + px + + + + ); +} diff --git a/src/data-entry/configSlice.ts b/src/data-entry/configSlice.ts deleted file mode 100644 index 5653666..0000000 --- a/src/data-entry/configSlice.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import testBeamlineConfig from "../presets/presetConfigs.json"; -import { BeamlineConfig, SerialisedVector2 } from "../utils/types"; -import { RootState } from "../store"; -import { detectorList } from "../presets/presetManager"; -import { - AngleUnits, - DistanceUnits, - EnergyUnits, - WavelengthUnits, -} from "../utils/units"; - -interface BeamlineState extends BeamlineConfig { - energy: number | null; - wavelength: number | null; -} - -const initialState = { - ...testBeamlineConfig.test, - energy: null, - wavelength: null, -} as BeamlineState; - -const beamlineConfigSlice = createSlice({ - name: "config", - initialState: initialState, - reducers: { - editConfig: (state, action: PayloadAction>) => { - return { ...state, ...action.payload }; - }, - editBeamstopPosition: (state, action: PayloadAction>) => { - action.payload.x? state.angle = action.payload.x: {} - }, - }, -}); - -export const beamlineConfigReducer = beamlineConfigSlice.reducer; - -export const { editConfig, editBeamstopPosition, } = beamlineConfigSlice.actions; -export const configSelector = (state: RootState) => state.config; - -// Unit selectors -export const pixelSizeSelector = (state: RootState) => { - if (state.units.pixelSizeUnits === DistanceUnits.micrometre) { - return 1000 * detectorList[state.config.detector].pixel_size; - } else { - return detectorList[state.config.detector].pixel_size; - } -}; - -export const beamstopSelector = (state: RootState) => { - if (state.units.beamstopDiameterUnits === DistanceUnits.micrometre) { - return 1000 * state.config.beamstop.diameter; - } else { - return state.config.beamstop.diameter; - } -}; - - -export const cameraTubeSelector = (state: RootState) => { - if (state.units.cameraDiameterUnits === DistanceUnits.micrometre) { - return 1000 * state.config.cameraTube.diameter; - } else { - return state.config.cameraTube.diameter; - } -}; - -export const energySelector = (state: RootState) => { - if ( - state.config.energy && - state.units.beamEnergyUnits === EnergyUnits.electronVolts - ) { - return 1000 * state.config.energy; - } else { - return state.config.energy; - } -}; - -export const wavelengthSelector = (state: RootState) => { - if ( - state.config.wavelength && - state.units.wavelengthUnits === WavelengthUnits.angstroms - ) { - return 10 * state.config.wavelength; - } else { - return state.config.wavelength; - } -}; - -export const angleSelector = (state: RootState) => { - if (state.config.angle && state.units.angleUnits === AngleUnits.degrees) { - return state.config.angle * (180 / Math.PI); - } else { - return state.config.angle; - } -}; diff --git a/src/data-entry/dataSideBar.tsx b/src/data-entry/dataSideBar.tsx index 92945b5..b8cb0da 100644 --- a/src/data-entry/dataSideBar.tsx +++ b/src/data-entry/dataSideBar.tsx @@ -6,101 +6,30 @@ import { Divider, Autocomplete, TextField, - Button, FormControl, Select, MenuItem, InputLabel, - SelectChangeEvent, - ButtonGroup, - Input, } from "@mui/material"; -import RemoveIcon from "@mui/icons-material/Remove"; -import AddIcon from "@mui/icons-material/Add"; -import { - DistanceUnits, - EnergyUnits, - WavelengthUnits, - AngleUnits, -} from "../utils/units"; -import { useDispatch, useSelector } from "react-redux"; -import { - configSelector, - editConfig, - - pixelSizeSelector, - cameraTubeSelector, - angleSelector, - wavelengthSelector, - energySelector, -} from "./configSlice"; -import { editUnits, unitSelector } from "./unitSlice"; -import { presetList, detectorList } from "../presets/presetManager"; -import { ChangeEvent, useState } from "react"; +import { DistanceUnits } from "../utils/units"; import BeamStopDataEntry from "./beamstop"; +import CameraTubeDataEntry from "./cameraTube"; +import { useDetectorStore } from "./detectorStore"; +import BeampropertiesDataEntry from "./beamProperties"; export default function DataSideBar(): JSX.Element { - const config = useSelector(configSelector); - const units = useSelector(unitSelector); - - // Selectors with unit conversion logic - const pixelSize = useSelector(pixelSizeSelector); - - const cameraTubeDiameter = useSelector(cameraTubeSelector); - const angle = useSelector(angleSelector); - const energy = useSelector(energySelector); - const wavelength = useSelector(wavelengthSelector); - - const dispatch = useDispatch(); - const [preset, setPreset] = useState("test"); - - const handlePresetChange = (preset: string): void => { - setPreset(preset); - dispatch(editConfig(presetList[preset])); - }; - - const handleDetectorChange = (detector: string): void => { - dispatch(editConfig({ detector: detector })); - }; - - // Add loads of error hnadling here - const handleEnergyChange = (event: ChangeEvent) => { - if (!event.target.value) { - dispatch(editConfig({ energy: null })); - return; - } - if (units.beamEnergyUnits === EnergyUnits.electronVolts) { - dispatch(editConfig({ energy: parseFloat(event.target.value) / 1000 })); - } else { - dispatch(editConfig({ energy: parseFloat(event.target.value) })); - } - }; - - const handleAngleChange = (event: ChangeEvent) => { - if (!event.target.value) { - dispatch(editConfig({ angle: null })); - return; - } - if (units.angleUnits === AngleUnits.degrees) { - dispatch( - editConfig({ angle: parseFloat(event.target.value) / (180 / Math.PI) }), - ); - } else { - dispatch(editConfig({ angle: parseFloat(event.target.value) })); - } - }; - - const handleWavelengthChange = (event: ChangeEvent) => { - if (!event.target.value) { - dispatch(editConfig({ wavelength: null })); - return; - } - if (units.wavelengthUnits === WavelengthUnits.angstroms) { - dispatch(editConfig({ wavelength: parseFloat(event.target.value) / 10 })); - } else { - dispatch(editConfig({ wavelength: parseFloat(event.target.value) })); + const name = useDetectorStore((state) => state.name); + const resolution = useDetectorStore((state) => state.current.resolution); + const pixelSize = useDetectorStore((state) => { + if (state.pixelUnits === DistanceUnits.micrometre) { + return 1000 * state.current.pixelSize; } - }; + return state.current.pixelSize; + }); + const detectorList = useDetectorStore((state) => state.detectorList); + const pixelUnits = useDetectorStore((state) => state.pixelUnits); + const updateUnits = useDetectorStore((state) => state.updateUnits); + const updateDetector = useDetectorStore((state) => state.updateDetector); return ( @@ -111,24 +40,19 @@ export default function DataSideBar(): JSX.Element { Predefined Configuration Templates ( )} - onChange={(_, value) => { - value ? handlePresetChange(value) : {}; - }} /> Detector ( )} + value={name} onChange={(_, value) => { - value ? handleDetectorChange(value) : {}; + value ? updateDetector(value) : {}; }} /> - Resolution: {detectorList[config.detector].resolution.height} x{" "} - {detectorList[config.detector].resolution.width} + Resolution: {resolution.height} x {resolution.width} - - Pixel size: {pixelSize} x {pixelSize}{" "} - + Pixel size: {pixelSize} units - dispatch( - editUnits({ - cameraDiameterUnits: event.target.value as DistanceUnits, - }), - ) - } - > - - {DistanceUnits.millimetre} - - - {DistanceUnits.micrometre} - - - - - Position - - x: - - px - - - y: - - px - - - Beam properties - - Energy: - - - units - - - - - WaveLength - - - units - - - - - Minimum allowed wavelength: {config.minWavelength}{" "} - - - Maximum allowed wavelength: {config.maxWavelength} - - - - Camera Length: - - - - - - m - + - - Angle: - - - units - - - + diff --git a/src/data-entry/unitSlice.ts b/src/data-entry/unitSlice.ts deleted file mode 100644 index 2e3aa35..0000000 --- a/src/data-entry/unitSlice.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { RootState } from "../store"; -import { - AngleUnits, - DistanceUnits, - EnergyUnits, - UnitConfig, - WavelengthUnits, -} from "../utils/units"; - -// Note the app will do all calculations with these units in mind and then use -// redux selectors to display in units of your choice -const defaultUnits: UnitConfig = { - cameraDiameterUnits: DistanceUnits.millimetre, - beamEnergyUnits: EnergyUnits.kiloElectronVolts, - pixelSizeUnits: DistanceUnits.millimetre, - angleUnits: AngleUnits.radians, - wavelengthUnits: WavelengthUnits.nanmometres, - beamstopDiameterUnits: DistanceUnits.millimetre, -}; - -const unitConfigSlice = createSlice({ - name: "unit-config", - initialState: defaultUnits, - reducers: { - editUnits: (state, action: PayloadAction>) => { - return { ...state, ...action.payload }; - }, - }, -}); - -export const { editUnits } = unitConfigSlice.actions; -export const unitConfigReducer = unitConfigSlice.reducer; -export const unitSelector = (state: RootState) => state.units; diff --git a/src/main.tsx b/src/main.tsx index 3569e45..1a43a8f 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,13 +2,9 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./app.tsx"; import "./index.css"; -import { Provider } from "react-redux"; -import store from "./store.ts"; ReactDOM.createRoot(document.getElementById("root")!).render( - - - + , ); diff --git a/src/presets/detectors.json b/src/presets/detectors.json index e0f08f2..4b88d0d 100644 --- a/src/presets/detectors.json +++ b/src/presets/detectors.json @@ -4,13 +4,13 @@ "width": 1475, "height": 1679 }, - "pixel_size": 0.172 + "pixelSize": 0.172 }, "test": { "resolution": { "width": 1, "height": 1 }, - "pixel_size": 1 + "pixelSize": 1 } } diff --git a/src/presets/presetManager.ts b/src/presets/presetManager.ts index 5a31a94..cdaa285 100644 --- a/src/presets/presetManager.ts +++ b/src/presets/presetManager.ts @@ -1,9 +1,15 @@ import detectorData from "../presets/detectors.json"; import presetData from "../presets/presetConfigs.json"; -import { BeamlineConfig, Detector } from "../utils/types"; +import { BeamlineConfig, Detector, CircularDevice } from "../utils/types"; + +interface AppDataFormat extends BeamlineConfig { + detector: string; + beamstop: CircularDevice; + cameraTube: CircularDevice; +} export const detectorList = detectorData as Record; export const presetList = presetData as unknown as Record< string, - BeamlineConfig + AppDataFormat >; diff --git a/src/store.ts b/src/store.ts deleted file mode 100644 index c42d4a5..0000000 --- a/src/store.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { configureStore } from "@reduxjs/toolkit"; -import { beamlineConfigReducer } from "./data-entry/configSlice"; -import { unitConfigReducer } from "./data-entry/unitSlice"; - -const store = configureStore({ - reducer: { - config: beamlineConfigReducer, - units: unitConfigReducer, - }, -}); - -export default store; - -export type RootState = ReturnType; -export type AppDispatch = typeof store.dispatch; diff --git a/src/utils/types.ts b/src/utils/types.ts index ad79efb..931c684 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,11 +1,11 @@ export interface Detector { resolution: { height: number; width: number }; - pixel_size: number; + pixelSize: number; } export interface SerialisedVector2 { - x:number; - y:number; + x?: number | null; + y?: number | null; } export interface CircularDevice { @@ -14,12 +14,8 @@ export interface CircularDevice { } export interface BeamlineConfig { - detector: string; - beamstop: CircularDevice; - cameraTube: CircularDevice; angle: number | null; cameraLength: number; - clearance: number; // remember to do int checks on this value minWavelength: number; maxWavelength: number; minCameraLength: number; diff --git a/src/utils/units.ts b/src/utils/units.ts index d976998..3484631 100644 --- a/src/utils/units.ts +++ b/src/utils/units.ts @@ -2,30 +2,30 @@ const mu = "\u03bc"; const angstrum = "\u212B"; export enum DistanceUnits { - millimetre = "mm", - micrometre = mu + "m", + millimetre = "mm", + micrometre = mu + "m", } export enum EnergyUnits { - electronVolts = "eV", - kiloElectronVolts = "keV", + electronVolts = "eV", + kiloElectronVolts = "keV", } export enum WavelengthUnits { - nanmometres = "nm", - angstroms = angstrum, + nanmometres = "nm", + angstroms = angstrum, } export enum AngleUnits { - radians = "rad", - degrees = "deg", + radians = "rad", + degrees = "deg", } export interface UnitConfig { - pixelSizeUnits: DistanceUnits; - beamEnergyUnits: EnergyUnits; - beamstopDiameterUnits: DistanceUnits; - cameraDiameterUnits: DistanceUnits; - wavelengthUnits: WavelengthUnits; - angleUnits: AngleUnits; + pixelSizeUnits: DistanceUnits; + beamEnergyUnits: EnergyUnits; + beamstopDiameterUnits: DistanceUnits; + cameraDiameterUnits: DistanceUnits; + wavelengthUnits: WavelengthUnits; + angleUnits: AngleUnits; }