diff --git a/apps/dataset_viewer/src/components/technique/tomo/TOMOSummaryView.tsx b/apps/dataset_viewer/src/components/technique/tomo/TOMOSummaryView.tsx index a6838d471021e9fb23fc9374f55fabce2964f889..b49a6012da0d492fc596f1b4b5998083a7037a2d 100644 --- a/apps/dataset_viewer/src/components/technique/tomo/TOMOSummaryView.tsx +++ b/apps/dataset_viewer/src/components/technique/tomo/TOMOSummaryView.tsx @@ -4,31 +4,15 @@ import { formatDateToDayAndTime, Gallery, getDatasetParamValue, + MetadataCard, MetadataTable, + AttenuatorsWidget, type MetadataTableParameter, - MetadataTableParameters, } from '@edata-portal/core'; +import { InstrumentSlitWidget } from '@edata-portal/core/src/components/metadata/InstrumentSlitWidget'; import type { Dataset } from '@edata-portal/icat-plus-api'; -import { Card, Col, Container, Row } from 'react-bootstrap'; - -const addParam = ( - params: MetadataTableParameter[], - dataset: Dataset, - key: string, - caption: string, - digits?: number, -) => { - const value = getDatasetParamValue(dataset, key) || undefined; - if (value) { - params.push({ - caption, - value, - digits, - parameterName: key, - }); - } -}; +import { Col, Container, Row } from 'react-bootstrap'; const presetSummary = (dataset: Dataset): MetadataTableParameter[] => { const params: MetadataTableParameter[] = []; @@ -107,30 +91,6 @@ const beamParameters = (dataset: Dataset) => [ }, ]; -const attenuatorsParameters = (dataset: Dataset): MetadataTableParameter[] => { - const params: MetadataTableParameter[] = []; - for (let i = 0; i < 15; i++) { - const index = (i + 1).toString().padStart(2, '0'); - const type = getDatasetParamValue( - dataset, - `InstrumentAttenuator${index}_type`, - ); - const thickness = getDatasetParamValue( - dataset, - `InstrumentAttenuator${index}_thickness`, - ); - - if (type && type.toLowerCase() !== 'empty') { - params.push({ - caption: `Att${i + 1}`, - value: `${type}, ${thickness}`, - parameterName: `InstrumentAttenuator${index}_type`, - }); - } - } - return params; -}; - const formatImagesDimensions = (dataset: Dataset) => { const value = getDatasetParamValue(dataset, `InstrumentDetector01Rois_value`); const dimensions = value?.split(','); @@ -197,145 +157,7 @@ const cameraParameters = (dataset: Dataset) => [ }, ]; -const primarySlitsParameters = (dataset: Dataset): MetadataTableParameter[] => { - const params: MetadataTableParameter[] = []; - addParam( - params, - dataset, - 'InstrumentSlitPrimary_horizontal_gap', - 'Horizontal gap', - 2, - ); - addParam( - params, - dataset, - 'InstrumentSlitPrimary_horizontal_offset', - 'Horizontal offset', - 2, - ); - addParam( - params, - dataset, - 'InstrumentSlitPrimary_vertical_gap', - 'Vertical gap', - 2, - ); - addParam( - params, - dataset, - 'InstrumentSlitPrimary_vertical_offset', - 'Vertical offset', - 2, - ); - - return params; -}; - -const secondarySlitsParameters = ( - dataset: Dataset, -): MetadataTableParameter[] => { - const params: MetadataTableParameter[] = []; - addParam( - params, - dataset, - 'InstrumentSlitSecondary_horizontal_gap', - 'Horizontal gap', - 2, - ); - addParam( - params, - dataset, - 'InstrumentSlitSecondary_horizontal_offset', - 'Horizontal offset', - 2, - ); - addParam( - params, - dataset, - 'InstrumentSlitSecondary_vertical_gap', - 'Vertical gap', - 2, - ); - addParam( - params, - dataset, - 'InstrumentSlitSecondary_vertical_offset', - 'Vertical offset', - 2, - ); - - return params; -}; - -const otherSlits = ( - dataset: Dataset, -): { slitsTitles: string[]; otherSlitsData: MetadataTableParameter[][] } => { - const paramKeys = [ - { key: 'InstrumentSlits_horizontal_gap', caption: 'Horizontal gap' }, - { key: 'InstrumentSlits_horizontal_offset', caption: 'Horizontal offset' }, - { key: 'InstrumentSlits_vertical_gap', caption: 'Vertical gap' }, - { key: 'InstrumentSlits_vertical_offset', caption: 'Vertical offset' }, - ]; - - const values = paramKeys.map(({ key }) => getDatasetParamValue(dataset, key)); - if (values.every((val) => val === null || val === undefined)) { - return { slitsTitles: ['Slits'], otherSlitsData: [] }; - } - const splitArrays = values.map((val) => - typeof val === 'string' - ? val.split(/\s+/).filter(Boolean) // Split on spaces and remove empty entries - : val !== null && val !== undefined - ? [String(val)] - : [], - ); - - // Ensure all split arrays have the same length - const slitsNumber = Math.max(...splitArrays.map((arr) => arr.length)); - const allHaveSameLength = splitArrays.every( - (arr) => arr.length === slitsNumber, - ); - - if (allHaveSameLength) { - // Slit names if available, otherwise "Slits" - const slitNames = getDatasetParamValue(dataset, 'InstrumentSlits_name'); - const slitTitles = slitNames - ? slitNames - .split(/\s+/) - .slice(0, slitsNumber) - .map((name) => name.trim()) - : Array(slitsNumber).fill('Slits'); - - // Data into separate rows per slit configuration - const slitData = Array.from({ length: slitsNumber }, (_, i) => - paramKeys.map(({ caption }, j) => ({ - caption, - value: splitArrays[j][i] || '', - digits: 2, - })), - ); - - return { - slitsTitles: slitTitles, - otherSlitsData: slitData, - }; - } - - return { - slitsTitles: ['Slits'], - otherSlitsData: [ - paramKeys - .map(({ caption }, i) => - values[i] !== null && values[i] !== undefined - ? { caption, value: String(values[i]).trim(), digits: 2 } - : null, - ) - .filter(Boolean) as MetadataTableParameter[], - ], - }; -}; - export default function TOMOSummaryView({ dataset }: { dataset: Dataset }) { - const { slitsTitles, otherSlitsData } = otherSlits(dataset); return ( <Container fluid> <Row className="g-3"> @@ -348,75 +170,28 @@ export default function TOMOSummaryView({ dataset }: { dataset: Dataset }) { </Col> )} <Col xs="auto"> - <TOMOMetadataCard + <MetadataCard title="Acquisition parameters" - dataset={dataset} - parameters={acquisitionParameters()} + entity={dataset} + content={acquisitionParameters()} /> </Col> <Col xs="auto"> - <TOMOMetadataCard + <MetadataCard title="Beam" - dataset={dataset} - parameters={beamParameters(dataset)} + entity={dataset} + content={beamParameters(dataset)} /> </Col> - {attenuatorsParameters(dataset).length > 0 && ( - <Col xs="auto"> - <TOMOMetadataCard - title="Attenuators" - dataset={dataset} - parameters={attenuatorsParameters(dataset)} - /> - </Col> - )} + <AttenuatorsWidget dataset={dataset} /> <Col xs="auto"> - <TOMOMetadataCard + <MetadataCard title="Camera and optics" - dataset={dataset} - parameters={cameraParameters(dataset)} + entity={dataset} + content={cameraParameters(dataset)} /> </Col> - {(primarySlitsParameters(dataset).length > 0 || - secondarySlitsParameters(dataset).length > 0 || - otherSlitsData.length > 0) && ( - <Col xs="auto"> - <Card> - <Card.Header className="text-center">Slits</Card.Header> - <Card.Body> - <Row> - {primarySlitsParameters(dataset).length > 0 && ( - <Col xs="auto"> - <TOMOMetadataCard - title="Primary Slits" - dataset={dataset} - parameters={primarySlitsParameters(dataset)} - /> - </Col> - )} - {secondarySlitsParameters(dataset).length > 0 && ( - <Col xs="auto"> - <TOMOMetadataCard - title="Secondary Slits" - dataset={dataset} - parameters={secondarySlitsParameters(dataset)} - /> - </Col> - )} - {otherSlitsData.map((params, index) => ( - <Col xs="auto" key={index}> - <TOMOMetadataCard - title={slitsTitles[index] || 'Slits'} - dataset={dataset} - parameters={params} - /> - </Col> - ))} - </Row> - </Card.Body> - </Card> - </Col> - )} + <InstrumentSlitWidget dataset={dataset} /> <Col xs="auto"> <Gallery dataset={dataset.outputDatasets?.[0]}></Gallery> </Col> @@ -424,20 +199,3 @@ export default function TOMOSummaryView({ dataset }: { dataset: Dataset }) { </Container> ); } - -function TOMOMetadataCard({ - title, - dataset, - parameters, -}: { - title: string; - dataset: Dataset; - parameters: MetadataTableParameters; -}) { - return ( - <Card> - <Card.Header className="text-center">{title}</Card.Header> - <MetadataTable entity={dataset} parameters={parameters}></MetadataTable> - </Card> - ); -} diff --git a/apps/portal/src/components/dataset/DatasetDetail.tsx b/apps/portal/src/components/dataset/DatasetDetail.tsx index 0ebb087bbf97ed6e544dd05a17a0718a157eb54d..070088ecc6b50ba91702e24fa666339870d4e429 100644 --- a/apps/portal/src/components/dataset/DatasetDetail.tsx +++ b/apps/portal/src/components/dataset/DatasetDetail.tsx @@ -1,24 +1,19 @@ import { - sortParameters, DatasetFilesTabName, Loading, MetadataTab, DatasetFiles, DoiInfo, useConfig, + getInstrummentTab, } from '@edata-portal/core'; -import { InstrumentWidget } from 'components/dataset/metadata/InstrumentWidget'; import { Col, Row, Tabs, Tab } from 'react-bootstrap'; import { Suspense } from 'react'; import type { Dataset, Parameter } from '@edata-portal/icat-plus-api'; import { GenericDatasetSummary } from '@edata-portal/core/src/components/dataset/generic/GenericDatasetSummary'; export function DatasetDetail({ dataset }: { dataset: Dataset }) { - const instrumentParams = sortParameters( - dataset.parameters.filter((parameter) => - parameter.name.startsWith('Instrument'), - ), - ); + const instrumentTab = getInstrummentTab(dataset); const config = useConfig(); @@ -53,12 +48,13 @@ export function DatasetDetail({ dataset }: { dataset: Dataset }) { } return null; })} - {instrumentParams.length > 0 && ( - <Tab key="beamline" eventKey="beamline" title="Beamlines"> - <InstrumentWidget - dataset={dataset} - parameters={instrumentParams} - /> + {instrumentTab && ( + <Tab + key={instrumentTab.key} + eventKey={instrumentTab.key} + title={instrumentTab.title} + > + {instrumentTab.content} </Tab> )} diff --git a/apps/portal/src/components/dataset/metadata/InstrumentSlitWidget.tsx b/apps/portal/src/components/dataset/metadata/InstrumentSlitWidget.tsx deleted file mode 100644 index 0e4151a29776c7ab9d6a3b9c302d87a9563c954d..0000000000000000000000000000000000000000 --- a/apps/portal/src/components/dataset/metadata/InstrumentSlitWidget.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { - renderParameterNameAsTitle, - MetadataCategories, -} from '@edata-portal/core'; -import type { Dataset } from '@edata-portal/icat-plus-api'; -import { Col, Card } from 'react-bootstrap'; - -export function InstrumentSlitWidget({ dataset }: { dataset: Dataset }) { - const prefixes = [ - { prefix: 'InstrumentSlitPrimary', name: 'Primary slit' }, - { prefix: 'InstrumentSlitSecondary', name: 'Secondary slit' }, - { prefix: 'InstrumentSlits', name: 'Slits' }, - ]; - const suffixes = [ - 'blade_front', - 'blade_back', - 'blade_up', - 'blade_down', - 'horizontal_gap', - 'horizontal_offset', - 'vertical_gap', - 'vertical_offset', - ]; - - const slits = prefixes.map((prefix) => { - return { - title: prefix.name, - id: prefix.name, - content: suffixes.map((suffix) => ({ - caption: renderParameterNameAsTitle(suffix), - parameterName: `${prefix.prefix}_${suffix}`, - })), - }; - }); - - if (slits.length === 0) { - return null; - } - return ( - <Col> - <Card> - <Card.Header> - <strong>Slits</strong> - </Card.Header> - <Card.Body> - <MetadataCategories categories={slits} entity={dataset} /> - </Card.Body> - </Card> - </Col> - ); -} diff --git a/apps/portal/src/components/dataset/metadata/InstrumentWidget.tsx b/apps/portal/src/components/dataset/metadata/InstrumentWidget.tsx deleted file mode 100644 index b3d9a62678c8e360448631035cab50def9e572e2..0000000000000000000000000000000000000000 --- a/apps/portal/src/components/dataset/metadata/InstrumentWidget.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { Dataset, Parameter } from '@edata-portal/icat-plus-api'; -import { InstrumentMonochromatorWidget } from 'components/dataset/metadata/InstrumentMonochromatorWidget'; -import { InstrumentDetectorWidget } from 'components/dataset/metadata/InstrumentDetectorWidget'; -import { InstrumentSlitWidget } from 'components/dataset/metadata/InstrumentSlitWidget'; -import { InstrumentOpticsWidget } from 'components/dataset/metadata/InstrumentOpticsWidget'; -import { InstrumentPositionersWidget } from 'components/dataset/metadata/InstrumentPositionersWidget'; -import { Col, Row } from 'react-bootstrap'; - -export function InstrumentWidget({ - dataset, - parameters, -}: { - dataset: Dataset; - parameters: Parameter[]; -}) { - return ( - <Row className="g-2 m-2"> - <Col xs={'auto'}> - <InstrumentMonochromatorWidget - dataset={dataset} - parameters={parameters} - /> - </Col> - <Col xs={'auto'}> - <InstrumentDetectorWidget dataset={dataset} parameters={parameters} /> - </Col> - <Col xs={'auto'}> - <InstrumentSlitWidget dataset={dataset} /> - </Col> - <Col xs={'auto'}> - <InstrumentOpticsWidget dataset={dataset} /> - </Col> - <Col xs={'auto'}> - <InstrumentPositionersWidget dataset={dataset} /> - </Col> - </Row> - ); -} diff --git a/packages/core/src/components/dataset/generic/genericDatasetTabs.tsx b/packages/core/src/components/dataset/generic/genericDatasetTabs.tsx index 5292de541f92ded2083618655e902513b8c006ee..a80ef8673cee7b39da6988384d9a173e0837db5c 100644 --- a/packages/core/src/components/dataset/generic/genericDatasetTabs.tsx +++ b/packages/core/src/components/dataset/generic/genericDatasetTabs.tsx @@ -2,7 +2,12 @@ import type { Dataset } from '@edata-portal/icat-plus-api'; import { Gallery } from 'components/dataset/gallery'; import { GenericDatasetSummary } from 'components/dataset/generic/GenericDatasetSummary'; import { MetadataCategories, MetadataCategory } from 'components/metadata'; -import { getFilesTab, getMetadataTab, TabDefinition } from 'components/tabs'; +import { + getFilesTab, + getInstrummentTab, + getMetadataTab, + TabDefinition, +} from 'components/tabs'; export type GenericDatasetTabsConfig = { summaryTab: @@ -42,8 +47,10 @@ export function getGenericDatasetTabs( ), }; + const instrumentTab = getInstrummentTab(dataset); return [ summary, + instrumentTab, config.filesTab ? getFilesTab(dataset, config.showPathInFilesTab, false) : undefined, diff --git a/packages/core/src/components/metadata/AttenuatorsWidget.tsx b/packages/core/src/components/metadata/AttenuatorsWidget.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3a6f4b8f4cb202ed748bcc6aff267e9abb16c707 --- /dev/null +++ b/packages/core/src/components/metadata/AttenuatorsWidget.tsx @@ -0,0 +1,40 @@ +import type { Dataset } from '@edata-portal/icat-plus-api'; +import { MetadataCard } from 'components/metadata/MetadataCard'; +import { MetadataTableParameter } from 'components/models'; +import { getDatasetParamValue } from 'helpers'; +import { Col } from 'react-bootstrap'; + +export function AttenuatorsWidget({ dataset }: { dataset: Dataset }) { + const presetAttenuators = (dataset: Dataset): MetadataTableParameter[] => { + const params: MetadataTableParameter[] = []; + for (let i = 0; i < 15; i++) { + const index = (i + 1).toString().padStart(2, '0'); + const type = getDatasetParamValue( + dataset, + `InstrumentAttenuator${index}_type`, + ); + const thickness = getDatasetParamValue( + dataset, + `InstrumentAttenuator${index}_thickness`, + ); + + if (type && type.toLowerCase() !== 'empty') { + params.push({ caption: `Att${i + 1}`, value: `${type}, ${thickness}` }); + } + } + return params; + }; + + if (presetAttenuators.length === 0) { + return null; + } + return ( + <Col xs="auto"> + <MetadataCard + title="Attenuators" + entity={dataset} + content={presetAttenuators(dataset)} + /> + </Col> + ); +} diff --git a/apps/portal/src/components/dataset/metadata/InstrumentDetectorWidget.tsx b/packages/core/src/components/metadata/InstrumentDetectorWidget.tsx similarity index 67% rename from apps/portal/src/components/dataset/metadata/InstrumentDetectorWidget.tsx rename to packages/core/src/components/metadata/InstrumentDetectorWidget.tsx index 68f943d371b0ad2d9385bd1a5ff87943887e90ea..d408febdc0db2df36cb81a52a588c8c0ce889e15 100644 --- a/apps/portal/src/components/dataset/metadata/InstrumentDetectorWidget.tsx +++ b/packages/core/src/components/metadata/InstrumentDetectorWidget.tsx @@ -1,25 +1,14 @@ -import { - type MetadataTableParameter, - getDatasetParamValue, - MetadataCategories, -} from '@edata-portal/core'; -import type { Dataset, Parameter } from '@edata-portal/icat-plus-api'; +import type { Dataset } from '@edata-portal/icat-plus-api'; +import { MetadataCategories } from 'components/metadata/MetadataCategories'; +import { MetadataTableParameter } from 'components/models'; +import { getDatasetParamValue } from 'helpers'; import { Col, Card } from 'react-bootstrap'; -export function InstrumentDetectorWidget({ - dataset, - parameters, -}: { - dataset: Dataset; - parameters: Parameter[]; -}) { +export function InstrumentDetectorWidget({ dataset }: { dataset: Dataset }) { const detectors = []; for (let index = 1; index < 10; index++) { const prefix = `InstrumentDetector0${index}`; - const detectorParams = parameters.filter((parameter) => - parameter.name.startsWith(prefix), - ); const presetDetector: MetadataTableParameter[] = []; const positionersValues = getDatasetParamValue(dataset, `${prefix}Positioners_value`) @@ -36,7 +25,7 @@ export function InstrumentDetectorWidget({ }); } - if (detectorParams.length > 0) { + if (presetDetector.length > 0) { const name = `${prefix}_name`; detectors.push({ title: `#${index} ${getDatasetParamValue(dataset, name)}`, @@ -52,9 +41,7 @@ export function InstrumentDetectorWidget({ return ( <Col> <Card> - <Card.Header> - <strong>Detector</strong> - </Card.Header> + <Card.Header className="text-center">Detector</Card.Header> <Card.Body> <MetadataCategories categories={detectors} entity={dataset} /> </Card.Body> diff --git a/apps/portal/src/components/dataset/metadata/InstrumentMonochromatorWidget.tsx b/packages/core/src/components/metadata/InstrumentMonochromatorWidget.tsx similarity index 82% rename from apps/portal/src/components/dataset/metadata/InstrumentMonochromatorWidget.tsx rename to packages/core/src/components/metadata/InstrumentMonochromatorWidget.tsx index 3dfa7e16e63b0a99d051f78e42d20ad69fb12aba..abf2ec79b4a0dda2780a4ed3e7f3774516ced1cf 100644 --- a/apps/portal/src/components/dataset/metadata/InstrumentMonochromatorWidget.tsx +++ b/packages/core/src/components/metadata/InstrumentMonochromatorWidget.tsx @@ -1,9 +1,7 @@ -import { - type MetadataTableParameter, - shortenParameterName, - MetadataCard, -} from '@edata-portal/core'; import type { Dataset, Parameter } from '@edata-portal/icat-plus-api'; +import { MetadataCard } from 'components/metadata/MetadataCard'; +import { MetadataTableParameter } from 'components/models'; +import { shortenParameterName } from 'helpers'; import { Col } from 'react-bootstrap'; export function InstrumentMonochromatorWidget({ diff --git a/apps/portal/src/components/dataset/metadata/InstrumentOpticsWidget.tsx b/packages/core/src/components/metadata/InstrumentOpticsWidget.tsx similarity index 80% rename from apps/portal/src/components/dataset/metadata/InstrumentOpticsWidget.tsx rename to packages/core/src/components/metadata/InstrumentOpticsWidget.tsx index 4b9acf44adcde43429823839c57c12333ed0b594..bf1d2639f66a86e19fbda49b285ce114aff37e7e 100644 --- a/apps/portal/src/components/dataset/metadata/InstrumentOpticsWidget.tsx +++ b/packages/core/src/components/metadata/InstrumentOpticsWidget.tsx @@ -1,9 +1,7 @@ -import { - type MetadataTableParameter, - getDatasetParamValue, - MetadataCard, -} from '@edata-portal/core'; import type { Dataset } from '@edata-portal/icat-plus-api'; +import { MetadataCard } from 'components/metadata/MetadataCard'; +import { MetadataTableParameter } from 'components/models'; +import { getDatasetParamValue } from 'helpers'; import { Col } from 'react-bootstrap'; export function InstrumentOpticsWidget({ dataset }: { dataset: Dataset }) { @@ -24,7 +22,7 @@ export function InstrumentOpticsWidget({ dataset }: { dataset: Dataset }) { return null; } return ( - <Col> + <Col xs="auto"> <MetadataCard title="Optics" entity={dataset} content={presetOptics} /> </Col> ); diff --git a/apps/portal/src/components/dataset/metadata/InstrumentPositionersWidget.tsx b/packages/core/src/components/metadata/InstrumentPositionersWidget.tsx similarity index 83% rename from apps/portal/src/components/dataset/metadata/InstrumentPositionersWidget.tsx rename to packages/core/src/components/metadata/InstrumentPositionersWidget.tsx index f874dc493ee23d9fcb24fc89ba8157a9caa1c93c..bd31f87cd35087e08ec53815bb89b944aea4608f 100644 --- a/apps/portal/src/components/dataset/metadata/InstrumentPositionersWidget.tsx +++ b/packages/core/src/components/metadata/InstrumentPositionersWidget.tsx @@ -1,9 +1,7 @@ -import { - type MetadataTableParameter, - getDatasetParamValue, - MetadataCard, -} from '@edata-portal/core'; import type { Dataset } from '@edata-portal/icat-plus-api'; +import { MetadataCard } from 'components/metadata/MetadataCard'; +import { MetadataTableParameter } from 'components/models'; +import { getDatasetParamValue } from 'helpers'; import { Col } from 'react-bootstrap'; export function InstrumentPositionersWidget({ dataset }: { dataset: Dataset }) { diff --git a/packages/core/src/components/metadata/InstrumentSlitWidget.tsx b/packages/core/src/components/metadata/InstrumentSlitWidget.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9cf0bfcbd014950dd88e6fb70b4a595fbdda3c99 --- /dev/null +++ b/packages/core/src/components/metadata/InstrumentSlitWidget.tsx @@ -0,0 +1,207 @@ +import type { Dataset } from '@edata-portal/icat-plus-api'; +import { MetadataCard } from 'components/metadata/MetadataCard'; +import { MetadataTableParameter } from 'components/models'; +import { getDatasetParamValue } from 'helpers'; +import { Col, Card, Row } from 'react-bootstrap'; + +export function InstrumentSlitWidget({ dataset }: { dataset: Dataset }) { + const addParam = ( + params: MetadataTableParameter[], + dataset: Dataset, + key: string, + caption: string, + digits?: number, + ) => { + const value = getDatasetParamValue(dataset, key) || undefined; + if (value) { + params.push({ caption, value, digits }); + } + }; + + const primarySlitsParameters = ( + dataset: Dataset, + ): MetadataTableParameter[] => { + const params: MetadataTableParameter[] = []; + addParam( + params, + dataset, + 'InstrumentSlitPrimary_horizontal_gap', + 'Horizontal gap', + 2, + ); + addParam( + params, + dataset, + 'InstrumentSlitPrimary_horizontal_offset', + 'Horizontal offset', + 2, + ); + addParam( + params, + dataset, + 'InstrumentSlitPrimary_vertical_gap', + 'Vertical gap', + 2, + ); + addParam( + params, + dataset, + 'InstrumentSlitPrimary_vertical_offset', + 'Vertical offset', + 2, + ); + + return params; + }; + + const secondarySlitsParameters = ( + dataset: Dataset, + ): MetadataTableParameter[] => { + const params: MetadataTableParameter[] = []; + addParam( + params, + dataset, + 'InstrumentSlitSecondary_horizontal_gap', + 'Horizontal gap', + 2, + ); + addParam( + params, + dataset, + 'InstrumentSlitSecondary_horizontal_offset', + 'Horizontal offset', + 2, + ); + addParam( + params, + dataset, + 'InstrumentSlitSecondary_vertical_gap', + 'Vertical gap', + 2, + ); + addParam( + params, + dataset, + 'InstrumentSlitSecondary_vertical_offset', + 'Vertical offset', + 2, + ); + + return params; + }; + + const otherSlits = ( + dataset: Dataset, + ): { slitsTitles: string[]; otherSlitsData: MetadataTableParameter[][] } => { + const paramKeys = [ + { key: 'InstrumentSlits_horizontal_gap', caption: 'Horizontal gap' }, + { + key: 'InstrumentSlits_horizontal_offset', + caption: 'Horizontal offset', + }, + { key: 'InstrumentSlits_vertical_gap', caption: 'Vertical gap' }, + { key: 'InstrumentSlits_vertical_offset', caption: 'Vertical offset' }, + ]; + + const values = paramKeys.map(({ key }) => + getDatasetParamValue(dataset, key), + ); + if (values.every((val) => val === null || val === undefined)) { + return { slitsTitles: ['Slits'], otherSlitsData: [] }; + } + const splitArrays = values.map((val) => + typeof val === 'string' + ? val.split(/\s+/).filter(Boolean) // Split on spaces and remove empty entries + : val !== null && val !== undefined + ? [String(val)] + : [], + ); + + // Ensure all split arrays have the same length + const slitsNumber = Math.max(...splitArrays.map((arr) => arr.length)); + const allHaveSameLength = splitArrays.every( + (arr) => arr.length === slitsNumber, + ); + + if (allHaveSameLength) { + // Slit names if available, otherwise "Slits" + const slitNames = getDatasetParamValue(dataset, 'InstrumentSlits_name'); + const slitTitles = slitNames + ? slitNames + .split(/\s+/) + .slice(0, slitsNumber) + .map((name) => name.trim()) + : Array(slitsNumber).fill('Slits'); + + // Data into separate rows per slit configuration + const slitData = Array.from({ length: slitsNumber }, (_, i) => + paramKeys.map(({ caption }, j) => ({ + caption, + value: splitArrays[j][i] || '', + digits: 2, + })), + ); + + return { + slitsTitles: slitTitles, + otherSlitsData: slitData, + }; + } + + return { + slitsTitles: ['Slits'], + otherSlitsData: [ + paramKeys + .map(({ caption }, i) => + values[i] !== null && values[i] !== undefined + ? { caption, value: String(values[i]).trim(), digits: 2 } + : null, + ) + .filter(Boolean) as MetadataTableParameter[], + ], + }; + }; + + const { slitsTitles, otherSlitsData } = otherSlits(dataset); + + return primarySlitsParameters(dataset).length > 0 || + secondarySlitsParameters(dataset).length > 0 || + otherSlitsData.length > 0 ? ( + <Col xs="auto"> + <Card> + <Card.Header className="text-center">Slits</Card.Header> + <Card.Body> + <Row> + {primarySlitsParameters(dataset).length > 0 && ( + <Col xs="auto"> + <MetadataCard + title="Primary Slits" + entity={dataset} + content={primarySlitsParameters(dataset)} + /> + </Col> + )} + {secondarySlitsParameters(dataset).length > 0 && ( + <Col xs="auto"> + <MetadataCard + title="Secondary Slits" + entity={dataset} + content={secondarySlitsParameters(dataset)} + /> + </Col> + )} + {otherSlitsData.map((params, index) => ( + <Col xs="auto" key={index}> + <MetadataCard + title={slitsTitles[index] || 'Slits'} + entity={dataset} + content={params} + /> + </Col> + ))} + </Row> + </Card.Body> + </Card> + </Col> + ) : null; +} diff --git a/packages/core/src/components/metadata/InstrumentWidget.tsx b/packages/core/src/components/metadata/InstrumentWidget.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dff98273b1825a8ef17b74aae3927e33d00da5d8 --- /dev/null +++ b/packages/core/src/components/metadata/InstrumentWidget.tsx @@ -0,0 +1,34 @@ +import type { Dataset } from '@edata-portal/icat-plus-api'; +import { InstrumentMonochromatorWidget } from 'components/metadata/InstrumentMonochromatorWidget'; +import { InstrumentDetectorWidget } from 'components/metadata/InstrumentDetectorWidget'; +import { InstrumentSlitWidget } from 'components/metadata/InstrumentSlitWidget'; +import { InstrumentOpticsWidget } from 'components/metadata/InstrumentOpticsWidget'; +import { InstrumentPositionersWidget } from 'components/metadata/InstrumentPositionersWidget'; +import { Col, Row } from 'react-bootstrap'; +import { getInstrumentParameters } from 'helpers'; + +export function InstrumentWidget({ dataset }: { dataset: Dataset }) { + const instrumentParams = getInstrumentParameters(dataset); + return ( + <Row className="g-2 m-2"> + <Col xs={'auto'}> + <InstrumentMonochromatorWidget + dataset={dataset} + parameters={instrumentParams} + /> + </Col> + <Col xs={'auto'}> + <InstrumentDetectorWidget dataset={dataset} /> + </Col> + <Col xs={'auto'}> + <InstrumentSlitWidget dataset={dataset} /> + </Col> + <Col xs={'auto'}> + <InstrumentOpticsWidget dataset={dataset} /> + </Col> + <Col xs={'auto'}> + <InstrumentPositionersWidget dataset={dataset} /> + </Col> + </Row> + ); +} diff --git a/packages/core/src/components/metadata/MetadataCard.tsx b/packages/core/src/components/metadata/MetadataCard.tsx index 861073c840202745d6f729ae57220be0702ceb1a..61784dce76da9aaa4b4db9937531c9b13dd61e98 100644 --- a/packages/core/src/components/metadata/MetadataCard.tsx +++ b/packages/core/src/components/metadata/MetadataCard.tsx @@ -14,15 +14,12 @@ export function MetadataCard({ }) { return ( <Card - className="text-center" style={{ maxWidth: 700, flexGrow: 1, }} > - <Card.Header className="p-1"> - <strong>{title}</strong> - </Card.Header> + <Card.Header className="text-center">{title}</Card.Header> <Card.Body style={{ padding: 0 }}> <MetadataTable entity={entity} parameters={content} /> </Card.Body> diff --git a/packages/core/src/components/metadata/index.tsx b/packages/core/src/components/metadata/index.tsx index b453c87f8e9be6ad1661e811c40c765c78c86284..6ecd21e4c71f240a8972fa45c40fa2f261ace207 100644 --- a/packages/core/src/components/metadata/index.tsx +++ b/packages/core/src/components/metadata/index.tsx @@ -1,3 +1,6 @@ export * from './MetadataCard'; export * from './MetadataCategories'; export * from './MetadataTable'; +export * from './InstrumentWidget'; +export * from './AttenuatorsWidget'; +export * from './InstrumentSlitWidget'; diff --git a/packages/core/src/components/tabs/tabDefinition.tsx b/packages/core/src/components/tabs/tabDefinition.tsx index 63cda52a2e4e13d43c1260f927223e031429ace8..174e3f9bb78abcf80133eb1edcfc331b95f836f4 100644 --- a/packages/core/src/components/tabs/tabDefinition.tsx +++ b/packages/core/src/components/tabs/tabDefinition.tsx @@ -5,7 +5,8 @@ import { DatasetList, MetadataTab, } from 'components/dataset'; -import { getInputDatasetIds } from 'helpers'; +import { InstrumentWidget } from 'components/metadata'; +import { getInputDatasetIds, getInstrumentParameters } from 'helpers'; import { Badge, Container } from 'react-bootstrap'; export type TabDefinition = { @@ -71,3 +72,18 @@ export function getInputsTab(dataset: Dataset): TabDefinition { hidden: !inputs.length, }; } + +export function getInstrummentTab(dataset: Dataset): TabDefinition | undefined { + const instrumentParams = getInstrumentParameters(dataset); + return instrumentParams.length > 0 + ? { + key: 'instrument', + title: 'Instrument', + content: ( + <Container fluid> + <InstrumentWidget dataset={dataset} /> + </Container> + ), + } + : undefined; +} diff --git a/packages/core/src/helpers/dataset.ts b/packages/core/src/helpers/dataset.ts index 877fdb2fb390456a64e4ab4f955dd7648d6a716b..7c4a0a1512d43884c5dd1c7bd6763313de96004d 100644 --- a/packages/core/src/helpers/dataset.ts +++ b/packages/core/src/helpers/dataset.ts @@ -1,4 +1,4 @@ -import type { Dataset } from '@edata-portal/icat-plus-api'; +import type { Dataset, Parameter } from '@edata-portal/icat-plus-api'; import { immutableArray } from 'helpers/array'; import { DATASET_TYPE_ACQUISITION, @@ -8,6 +8,7 @@ import { import { prettyPrint } from 'helpers/string'; import { unit } from 'mathjs'; import { convertToFixed } from 'helpers/numeric'; +import { sortParameters } from 'helpers/metadata'; export const DATASET_NAME_PARAM = 'datasetName'; @@ -148,3 +149,18 @@ export function convertUnitDatasetParameter( } return value; } + +/** + * Returns the sorted list of Instrument metadata for a given dataset + * Empty if not Instrument metadata are found + * @param dataset + * @returns + */ +export function getInstrumentParameters(dataset: Dataset): Parameter[] { + const instrumentParams = sortParameters( + dataset.parameters.filter((parameter) => + parameter.name.startsWith('Instrument'), + ), + ); + return instrumentParams; +}