diff --git a/src/firefly/js/ui/dynamic/EmbeddedPositionSearchPanel.jsx b/src/firefly/js/ui/dynamic/EmbeddedPositionSearchPanel.jsx index c4c9a9adc..e03a25242 100644 --- a/src/firefly/js/ui/dynamic/EmbeddedPositionSearchPanel.jsx +++ b/src/firefly/js/ui/dynamic/EmbeddedPositionSearchPanel.jsx @@ -3,7 +3,7 @@ import React, {Fragment, useContext, useEffect, useState} from 'react'; import {oneOf, bool, string, number, arrayOf, object, func, shape, elementType} from 'prop-types'; import {defaultsDeep} from 'lodash'; import CoordinateSys from '../../visualize/CoordSys.js'; -import {CONE_AREA_OPTIONS, CONE_AREA_OPTIONS_UPLOAD, CONE_CHOICE_KEY, POLY_CHOICE_KEY, UPLOAD_CHOICE_KEY +import {CONE_CHOICE_KEY, POLY_CHOICE_KEY, UPLOAD_CHOICE_KEY } from '../../visualize/ui/CommonUIKeys.js'; import {HiPSTargetView} from '../../visualize/ui/TargetHiPSPanel.jsx'; import {showInfoPopup} from '../PopupUtil'; @@ -82,7 +82,7 @@ export const emptyHeaderSx = { * */ export function EmbeddedPositionSearchPanel({ - initSelectToggle= CONE_CHOICE_KEY, + initSelectToggle, nullAllowed= false, insetSpacial=true, usePosition= true, @@ -106,25 +106,36 @@ export function EmbeddedPositionSearchPanel({ //conditionally show UploadTableChooser only when uploadInfo is empty - TAP like behavior useEffect(() => { if (doGetSearchTypeOp() === UPLOAD_CHOICE_KEY) { - if (!uploadInfo.columns) showUploadTableChooser(setUploadInfo); + if (!uploadInfo?.columns) showUploadTableChooser(setUploadInfo); else setUploadInfo(uploadInfo); } }, [uploadInfo]); - if (!usePolygon && !usePosition && !useUpload) return false; - const doToggle= usePosition && usePolygon; - const initToggle= initSelectToggle; + const searchTypes = [ + {key: CONE_CHOICE_KEY, use: usePosition, label: 'Cone'}, + {key: POLY_CHOICE_KEY, use: usePolygon, label: 'Polygon'}, + {key: UPLOAD_CHOICE_KEY, use: useUpload, label: 'Multi-object'}, + ]; + + const enabledSearchTypes = searchTypes.filter((searchType)=>searchType.use); + if (enabledSearchTypes.length===0) return false; + const doToggle= enabledSearchTypes.length > 1; + const initToggle= initSelectToggle ?? enabledSearchTypes[0].key; + const searchTypeToggleOptions = enabledSearchTypes.map(({label, key:value})=>({label, value})); const doGetSearchTypeOp= () => { if (doToggle) return getSearchTypeOp() ?? initToggle; - if (usePolygon) return POLY_CHOICE_KEY; - if (useUpload) return UPLOAD_CHOICE_KEY; - return CONE_CHOICE_KEY; + return initToggle; }; const {targetKey=DEF_TARGET_PANEL_KEY}= slotProps.targetPanel ?? {}; const {polygonKey=DEFAULT_POLYGON_KEY, }= slotProps.polygonField ?? {}; - const {sizeKey= DEFAULT_SIZE_KEY, min= 1 / 3600, max= 1}= slotProps.sizeInput ?? {}; + const {sizeKey= DEFAULT_SIZE_KEY, min= 1 / 3600, max= 1, enabled:sizeEnabled=true}= slotProps.sizeInput ?? {}; + + if (useUpload && !sizeEnabled && enabledSearchTypes.length===2) { + // because in this case, 'Cone' or 'Polygon' label won't make sense + searchTypeToggleOptions[0].label = 'Single-object'; + } const { hipsUrl= DEFAULT_HIPS, @@ -151,7 +162,6 @@ export function EmbeddedPositionSearchPanel({ overflow: 'auto', }; - const sizeEnabled= slotProps?.sizeInput?.enabled ?? true; return ( { @@ -219,7 +229,7 @@ export function EmbeddedPositionSearchPanel({ > {children} @@ -341,6 +351,7 @@ function SearchSummary({request, targetKey, sizeKey, polygonKey, searchTypeKey, //Label/Key & Value pairs do display, calculating here to determine easily where the last comma should be const keyValuePairs = [ + //TODO: Search Type's 'v' needs to come from label of toggle options, not hard-coded searchType strings in this function { k: 'Search Type', v: searchType }, ...(radius && searchType === 'Cone' ? [{ k: 'Search Radius', v: radius }] : []), ...(coords && searchType !== 'Multi-Object' ? [{ k: 'Coordinates', v: coords }] : []), @@ -362,7 +373,7 @@ function SearchSummary({request, targetKey, sizeKey, polygonKey, searchTypeKey, } function SpatialSearch({rootSlotProps: slotProps, insetSpacial, uploadInfo, setUploadInfo, searchTypeOp, doToggle, - initToggle, nullAllowed, useUpload}) { + initToggle, nullAllowed, searchTypeToggleOptions}) { const { searchTypeKey=CONE_AREA_KEY, sx } = slotProps.spatialSearch ?? {}; return ( @@ -371,7 +382,7 @@ function SpatialSearch({rootSlotProps: slotProps, insetSpacial, uploadInfo, setU sx:{alignSelf: 'center'}, fieldKey: searchTypeKey, orientation: 'horizontal', tooltip: 'Chose type of search', initialState: {value: initToggle}, - options: useUpload ? CONE_AREA_OPTIONS_UPLOAD : CONE_AREA_OPTIONS + options: searchTypeToggleOptions }} />} {searchTypeOp === CONE_CHOICE_KEY && } {searchTypeOp === POLY_CHOICE_KEY && } @@ -438,7 +449,8 @@ function UploadOp({slotProps, uploadInfo, setUploadInfo}) { sizeKey= DEFAULT_SIZE_KEY, min= 1 / 3600, max= 1, - initValue= DEFAULT_INIT_SIZE_VALUE + initValue= DEFAULT_INIT_SIZE_VALUE, + enabled= true }= slotProps.sizeInput ?? {}; return ( @@ -448,15 +460,15 @@ function UploadOp({slotProps, uploadInfo, setUploadInfo}) { centerColsInnerStack: {sx: {ml: 1, pt: 1.5}} } }}/> - + }} />} ); diff --git a/src/firefly/js/ui/tap/WavelengthPanel.jsx b/src/firefly/js/ui/tap/WavelengthPanel.jsx index 9c525d3f9..4a39f10f3 100644 --- a/src/firefly/js/ui/tap/WavelengthPanel.jsx +++ b/src/firefly/js/ui/tap/WavelengthPanel.jsx @@ -6,7 +6,7 @@ import {CheckboxGroupInputField} from '../CheckboxGroupInputField.jsx'; import {FieldGroupCtx, ForceFieldGroupValid} from '../FieldGroup.jsx'; import {ListBoxInputField} from '../ListBoxInputField.jsx'; import {RadioGroupInputField} from '../RadioGroupInputField.jsx'; -import {useFieldGroupRerender, useFieldGroupWatch} from '../SimpleComponent.jsx'; +import {useFieldGroupRerender, useFieldGroupValue, useFieldGroupWatch} from '../SimpleComponent.jsx'; import {ValidationField} from '../ValidationField.jsx'; import {makeAdqlQueryRangeFragment, ConstraintContext, siaQueryRange} from './Constraints.js'; import {getTapObsCoreOptions} from './ObsCoreOptions'; @@ -21,6 +21,15 @@ const panelTitle = 'Spectral Coverage'; const panelValue = 'Wavelength'; const panelPrefix = getPanelPrefix(panelValue); +const obsCoreWvlFieldKeys = { + selectionType: 'obsCoreWavelengthSelectionType', + rangeType: 'obsCoreWavelengthRangeType', + wvlContains: 'obsCoreWavelengthContains', + wvlMin: 'obsCoreWavelengthMinRange', + wvlMax: 'obsCoreWavelengthMaxRange', + wvlUnits: 'obsCoreWavelengthUnits', +}; + function getExponent(units) { switch (units) { case 'nm': return 'e-9'; @@ -31,17 +40,18 @@ function getExponent(units) { } -function makeWavelengthConstraints(wavelengthSelection, rangeType, filterDefinitions, fldObj) { +function makeWavelengthConstraints(filterDefinitions, fldObj) { const errList= makeFieldErrorList(); const siaConstraints= []; const adqlConstraintsAry = []; - const {obsCoreWavelengthContains:wlContains, obsCoreWavelengthMinRange:wlMinRange, - obsCoreWavelengthMaxRange:wlMaxRange, obsCoreWavelengthUnits:wlUnits}= fldObj; + const {[obsCoreWvlFieldKeys.selectionType]:wavelengthSelection, [obsCoreWvlFieldKeys.rangeType]:rangeType, + [obsCoreWvlFieldKeys.wvlContains]:wlContains, [obsCoreWvlFieldKeys.wvlMin]:wlMinRange, + [obsCoreWvlFieldKeys.wvlMax]:wlMaxRange, [obsCoreWvlFieldKeys.wvlUnits]:wlUnits} = fldObj; // pull out the fields we care about - if (wavelengthSelection === 'filter') { + if (wavelengthSelection?.value === 'filter') { const rangeList = []; filterDefinitions.forEach((filterDefinition) => { const fieldKey = 'filter' + filterDefinition.name; @@ -66,9 +76,9 @@ function makeWavelengthConstraints(wavelengthSelection, rangeType, filterDefinit // Need at least one field to be non-empty errList.addError('at least one filter must be checked'); } - } else if (wavelengthSelection === 'numerical') { + } else { //wavelengthSelection fld is undefined (because of no filters), or 'numerical' (radio option) const exponent= getExponent(wlUnits?.value); - if (rangeType === 'contains') { + if (rangeType?.value === 'contains') { errList.checkForError(wlContains); if (wlContains?.valid) { const range = wlContains.value; @@ -82,7 +92,7 @@ function makeWavelengthConstraints(wavelengthSelection, rangeType, filterDefinit } } } - if (rangeType === 'overlaps') { + if (rangeType?.value === 'overlaps') { errList.checkForError(wlMinRange); errList.checkForError(wlMaxRange); const anyHasValue = wlMinRange?.value || wlMaxRange?.value; @@ -110,29 +120,170 @@ function makeWavelengthConstraints(wavelengthSelection, rangeType, filterDefinit const checkHeaderCtl= makeCollapsibleCheckHeader(getPanelPrefix(panelValue)); const {CollapsibleCheckHeader, collapsibleCheckHeaderKeys}= checkHeaderCtl; -const fldKeys= ['obsCoreWavelengthContains', 'obsCoreWavelengthMinRange','obsCoreWavelengthSelectionType', - 'obsCoreWavelengthRangeType', 'obsCoreWavelengthMaxRange', 'obsCoreWavelengthUnits']; -export function ObsCoreWavelengthSearch({initArgs, serviceLabel, slotProps,useSIAv2}) { +export function WavelengthOptions({initArgs, fieldKeys, filterDefinitions, slotProps }) { + const [getSelectionType,] = useFieldGroupValue(fieldKeys.selectionType); + const [getRangeType,] = useFieldGroupValue(fieldKeys.rangeType); + + const hasFilters = filterDefinitions?.length > 0; + const useNumerical = !hasFilters || getSelectionType() === 'numerical'; + + const units = ( + + + + + ); + + return ( + + {hasFilters && ( + + )} + + {hasFilters && getSelectionType() === 'filter' && ( + + Require coverage at the approximate center of these filters: + + {filterDefinitions.map((filterDefinition) => ( + + ))} + + + )} + + {useNumerical && ( + +
+ +
+ {getRangeType() === 'contains' && ( +
+ +
+ )} + {getRangeType() === 'overlaps' && ( + + + to + + {units} + + )} +
+ )} +
+ ); +} + +WavelengthOptions.propTypes = { + initArgs: PropTypes.object, + fieldKeys: PropTypes.shape({ + selectionType: PropTypes.string, + rangeType: PropTypes.string, + wvlContains: PropTypes.string, + wvlMin: PropTypes.string, + wvlMax: PropTypes.string, + wvlUnits: PropTypes.string, + }).isRequired, + filterDefinitions: PropTypes.arrayOf(PropTypes.shape({ + name: PropTypes.string, + options: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string, label: PropTypes.string })) + })), + slotProps: PropTypes.shape({ + selectionType: PropTypes.object, + rangeType: PropTypes.object, + wvlContains: PropTypes.object, + wvlMin: PropTypes.object, + wvlMax: PropTypes.object, + wvlUnits: PropTypes.object, + filterDefOptionsGroup: PropTypes.object, + numericalWvlOptions: PropTypes.object, + filterBandsWvlOptions: PropTypes.object, + }) +}; + + +export function ObsCoreWavelengthSearch({ initArgs, serviceLabel, slotProps, useSIAv2 }) { const filterDefinitions = getTapObsCoreOptions(serviceLabel).filterDefinitions ?? []; - const fdDefsKeys= filterDefinitions.length ? filterDefinitions.map((fd) =>'filter' +fd.name ) : []; + const fdDefsKeys = filterDefinitions.length ? filterDefinitions.map((fd) => 'filter' + fd.name) : []; + const fldKeys = Object.values(obsCoreWvlFieldKeys); - const {getVal,makeFldObj}= useContext(FieldGroupCtx); - const {setConstraintFragment}= useContext(ConstraintContext); + const { makeFldObj } = useContext(FieldGroupCtx); + const { setConstraintFragment } = useContext(ConstraintContext); const [constraintResult, setConstraintResult] = useState({}); - useFieldGroupRerender([...fldKeys,...fdDefsKeys, ...collapsibleCheckHeaderKeys]); // force rerender on any change + useFieldGroupRerender([...fldKeys, ...fdDefsKeys, ...collapsibleCheckHeaderKeys]); // force rerender on any change - - const rangeType= getVal('obsCoreWavelengthRangeType'); - const selectionType= getVal('obsCoreWavelengthSelectionType') ?? 'numerical'; - const hasFilters = filterDefinitions?.length > 0; - const useNumerical = !hasFilters || selectionType === 'numerical'; - const updatePanelStatus= makePanelStatusUpdater(checkHeaderCtl.isPanelActive(), panelValue); + const updatePanelStatus = makePanelStatusUpdater(checkHeaderCtl.isPanelActive(), panelValue); useEffect(() => { - const fldObj= makeFldObj([...fdDefsKeys, ...fldKeys]); - const constraints= makeWavelengthConstraints(selectionType,rangeType, filterDefinitions, fldObj); - updatePanelStatus(constraints, constraintResult, setConstraintResult,useSIAv2); + const fldObj = makeFldObj([...fdDefsKeys, ...fldKeys]); + const constraints = makeWavelengthConstraints(filterDefinitions, fldObj); + updatePanelStatus(constraints, constraintResult, setConstraintResult, useSIAv2); }); useEffect(() => { @@ -140,114 +291,23 @@ export function ObsCoreWavelengthSearch({initArgs, serviceLabel, slotProps,useSI return () => setConstraintFragment(panelPrefix, ''); }, [constraintResult]); - useFieldGroupWatch([...fdDefsKeys, - // 'obsCoreWavelengthContains', 'obsCoreWavelengthMinRange', 'obsCoreWavelengthMaxRange', 'obsCoreWavelengthUnits' ], - 'obsCoreWavelengthContains', 'obsCoreWavelengthMinRange', 'obsCoreWavelengthMaxRange' ], - (valAry,isInit) => { - !isInit && valAry.some((v)=>v) && checkHeaderCtl.setPanelActive(true); - }); - - const units= ( - - - - - ); - - + useFieldGroupWatch([...fdDefsKeys, obsCoreWvlFieldKeys.wvlContains, obsCoreWvlFieldKeys.wvlMin, obsCoreWvlFieldKeys.wvlMax], + (valAry, isInit) => { + !isInit && valAry.some((v) => v) && checkHeaderCtl.setPanelActive(true); + }); return ( - - + + - {hasFilters && } - {hasFilters && selectionType === 'filter' && - - Require coverage at the approximate center of these filters: - - {filterDefinitions.map((filterDefinition) => { - return ( - ); - })} - - - } - {useNumerical && - -
- -
- {rangeType === 'contains' && -
- -
- } - {rangeType === 'overlaps' && - - - to - - {units} - - } -
- } - + +
@@ -259,9 +319,7 @@ ObsCoreWavelengthSearch.propTypes = { serviceLabel: PropTypes.string, useSIAv2: PropTypes.bool, slotProps: PropTypes.shape({ - obsCoreWavelengthUnits: PropTypes.object, - obsCoreFilterDefinitions: PropTypes.object, - obsCoreWavelengthSelectionType: PropTypes.object, - obsCoreWavelengthRangeType: PropTypes.object, + root: PropTypes.object, + wavelengthOptions: WavelengthOptions.propTypes.slotProps }) };