-
Notifications
You must be signed in to change notification settings - Fork 18
Firefly-2085: Refactor FootprintToolUI to Use a Functional Component #2008
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -395,13 +395,19 @@ function getLayerChanges(drawLayer, action) { | |
| return retV; | ||
|
|
||
| case MODIFY_CUSTOM_FIELD: | ||
| const {fpText, fpTextLoc, angleDeg} = action.payload.changes; | ||
| const {fpText, fpTextLoc, angleDeg, activePlotId} = action.payload.changes; | ||
|
|
||
| if (plotIdAry) { | ||
| if (!isNil(angleDeg)) { | ||
| return updateFootprintAngle(angleDeg, dd[DataTypes.DATA], plotIdAry); | ||
| } else { | ||
| return updateMarkerText(fpText, fpTextLoc, dd[DataTypes.DATA], plotIdAry); | ||
| // only update the layer title if there is a footprint in the active plot | ||
| const textApplied = isGoodPlot(activePlotId) && | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we want to move away from !isEmpty(dd.[DataTypes.DATA]?.activePlotId]) |
||
| !isEmpty(get(dd, [DataTypes.DATA, activePlotId])); | ||
|
|
||
| // the layer title tracks the label; an empty label falls back to the title with which it was created | ||
| return {...updateMarkerText(fpText, fpTextLoc, dd[DataTypes.DATA], plotIdAry), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a cool but very complex line. Maybe you should simplify it to. const title= {...(textApplied && {title: fpText || drawLayer.defaultTitle})};
return {...updateMarkerText(fpText, fpTextLoc, dd[DataTypes.DATA], plotIdAry), ...title};I think it would be easier to read. |
||
| ...(textApplied && {title: fpText || drawLayer.defaultTitle})}; | ||
| } | ||
| } | ||
| break; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,11 +2,12 @@ | |
| * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt | ||
| */ | ||
|
|
||
| import {Chip, Stack, Typography} from '@mui/joy'; | ||
| import React, {PureComponent} from 'react'; | ||
| import {Chip, Stack, Tooltip, Typography} from '@mui/joy'; | ||
| import React, {useState} from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
| import {flux} from '../core/ReduxFlux.js'; | ||
| import {ListBoxInputFieldView} from '../ui/ListBoxInputField.jsx'; | ||
| import {useStoreConnector} from '../ui/SimpleComponent.jsx'; | ||
| import {dispatchModifyCustomField} from '../visualize/DrawLayerDispatch'; | ||
| import {formatWorldPt} from '../visualize/ui/WorldPtFormat.jsx'; | ||
| import {DRAWING_LAYER_KEY} from '../visualize/VisConst'; | ||
|
|
@@ -17,170 +18,149 @@ import {ANGLE_UNIT} from '../visualize/draw/MarkerFootprintObj.js'; | |
| import {currentP, getDrawLayerById} from '../visualize/PlotViewUtil.js'; | ||
| import CsysConverter from '../visualize/CsysConverter.js'; | ||
| import {InputFieldView} from '../ui/InputFieldView.jsx'; | ||
| import {isNil} from 'lodash'; | ||
| import {sprintf} from '../externalSource/sprintf'; | ||
| import {FixedPtControl} from './FixedPtControl.jsx'; | ||
|
|
||
| export const getFootprintToolUIComponent = (drawLayer,pv) => <FootprintToolUI drawLayer={drawLayer} pv={pv}/>; | ||
| // key by plotId to handle switching between images | ||
| export const getFootprintToolUIComponent = (drawLayer,pv) => | ||
| <FootprintToolUI key={pv.plotId} drawLayer={drawLayer} pv={pv}/>; | ||
| export const defaultFootprintTextLoc = TextLocation.REGION_SE; | ||
|
|
||
| const precision = '%.1f'; | ||
|
|
||
| class FootprintToolUI extends PureComponent { | ||
| constructor(props) { | ||
| super(props); | ||
|
|
||
| const fpObj = this.props.drawLayer?.drawData?.data?.[this.props.pv.plotId] ?? {}; | ||
| const {angle = 0.0, angleUnit = ANGLE_UNIT.radian, text = '', textLoc = defaultFootprintTextLoc} = fpObj; | ||
| const angleDeg = `${formatAngle(convertAngle(angleUnit.key, 'deg', angle))}`; | ||
| const {fpInfo} = this.props.drawLayer; | ||
| const {currentPt} = fpObj?.actionInfo ?? {}; | ||
| const {plot}= currentP(this.props.pv.plotId); | ||
|
|
||
| this.csys = CsysConverter.make(plot); | ||
| this.state = {fpText: text, fpTextLoc: textLoc.key, angleDeg, fpInfo, | ||
| currentPt: this.csys.getWorldCoords(currentPt), isValidAngle: true}; | ||
| this.changeFootprintText = this.changeFootprintText.bind(this); | ||
| this.changeFootprintTextLocation = this.changeFootprintTextLocation.bind(this); | ||
| this.changeFootprintAngle = this.changeFootprintAngle.bind(this); | ||
| } | ||
| export function FootprintToolUI({drawLayer, pv}) { | ||
| const {drawLayerId, fpInfo} = drawLayer; | ||
| const {plotId} = pv; | ||
|
|
||
| const {hasData, currentPt, angle, angleUnit, angleFromUI, text, textLoc} = useStoreConnector(() => { | ||
| const dl = getDrawLayerById(flux.getState()[DRAWING_LAYER_KEY], drawLayerId); | ||
| const fpObj = dl?.drawData?.data?.[plotId]; | ||
| const {angle = 0.0, angleUnit = ANGLE_UNIT.radian, angleFromUI = false, | ||
| text = '', textLoc = defaultFootprintTextLoc} = fpObj ?? {}; | ||
| return {hasData: Boolean(fpObj), currentPt: fpObj?.actionInfo?.currentPt, | ||
| angle, angleUnit, angleFromUI, text, textLoc}; | ||
|
Comment on lines
+36
to
+41
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. put this in a function outside of the component, then ... = useStoreConnector(() -> getXXX(drawLayerId,plotId))It make the component easier to read. |
||
| }, [drawLayerId, plotId]); | ||
|
|
||
| const csys = CsysConverter.make(currentP(plotId)?.plot); | ||
| const derivedCenterPt = currentPt && csys?.getWorldCoords(currentPt); | ||
| const derivedCenterKey = derivedCenterPt | ||
| ? `${derivedCenterPt.x},${derivedCenterPt.y},${derivedCenterPt.cSys}` : ''; | ||
|
|
||
| const storeAngleDeg = formatAngle(convertAngle(angleUnit.key, 'deg', angle)); | ||
|
|
||
| const storeFpKey = `${storeAngleDeg}|${currentPt?.x},${currentPt?.y}`; | ||
|
|
||
| const [angleDeg, setAngleDeg] = useState(storeAngleDeg); | ||
| const [centerPt, setCenterPt] = useState(derivedCenterPt); | ||
| const [lastCenterKey, setLastCenterKey] = useState(derivedCenterKey); | ||
| const [lastStoreFpKey, setLastStoreFpKey] = useState(storeFpKey); | ||
| const [fpText, setFpText] = useState(text); | ||
| const [fpTextLoc, setFpTextLoc] = useState(textLoc); | ||
|
|
||
| const isValidAngle = !isNaN(parseFloat(angleDeg)); | ||
|
|
||
| if (hasData) { | ||
| // update the displayed center as the footprint moves. compare positions as strings. | ||
| // an empty key means there was no plot or stored center - in that case keep showing | ||
| // the last position instead of blanking the readout. | ||
| if (derivedCenterKey && derivedCenterKey !== lastCenterKey) { | ||
| setLastCenterKey(derivedCenterKey); | ||
| setCenterPt(derivedCenterPt); | ||
| } | ||
|
|
||
| // keep local state and take the store's value when it differs. | ||
| // the dispatch preserves in-progress edits. | ||
| if (text !== fpText) setFpText(text); | ||
|
|
||
| componentWillUnmount() { | ||
| this.iAmMounted= false; | ||
| if (this.removeListener) this.removeListener(); | ||
| } | ||
| if (textLoc !== fpTextLoc) setFpTextLoc(textLoc); | ||
|
|
||
| componentDidMount() { | ||
| this.iAmMounted= true; | ||
| this.removeListener= flux.addListener(() => this.stateUpdate()); | ||
| } | ||
|
|
||
| stateUpdate() { | ||
| const dl = getDrawLayerById(flux.getState()[DRAWING_LAYER_KEY], this.props.drawLayer.drawLayerId); | ||
|
|
||
| if (dl && this.iAmMounted) { | ||
| const crtFpObj = dl?.drawData?.data?.[this.props.pv.plotId]; | ||
|
|
||
| if (crtFpObj) { | ||
| let {currentPt} = crtFpObj?.actionInfo ?? {}; | ||
| if (currentPt) { | ||
| currentPt = this.csys.getWorldCoords(currentPt); | ||
| if (currentPt !== this.state.currentPt) { | ||
| this.setState({currentPt}); | ||
| } | ||
| } | ||
|
|
||
| if (!crtFpObj?.angleFromUI) { | ||
| var {angle = 0.0, angleUnit = ANGLE_UNIT.radian} = crtFpObj; | ||
|
|
||
| angle = convertAngle(angleUnit.key, 'deg', angle); | ||
| this.setState({angleDeg: `${formatAngle(angle)}`, isValidAngle: true}); | ||
| } | ||
|
|
||
| var {text = '', textLoc = defaultFootprintTextLoc} = crtFpObj; | ||
| if (text !== this.state.fpText) { | ||
| this.setState({fpText: text}); | ||
| } | ||
| if (textLoc.key !== this.state.fpTextLoc) { | ||
| this.setState({fpTextLoc: textLoc.key}); | ||
| } | ||
| // refresh the angle box when the stored angle or position changes (drag or rotation), but not | ||
| // while angleFromUI is set: that flag stays set after an edit rotates the footprint, so the | ||
| // field isn't rewritten between keystrokes. an entry that isn't a number is dropped. | ||
| if (storeFpKey !== lastStoreFpKey) { | ||
| setLastStoreFpKey(storeFpKey); | ||
| if (!angleFromUI || !isValidAngle) { | ||
| setAngleDeg(storeAngleDeg); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| changeFootprintText(ev) { | ||
| let fpText = ev?.target?.value; | ||
| const changeFootprintText = (ev) => { | ||
| const newText = ev?.target?.value ?? ''; | ||
|
|
||
| if (isNil(fpText) || !fpText) { | ||
| const dl = getDrawLayerById(flux.getState()[DRAWING_LAYER_KEY], this.props.drawLayer.drawLayerId); | ||
| setFpText(newText); | ||
| dispatchModifyCustomField(drawLayerId, | ||
| {fpText: newText, fpTextLoc, activePlotId: plotId}, | ||
| plotId); | ||
| }; | ||
|
|
||
| fpText = ''; | ||
| this.props.drawLayer.title = dl?.defaultTitle; | ||
| } else { | ||
| this.props.drawLayer.title = fpText; | ||
| } | ||
| this.setState({fpText}); | ||
| const changeFootprintTextLocation = (ev, newLocKey) => { | ||
| const newLoc = TextLocation.get(newLocKey) ?? defaultFootprintTextLoc; | ||
|
|
||
| dispatchModifyCustomField( this.props.drawLayer.drawLayerId, | ||
| {fpText, fpTextLoc: TextLocation.get(this.state.fpTextLoc), activePlotId: this.props.pv.plotId }, | ||
| this.props.pv.plotId); | ||
| } | ||
| setFpTextLoc(newLoc); | ||
| dispatchModifyCustomField(drawLayerId, | ||
| {fpText, fpTextLoc: newLoc, activePlotId: plotId}, | ||
| plotId); | ||
| }; | ||
|
|
||
| changeFootprintTextLocation(ev,fpTextLoc ) { | ||
| const changeFootprintAngle = (ev) => { | ||
| const newAngleDeg = ev?.target?.value ?? ''; | ||
| const valid = !isNaN(parseFloat(newAngleDeg)); | ||
|
|
||
| this.setState({fpTextLoc}); | ||
| dispatchModifyCustomField( this.props.drawLayer.drawLayerId, | ||
| {fpText: this.state.fpText, fpTextLoc: TextLocation.get(fpTextLoc), activePlotId: this.props.pv.plotId }, | ||
| this.props.pv.plotId); | ||
| } | ||
|
|
||
| changeFootprintAngle(ev) { | ||
| let angleDeg = ev?.target?.value; | ||
| let isValidAngle = true; | ||
| setAngleDeg(newAngleDeg); | ||
|
|
||
| if (isNaN(parseFloat(angleDeg))) { | ||
| if (!angleDeg) angleDeg = ''; | ||
| isValidAngle = false; | ||
| } | ||
| this.setState({isValidAngle, angleDeg}); | ||
| if (isValidAngle) { | ||
| dispatchModifyCustomField(this.props.drawLayer.drawLayerId, {angleDeg, activePlotId: this.props.pv.plotId }, | ||
| this.props.pv.plotId); | ||
| if (valid) { | ||
| dispatchModifyCustomField(drawLayerId, {angleDeg: newAngleDeg, activePlotId: plotId}, plotId); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const textOnLink = fpInfo?.fromFile ? `Add another ${fpInfo.fromFile}` | ||
| : fpInfo?.fromRegionAry | ||
| ? `Add another ${drawLayer.title}` | ||
| : `Add another ${fpInfo?.footprint}${fpInfo?.instrument ? ' '+fpInfo.instrument : ''}`; | ||
|
Comment on lines
+117
to
+120
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use another line to keep style consistent const textOnLink = fpInfo?.fromFile
? `Add another ${fpInfo.fromFile}`
: fpInfo?.fromRegionAry
? `Add another ${drawLayer.title}`
: `Add another ${fpInfo?.footprint}${fpInfo?.instrument ? ' '+fpInfo.instrument : ''}`; |
||
|
|
||
| return ( | ||
| <Stack {...{py:1, spacing:1}}> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. normally we would always want to use |
||
| <Stack {...{direction:'row', alignItems:'center', justifyContent:'flex-start', spacing:1, pl:1}}> | ||
| <Typography level='body-sm'>Center:</Typography> | ||
| {formatWorldPt(centerPt,3,false)} | ||
| <FixedPtControl wp={centerPt} pv={pv}/> | ||
| <InputFieldView | ||
| valid={isValidAngle} | ||
| orientation='horizontal' | ||
| slotProps={{input:{sx:{width:'7rem'}}}} | ||
| onChange={changeFootprintAngle} | ||
| value={angleDeg} | ||
| message='invalid angle value' | ||
| label='Angle:' | ||
| tooltip='Enter the angle in degree you want the footprint rotated' | ||
| /> | ||
| </Stack> | ||
|
|
||
| render() { | ||
| const {isValidAngle, angleDeg, fpText, fpTextLoc, fpInfo} = this.state; | ||
| const textOnLink = fpInfo?.fromFile ? `Add another ${fpInfo.fromFile}` | ||
| :fpInfo?.fromRegionAry | ||
| ? `Add another ${this.props.drawLayer.title}` | ||
| : `Add another ${fpInfo?.footprint} ${fpInfo?.instrument}`; | ||
|
|
||
|
|
||
| return ( | ||
| <Stack {...{py:1}}> | ||
| <Stack {...{direction:'row', alignItems:'center', justifyContent: 'flex-start', spacing:1, pl:1, pb:1}}> | ||
| <Typography level='body-sm'>Center:</Typography> | ||
| {formatWorldPt(this.state.currentPt,3,false)} | ||
| <FixedPtControl wp={this.state.currentPt} pv={this.props.pv}/> | ||
| </Stack> | ||
|
|
||
| <Stack {...{direction:'row', spacing:1, justifyContent:'flex-start'}}> | ||
| <Stack spacing={1}> | ||
| <InputFieldView label='Label' tooltip= 'Add a lable to this footprint' | ||
| slotProps={{input:{sx:{width:'15rem'}}}} | ||
| onChange={this.changeFootprintText} value={fpText}/> | ||
| <ListBoxInputFieldView | ||
| onChange={this.changeFootprintTextLocation} | ||
| value={fpTextLoc} | ||
| label='Label Location' tooltip='Choose a corner' | ||
| options={[ | ||
| {value: TextLocation.REGION_NE.key, label:'NE'}, | ||
| {value: TextLocation.REGION_NW.key, label:'NW'}, | ||
| {value: TextLocation.REGION_SE.key, label:'SE'}, | ||
| {value: TextLocation.REGION_SW.key, label:'SW'}, | ||
| ]}/> | ||
| </Stack> | ||
| <Stack spacing={1}> | ||
| <InputFieldView | ||
| valid={isValidAngle} | ||
| slotProps={{input:{sx:{width:'7rem'}}}} | ||
| onChange={this.changeFootprintAngle} | ||
| value={angleDeg} | ||
| message='invalid angle value' | ||
| label='Angle' | ||
| tooltip='Enter the angle in degree you want the footprint rotated' | ||
|
|
||
| /> | ||
| <Chip onClick={()=>addFootprintDrawLayer(this.props.pv, this.state.fpInfo)}> | ||
| {textOnLink} | ||
| </Chip> | ||
| </Stack> | ||
| </Stack> | ||
| <Stack {...{direction:'row', alignItems:'center', justifyContent:'flex-start', | ||
| spacing:1, pl:1, flexWrap:'wrap', useFlexGap:true}}> | ||
| <InputFieldView label='Label:' tooltip='Add a label to this footprint' | ||
| orientation='horizontal' | ||
| slotProps={{input:{sx:{width:'10em'}}}} | ||
| onChange={changeFootprintText} value={fpText}/> | ||
| <ListBoxInputFieldView | ||
| onChange={changeFootprintTextLocation} | ||
| value={fpTextLoc.key} | ||
| label='Corner:' tooltip='Choose a corner' | ||
| options={[ | ||
| {value: TextLocation.REGION_NE.key, label:'NE'}, | ||
| {value: TextLocation.REGION_NW.key, label:'NW'}, | ||
| {value: TextLocation.REGION_SE.key, label:'SE'}, | ||
| {value: TextLocation.REGION_SW.key, label:'SW'}, | ||
| ]}/> | ||
| <Tooltip title='Add an additional footprint'> | ||
| <Chip onClick={()=>addFootprintDrawLayer(pv, fpInfo)}> | ||
| {textOnLink} | ||
| </Chip> | ||
| </Tooltip> | ||
| </Stack> | ||
| ); | ||
| } | ||
| </Stack> | ||
| ); | ||
| } | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was not there before but for safety.