Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/firefly/js/drawingLayers/FootprintTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Copy link
Copy Markdown
Contributor

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.

const {fpText, fpTextLoc, angle Deg, 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) &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want to move away from get

 !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),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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;
Expand Down
266 changes: 123 additions & 143 deletions src/firefly/js/drawingLayers/FootprintToolUI.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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}}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normally we would always want to use spacing:1, however the way this component is layed out it might not be necessary.

<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>
);
}


Expand Down