this.rootWidget= c,
+ }}
/>
);
}
}
MultiImageViewer.propTypes= {
- viewerId : PropTypes.string.isRequired,
- canReceiveNewPlots : PropTypes.string,
- Toolbar : PropTypes.func,
- handleToolbar : PropTypes.bool,
- forceRowSize : PropTypes.number,
- forceColSize : PropTypes.number,
- gridDefFunc : PropTypes.func,
- insideFlex : PropTypes.bool,
- closeFunc : PropTypes.func,
- tableId : PropTypes.string,
- controlViewerMounting : PropTypes.bool
+ viewerId : string.isRequired,
+ canReceiveNewPlots : string,
+ Toolbar : func,
+ Legend : func,
+ handleToolbar : bool,
+ forceRowSize : number,
+ forceColSize : number,
+ gridDefFunc : func,
+ insideFlex : bool,
+ closeFunc : func,
+ tableId : string,
+ controlViewerMounting : bool
};
// function gridDefFunc(plotIdAry) : [ {title :string, plotId:[string]}]
diff --git a/src/firefly/js/visualize/ui/MultiImageViewerView.jsx b/src/firefly/js/visualize/ui/MultiImageViewerView.jsx
index 359e4edb0..ddae5988e 100644
--- a/src/firefly/js/visualize/ui/MultiImageViewerView.jsx
+++ b/src/firefly/js/visualize/ui/MultiImageViewerView.jsx
@@ -5,8 +5,9 @@
import {Stack} from '@mui/joy';
import React, {useRef} from 'react';
-import PropTypes from 'prop-types';
+import {object, arrayOf, bool, func, number, oneOf, string, elementType, any} from 'prop-types';
import {omit} from 'lodash';
+import {checkProps} from '../../ui/SimpleComponent';
import {SINGLE, GRID, getMultiViewRoot, getViewer} from '../MultiViewCntlr.js';
import {primePlot} from '../PlotViewUtil.js';
import {MultiItemViewerView} from './MultiItemViewerView.jsx';
@@ -29,89 +30,70 @@ function makeState() {
}
export function MultiImageViewerView(props) {
+ checkProps(props, MultiImageViewerView);
const {current:elementWrapper}= useRef({element:undefined});
const {readout, readoutData, readoutShowing}= useMouseStoreConnector(makeState);
- const {Toolbar, visRoot, viewerPlotIds=[], showWhenExpanded=false, mouseReadoutEmbedded=true,
- handleToolbar=true, layoutType=GRID, scrollGrid, viewerId, ref}= props;
+ const {Toolbar, Legend, visRoot, viewerPlotIds=[], showWhenExpanded=false, mouseReadoutEmbedded=true,
+ handleToolbar=true, layoutType=GRID, scrollGrid=false, viewerId, ref}= props;
- const viewer= getViewer(getMultiViewRoot(),viewerId);
+ const viewer= getViewer(getMultiViewRoot(),viewerId) ?? {};
const {bottomUIComponent}= viewer ?? {};
- const makeToolbar = Toolbar ? () => () : undefined;
- const makeItemViewer = (plotId) => ( );
- const makeItemViewerFull = (plotId) => ( );
+ const makeToolbar = Toolbar && (() => ());
+ const makeLegend = Legend && (() => ());
+ const makeItemViewer = (plotId) => ( );
- const doReadoutAndShowing= readoutShowing && viewerPlotIds.includes(readoutData?.plotId);
+ const newProps = {...omit(props, ['Toolbar', 'visRoot', 'viewerPlotIds', 'showWhenExpanded']),
+ activeItemId: visRoot.activePlotId, viewerItemIds: viewerPlotIds,
+ makeToolbar:handleToolbar?makeToolbar:undefined, makeItemViewer, makeItemViewerFull:makeItemViewer};
- const newProps = Object.assign(omit(props, ['Toolbar', 'visRoot', 'viewerPlotIds', 'showWhenExpanded']),
- {activeItemId: visRoot.activePlotId, viewerItemIds: viewerPlotIds,
- makeToolbar:handleToolbar?makeToolbar:undefined, makeItemViewer, makeItemViewerFull});
+ const insideStyle= props.insideFlex ? {flex:'1 1 auto', maxWidth:'100%'} : {width:'100%', height:'100%'};
+ const style= { display:'flex', flexDirection:'column', position:'relative', ...insideStyle, ...props.style };
- let style= {display:'flex', flexDirection:'column', position:'relative'};
- if (props.insideFlex) {
- style= {...style, flex:'1 1 auto', maxWidth:'100%', ...props.style};
- }
- else {
- style= {...style, width:'100%', height:'100%', ...props.style};
- }
+ const pvToUseForReadout= isLockByClick(readout) ? primePlot(visRoot) : primePlot(visRoot,lastMouseCtx().plotId);
+ const one= layoutType===SINGLE || viewerPlotIds?.length===1;
+ const sStyle= {
+ position: 'absolute',
+ left: 3,
+ bottom:one ? 2 : 3,
+ right: one ? 3 : scrollGrid? 15 : 6,
+ };
- const {readoutPref}= readoutRoot();
- const pvToUse= isLockByClick(readoutRoot()) ? primePlot(visRoot) : primePlot(visRoot,lastMouseCtx().plotId);
- const radix= getFluxRadix(readoutPref, pvToUse);
-
-
- const mouseReadout= (
-
+ return (
+ elementWrapper.element= e}>
+
+
+ {bottomUIComponent?.()}
+
+
+
);
-
- if (layoutType===SINGLE || viewerPlotIds?.length===1) {
- return (
- elementWrapper.element= e}>
-
-
- {bottomUIComponent?.()}
- {mouseReadout}
-
-
- );
- }
- else {
- return (
- elementWrapper.element= e}>
-
-
- {bottomUIComponent?.()}
- {mouseReadout}
-
-
- );
- }
}
MultiImageViewerView.propTypes= {
- visRoot : PropTypes.object,
- viewerPlotIds : PropTypes.arrayOf(PropTypes.string).isRequired,
- showWhenExpanded : PropTypes.bool,
-
- Toolbar : PropTypes.func,
- viewerId : PropTypes.string.isRequired,
- style : PropTypes.object,
- defaultDecoration : PropTypes.bool,
- layoutType : PropTypes.oneOf([GRID,SINGLE]),
- forceRowSize : PropTypes.number, //optional - force a certain number of rows
- forceColSize : PropTypes.number, //optional - force a certain number of columns
- gridDefFunc : PropTypes.func, // optional - a function to return the grid definition
- gridComponent : PropTypes.object, // a react element to define the grid - not implemented, just an idea
- insideFlex : PropTypes.bool,
- handleToolbar: PropTypes.bool,
- scrollGrid: PropTypes.bool,
- mouseReadoutEmbedded: PropTypes.bool
+ visRoot : object,
+ viewerPlotIds : arrayOf(string).isRequired,
+ showWhenExpanded : bool,
+ Toolbar : elementType,
+ Legend : elementType,
+ viewerId : string.isRequired,
+ style : object,
+ defaultDecoration : bool,
+ layoutType : oneOf([GRID,SINGLE]),
+ forceRowSize : number, //optional - force a certain number of rows
+ forceColSize : number, //optional - force a certain number of columns
+ gridDefFunc : func, // optional - a function to return the grid definition
+ gridComponent : object, // a React element to define the grid - not implemented, just an idea
+ insideFlex : bool,
+ handleToolbar: bool,
+ scrollGrid: bool,
+ mouseReadoutEmbedded: bool,
+ ref: any,
};
diff --git a/src/firefly/js/visualize/ui/MultiItemViewerView.jsx b/src/firefly/js/visualize/ui/MultiItemViewerView.jsx
index c55cb4919..196a31f40 100644
--- a/src/firefly/js/visualize/ui/MultiItemViewerView.jsx
+++ b/src/firefly/js/visualize/ui/MultiItemViewerView.jsx
@@ -5,7 +5,8 @@
import {debounce} from 'lodash';
import React, {useEffect, useRef, useState} from 'react';
-import PropTypes from 'prop-types';
+import {bool, number, string, object, func, oneOf, arrayOf, any} from 'prop-types';
+import {checkProps} from '../../ui/SimpleComponent';
import {SINGLE, GRID} from '../MultiViewCntlr.js';
import {Divider, Stack} from '@mui/joy';
@@ -23,7 +24,7 @@ const defDecStyle= {
export function MultiItemViewerView(props) {
-
+ checkProps(props, MultiItemViewerView);
const {current:gridContainerElement}= useRef({element:undefined});
const [,setWindowWidth]= useState(window?.innerWidth??1000);
const {layoutType, activeItemId,
@@ -31,7 +32,6 @@ export function MultiItemViewerView(props) {
style, insideFlex=false, defaultDecoration=true, sparseGridTitleLocation= 'top',
scrollGrid=false, ref,
makeToolbar, makeItemViewer, makeItemViewerFull, autoRowOriented=true}= props;
- let wrapperStyle;
useEffect(() => {
if (!scrollGrid) return;
@@ -41,14 +41,12 @@ export function MultiItemViewerView(props) {
return () => {
window.removeEventListener('resize', browserResizeCallback);
};
- },[]);
+ },[]); // eslint-disable-line react-hooks/exhaustive-deps
+
+ const wrapperStyle= insideFlex
+ ? {...flexContainerStyle, flex:'1 1 auto'}
+ : {...flexContainerStyle, width:'100%', height:'100%'};
- if (insideFlex) {
- wrapperStyle= Object.assign({}, flexContainerStyle, {flex:'1 1 auto'});
- }
- else {
- wrapperStyle= Object.assign({}, flexContainerStyle, {width:'100%', height:'100%'});
- }
let container;
if (!viewerItemIds.length && !gridDefFunc) {
container= false;
@@ -74,13 +72,8 @@ export function MultiItemViewerView(props) {
container= makePackedGrid(viewerItemIds,rows,forceColSize,true,makeItemViewer);
}
else if (scrollGrid) {
- let cols;
- const {width:containerWidth}= gridContainerElement?.element ?
- gridContainerElement.element.getBoundingClientRect() : {width:0,height:0};
- if (viewerItemIds.length>16) cols=4;
- else if (viewerItemIds.length>5) cols=3;
- else cols=2;
- container= makeScrollGrid(viewerItemIds,cols,containerWidth, makeItemViewer);
+ const {width:containerWidth=0}= gridContainerElement?.element?.getBoundingClientRect() ?? {};
+ container= makeScrollGrid(viewerItemIds,containerWidth, makeItemViewer);
}
else { // GRID automatic
const dim= findAutoGridDim(viewerItemIds.length, autoRowOriented);
@@ -107,26 +100,27 @@ export function MultiItemViewerView(props) {
}
MultiItemViewerView.propTypes= {
- viewerId : PropTypes.string.isRequired,
- style : PropTypes.object,
- defaultDecoration : PropTypes.bool,
- layoutType : PropTypes.oneOf([GRID,SINGLE]),
- forceRowSize : PropTypes.number, //optional - force a certain number of rows
- forceColSize : PropTypes.number, //optional - force a certain number of columns
- makeCustomLayout : PropTypes.func, //optional - a function to present the items in a custom layout
- gridDefFunc : PropTypes.func, // optional - a function to return the grid definition
- gridComponent : PropTypes.object, // an element to define the grid - not implemented, just an idea
- scrollGrid: PropTypes.bool,
- insideFlex : PropTypes.bool,
- autoRowOriented: PropTypes.bool,
-
- viewerItemIds : PropTypes.arrayOf(PropTypes.string).isRequired,
- activeItemId : PropTypes.string,
- makeToolbar : PropTypes.func,
- makeItemViewer : PropTypes.func,
- makeItemViewerFull : PropTypes.func,
- eventCallback: PropTypes.object,
- sparseGridTitleLocation : PropTypes.oneOf(['top', 'left', 'off', ''])
+ viewerId : string.isRequired,
+ style : object,
+ defaultDecoration : bool,
+ layoutType : oneOf([GRID,SINGLE]),
+ forceRowSize :number, //optional - force a certain number of rows
+ forceColSize : number, //optional - force a certain number of columns
+ makeCustomLayout : func, //optional - a function to present the items in a custom layout
+ gridDefFunc : func, // optional - a function to return the grid definition
+ gridComponent : object, // an element to define the grid - not implemented, just an idea
+ scrollGrid: bool,
+ insideFlex : bool,
+ autoRowOriented: bool,
+
+ viewerItemIds : arrayOf(string).isRequired,
+ activeItemId : string,
+ makeToolbar : func,
+ makeItemViewer : func,
+ makeItemViewerFull : func,
+ eventCallback: object,
+ sparseGridTitleLocation : oneOf(['top', 'left', 'off', '']),
+ ref: any,
};
@@ -143,7 +137,13 @@ function makePackedGrid(viewerItemIds,rows,cols, columnBased,makeItemViewer) {
rowBasedIvAry(viewerItemIds,rows,percentWidth,percentHeight,width,height,makeItemViewer);
}
-function makeScrollGrid(viewerItemIds,cols,containerWidth, makeItemViewer) {
+function makeScrollGrid(viewerItemIds,containerWidth, makeItemViewer) {
+ const cols =
+ viewerItemIds.length>16
+ ? 4
+ : viewerItemIds.length>5
+ ? 3
+ : 2;
const size= 100/cols;
const sizePx= containerWidth ? Math.trunc((size/100)*containerWidth-2) : 0;
const width= `calc(${size}% - 2px)`;
@@ -225,7 +225,7 @@ function findAutoGridDim(size, rowOriented=true) {
* an empty element will act as a placeholder in the row.
*
* @param {Array} viewerItemIds
- * @param {Array.<{string,string[]}>} gridDef
+ * @param {Array.<{title:string,plotIdAry:string[],noDataMessage:string,size:number}>} gridDef
* @param {Function} makeItemViewer
* @param sparseGridTitleLocation
*/
diff --git a/src/firefly/js/visualize/ui/VisInlineToolbarView.jsx b/src/firefly/js/visualize/ui/RightSidePvDecoration.jsx
similarity index 69%
rename from src/firefly/js/visualize/ui/VisInlineToolbarView.jsx
rename to src/firefly/js/visualize/ui/RightSidePvDecoration.jsx
index c6176ec19..d54fc7785 100644
--- a/src/firefly/js/visualize/ui/VisInlineToolbarView.jsx
+++ b/src/firefly/js/visualize/ui/RightSidePvDecoration.jsx
@@ -5,7 +5,7 @@
import {Box, ChipDelete, Stack, Typography} from '@mui/joy';
import {isString} from 'lodash';
import React, {memo} from 'react';
-import {object, bool, number, string} from 'prop-types';
+import {object, bool, number, string, func} from 'prop-types';
import {showInfoPopup} from '../../ui/PopupUtil';
import {PlotAttribute} from '../PlotAttribute';
import {makeMouseStatePayload, fireMouseCtxChange, MouseState} from '../VisMouseSync.js';
@@ -16,8 +16,8 @@ import {WarningButton} from './Buttons';
-export const VisInlineToolbarView = memo( (props) => {
- const {pv, showDelete,deleteVisible, topOffset=0}= props;
+export const RightSidePvDecoration = memo( (props) => {
+ const {pv, showDelete,deleteVisible, makeLegend}= props;
if (!pv) return undefined;
const deleteClick= () => {
const mouseStatePayload= makeMouseStatePayload(undefined,MouseState.EXIT,undefined,0,0,'');
@@ -32,17 +32,24 @@ export const VisInlineToolbarView = memo( (props) => {
};
const warnAry= getWarningsAry(pv);
- if (!showDelete && !warnAry?.length) return;
+ if (!showDelete && !warnAry?.length && !makeLegend?.()) return;
return (
-
-
-
- {showDelete &&
- }
+
+
+
+
+ {showDelete &&
+ }
+
+ { makeLegend &&
+
+ {makeLegend()}
+
+ }
+
);
@@ -87,10 +94,11 @@ function WarningsAlert({pv}) {
-VisInlineToolbarView.propTypes= {
+RightSidePvDecoration.propTypes= {
pv : object,
showDelete : bool,
deleteVisible : bool,
help_id : string,
- topOffset: number
+ topOffset: number,
+ makeLegend : func,
};
diff --git a/src/firefly/js/visualize/ui/SmallLegend.jsx b/src/firefly/js/visualize/ui/SmallLegend.jsx
new file mode 100644
index 000000000..1a9179ee0
--- /dev/null
+++ b/src/firefly/js/visualize/ui/SmallLegend.jsx
@@ -0,0 +1,113 @@
+
+import {isString} from 'lodash';
+import React from 'react';
+import {Card, Divider, Stack, Switch, Typography} from '@mui/joy';
+import Catalog from '../../drawingLayers/Catalog';
+import HpxCatalog from '../../drawingLayers/hpx/HpxCatalog';
+import HiPSMOC from '../../drawingLayers/HiPSMOC.js';
+import {DropDownToolbarButton} from '../../ui/DropDownToolbarButton';
+import BrowserInfo from '../../util/BrowserInfo';
+import {PlotAttribute} from '../PlotAttribute';
+import {
+ getActivePlotView, getAllDrawLayersForPlot, getLayerTitle, getPlotViewById, isDrawLayerVisible, primePlot
+} from '../PlotViewUtil';
+import {DrawLayerLegendView} from './DrawLayerItemView';
+import {changeVisible, modifyDrawColor} from './DrawLayerUIComponents';
+import {SelectAreaDropDownView} from './SelectAreaDropDownView';
+
+
+
+
+export function SmallLegend({dlAry, visRoot, menuItemKeys={}}) {
+
+ const pv= getActivePlotView(visRoot);
+ if (!pv) return;
+ const {plotId}= pv;
+ const {legendSelectArea}= menuItemKeys;
+ const layers= getAllDrawLayersForPlot(dlAry,plotId).filter( ({drawLayerTypeId}) =>
+ drawLayerTypeId===HiPSMOC.TYPE_ID ||
+ drawLayerTypeId===HpxCatalog.TYPE_ID ||
+ drawLayerTypeId===Catalog.TYPE_ID);
+
+ const hasLayers= Boolean(layers.length);
+
+ if (!hasLayers && !legendSelectArea) return;
+
+ const maxTitleChars= layers.reduce( (max,l) => {
+ const t= getLayerTitle(pv.plotId,l);
+ let tLen= 0;
+ if (t) tLen= l.autoFormatTitle? t.length : 30;
+ return Math.max(max, tLen);
+ },20);
+
+ const sx= (theme) => ({
+ '--Card-padding': '.5rem',
+ mr:.25,
+ overflow:'hidden',
+ backgroundColor: BrowserInfo.supportsCssColorMix() ?
+ `color-mix(in srgb, ${theme.vars.palette.neutral.softBg} 90%, transparent)` :
+ theme.vars.palette.neutral.softBg,
+ });
+
+
+
+ return (
+
+
+ {hasLayers && layers.map( (dl) => (
+ changeVisible(dl, !isDrawLayerVisible(dl,plotId),plotId ),
+ modifyColor: () => modifyDrawColor(dl,pv.plotId,dl.tbl_id),
+ }}/>
+ )) }
+ {legendSelectArea && }
+
+
+ );
+}
+
+function SelectArea({pv,hasLayers}) {
+ const plot= primePlot(pv);
+ if (!plot) return;
+ const att= plot?.attributes;
+ const reselect= Boolean(
+ (att[PlotAttribute.USER_SEARCH_RADIUS_DEG] && att[PlotAttribute.USER_SEARCH_WP])
+ || (att[PlotAttribute.USE_POLYGON] && att[PlotAttribute.POLYGON_ARY])
+ );
+ return (
+
+ {hasLayers && }
+
+
+ }} />
+
+
+ );
+}
+
+function getShortTitle(plotId,dl) {
+ const shortTitle= dl.shortTitle;
+ if (shortTitle) return shortTitle;
+ const title= getLayerTitle(plotId,dl);
+ if (isString(title) && title.startsWith('MOC - ')) return title.substr(6);
+ return title;
+}
\ No newline at end of file
diff --git a/src/firefly/js/visualize/ui/TargetHiPSPanel.jsx b/src/firefly/js/visualize/ui/TargetHiPSPanel.jsx
index 64359d160..7704b4ffc 100644
--- a/src/firefly/js/visualize/ui/TargetHiPSPanel.jsx
+++ b/src/firefly/js/visualize/ui/TargetHiPSPanel.jsx
@@ -45,6 +45,7 @@ import {createHiPSMocLayerFromPreloadedTable} from '../task/PlotHipsTask.js';
import {WebPlotRequest} from '../WebPlotRequest.js';
import {CONE_CHOICE_KEY, POLY_CHOICE_KEY} from './CommonUIKeys.js';
import {MultiImageViewer} from './MultiImageViewer.jsx';
+import {SmallLegend} from './SmallLegend';
import {HelpLines, targetHipsDefaultMenuItemKey, TargetHipsPanelToolbar} from './TargetHipsPanelToolbar.jsx';
import {closeToolbarModalLayers} from './ToolbarToolModalEnd.js';
import {
@@ -153,7 +154,7 @@ export const HiPSTargetView = ({sx, hipsDisplayKey='none',
useEffect(() => {
setMocError();
void updateMoc(mocList,plotId,setMocError);
- }, [mocList]);
+ }, [mocList, plotId]);
useEffect(() => { // if plot view changes then update the target or polygon field
const setWhichOverlayWrapper= setWhichOverlay ?
@@ -165,7 +166,7 @@ export const HiPSTargetView = ({sx, hipsDisplayKey='none',
setHiPSRadius,getHiPSRadius,setPolygon,getPolygon,minSize,maxSize,
canUpdateModalEndInfo:false
});
- },[pv]);
+ },[pv]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => { // if target or radius field change then hips plot to reflect it
const whichOverlay= getWhichOverlay();
@@ -183,7 +184,7 @@ export const HiPSTargetView = ({sx, hipsDisplayKey='none',
useEffect(() => {
attachSRegion(sRegion,plotId);
- }, [sRegion]);
+ }, [plotId, sRegion]);
return (
@@ -198,7 +199,8 @@ export const HiPSTargetView = ({sx, hipsDisplayKey='none',
whichOverlay={getWhichOverlay()}
toolbarHelpId={toolbarHelpId}
handleToolbar={false}
- menuItemKeys={{...targetHipsDefaultMenuItemKey, selectArea:usingRadius}}
+ menuItemKeys={{...targetHipsDefaultMenuItemKey, selectArea:false, legendSelectArea:usingRadius}}
+ Legend={SmallLegend}
Toolbar={TargetHipsPanelToolbar}/>
);
@@ -312,7 +314,7 @@ function HiPSPanelPopupButton({groupKey:gk, polygonKey, whichOverlay=CONE_CHOICE
connectContext?.setControlConnected(false);
}
};
- },[]);
+ },[]); // eslint-disable-line react-hooks/exhaustive-deps
return (
{
{whichOverlay===CONE_CHOICE_KEY ?
(<>
- Click to choose a search center, or use the Selection Tools (
- {selectButton}
- ) to choose a search center and radius.
+ Click to choose a search center, or use the Selection Tools
+ to choose a search center and radius.
> ) :
(<>
- Use the Selection Tools (
- {selectButton}
- ) to choose a search polygon. Click to change the center.
+ Use the Selection Tools
+ to choose a search polygon. Click to change the center.
> )}