/* * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt */ import {has,isArray,omit} from 'lodash'; import Enum from 'enum'; import {flux} from '../Firefly.js'; import {ZoomType} from './ZoomType.js'; import {reducer as plotChangeReducer} from './reducer/HandlePlotChange.js'; import {reducer as plotCreationReducer} from './reducer/HandlePlotCreation.js'; import {reducer as plotAdminReducer} from './reducer/HandlePlotAdmin.js'; import {getPlotGroupById} from './PlotGroup.js'; import {isActivePlotView, getPlotViewById, getOnePvOrGroup, primePlot} from './PlotViewUtil.js'; import {dispatchReplaceViewerItems, EXPANDED_MODE_RESERVED} from './MultiViewCntlr.js'; import {REINIT_APP} from '../core/AppDataCntlr.js'; import {UserZoomTypes} from './ZoomUtil.js'; import {zoomActionCreator} from './task/ZoomTask.js'; import {convertToIdentityObj} from '../util/WebUtil.js'; import {changePrime} from './ChangePrime.js'; import {makePlotImageAction} from './task/PlotImageTask.js'; import {makePlotHiPSAction, makeChangeHiPSAction, makeImageOrHiPSAction} from './task/PlotHipsTask.js'; import {plotImageMaskActionCreator, plotImageMaskLazyActionCreator, overlayPlotChangeAttributeActionCreator} from './task/ImageOverlayTask.js'; import {colorChangeActionCreator, stretchChangeActionCreator, cropActionCreator} from './task/PlotChangeTask.js'; import {wcsMatchActionCreator} from './task/WcsMatchTask.js'; import {autoPlayActionCreator, changePointSelectionActionCreator, restoreDefaultsActionCreator, deletePlotViewActionCreator} from './task/PlotAdminTask.js'; import {processScrollActionCreator, recenterActionCreator} from './task/PlotChangeTask'; /** enum can be 'COLLAPSE', 'GRID', 'SINGLE' */ export const ExpandType= new Enum(['COLLAPSE', 'GRID', 'SINGLE']); /** enum can be 'Standard', 'Target' */ export const WcsMatchType= new Enum(['Standard', 'Target', 'Pixel', 'PixelCenter']); /** * enum can be 'GROUP', 'SINGLE', 'LIST' * @public * @global */ export const ActionScope= new Enum(['GROUP','SINGLE', 'LIST']); export const PLOTS_PREFIX= 'ImagePlotCntlr'; /** Action Type: plot of new image started */ const PLOT_IMAGE_START= `${PLOTS_PREFIX}.PlotImageStart`; /** Action Type: plot of new image failed */ const PLOT_IMAGE_FAIL= `${PLOTS_PREFIX}.PlotImageFail`; /** Action Type: plot of new image completed */ const PLOT_IMAGE= `${PLOTS_PREFIX}.PlotImage`; /** Action Type: plot of new HiPS image */ const PLOT_HIPS= `${PLOTS_PREFIX}.PlotHiPS`; const CHANGE_HIPS= `${PLOTS_PREFIX}.ChangeHiPS`; const PLOT_HIPS_OR_IMAGE= `${PLOTS_PREFIX}.plotHiPSOrImage`; const PLOT_HIPS_FAIL= `${PLOTS_PREFIX}.PlotHiPSFail`; /** Action Type: A image replot occurred */ const ANY_REPLOT= `${PLOTS_PREFIX}.Replot`; /** Action Type: start the zoom process. The image will appear zoomed by scaling, the server has not updated yet */ const ZOOM_IMAGE_START= `${PLOTS_PREFIX}.ZoomImageStart`; /** Action Type: The zoom from the server has complete */ const ZOOM_IMAGE= `${PLOTS_PREFIX}.ZoomImage`; const ZOOM_IMAGE_FAIL= `${PLOTS_PREFIX}.ZoomImageFail`; const ZOOM_LOCKING= `${PLOTS_PREFIX}.ZoomEnableLocking`; /** Action Type: image with new color table call started */ const COLOR_CHANGE_START= `${PLOTS_PREFIX}.ColorChangeStart`; /** Action Type: image with new color table loaded */ const COLOR_CHANGE= `${PLOTS_PREFIX}.ColorChange`; const COLOR_CHANGE_FAIL= `${PLOTS_PREFIX}.ColorChangeFail`; /** Action Type: server image stretch call started */ const STRETCH_CHANGE_START= `${PLOTS_PREFIX}.StretchChangeStart`; /** Action Type: image loaded with new stretch */ const STRETCH_CHANGE= `${PLOTS_PREFIX}.StretchChange`; const STRETCH_CHANGE_FAIL= `${PLOTS_PREFIX}.StretchChangeFail`; /** Action Type: image rotated */ const ROTATE= `${PLOTS_PREFIX}.Rotate`; /** Action Type: image flipped */ const FLIP= `${PLOTS_PREFIX}.Flip`; const CROP_START= `${PLOTS_PREFIX}.CropStart`; /** Action Type: image cropped */ const CROP= `${PLOTS_PREFIX}.Crop`; const CROP_FAIL= `${PLOTS_PREFIX}.CropFail`; const UPDATE_VIEW_SIZE= `${PLOTS_PREFIX}.UpdateViewSize`; const PROCESS_SCROLL= `${PLOTS_PREFIX}.ProcessScroll`; const CHANGE_CENTER_OF_PROJECTION= `${PLOTS_PREFIX}.changeCenterOfProjection`; /** Action Type: Recenter in image on the active target */ const RECENTER= `${PLOTS_PREFIX}.recenter`; /** Action Type: replot the image with the original plot parameters */ const RESTORE_DEFAULTS= `${PLOTS_PREFIX}.restoreDefaults`; const POSITION_LOCKING= `${PLOTS_PREFIX}.PositionLocking`; const OVERLAY_COLOR_LOCKING= `${PLOTS_PREFIX}.OverlayColorLocking`; const CHANGE_POINT_SELECTION= `${PLOTS_PREFIX}.ChangePointSelection`; const CHANGE_ACTIVE_PLOT_VIEW= `${PLOTS_PREFIX}.ChangeActivePlotView`; const CHANGE_PLOT_ATTRIBUTE= `${PLOTS_PREFIX}.ChangePlotAttribute`; /** Action Type: display mode to or from expanded */ const CHANGE_EXPANDED_MODE= `${PLOTS_PREFIX}.changeExpandedMode`; /** Action Type: turn on/off expanded auto-play */ const EXPANDED_AUTO_PLAY= `${PLOTS_PREFIX}.expandedAutoPlay`; /** Action Type: change the primary plot for a multi image fits display */ const CHANGE_PRIME_PLOT= `${PLOTS_PREFIX}.changePrimePlot`; const CHANGE_IMAGE_VISIBILITY= `${PLOTS_PREFIX}.changeImageVisibility`; const CHANGE_MOUSE_READOUT_MODE=`${PLOTS_PREFIX}.changeMouseReadoutMode`; /** Action Type: delete a plotView */ const DELETE_PLOT_VIEW=`${PLOTS_PREFIX}.deletePlotView`; const PLOT_MASK_START= `${PLOTS_PREFIX}.plotMaskStart`; /** Action Type: add a mask image*/ const PLOT_MASK=`${PLOTS_PREFIX}.plotMask`; const PLOT_MASK_LAZY_LOAD=`${PLOTS_PREFIX}.plotMaskLazyLoad`; const PLOT_MASK_FAIL= `${PLOTS_PREFIX}.plotMaskFail`; const DELETE_OVERLAY_PLOT=`${PLOTS_PREFIX}.deleteOverlayPlot`; const OVERLAY_PLOT_CHANGE_ATTRIBUTES=`${PLOTS_PREFIX}.overlayPlotChangeAttributes`; const CHANGE_HIPS_IMAGE_CONVERSION=`${PLOTS_PREFIX}.changeHipsImageConversion`; const CHANGE_TABLE_AUTO_SCROLL=`${PLOTS_PREFIX}.changeTableAutoScroll`; const USE_TABLE_AUTO_SCROLL=`${PLOTS_PREFIX}.useTableAutoScroll`; const WCS_MATCH=`${PLOTS_PREFIX}.wcsMatch`; const PLOT_PROGRESS_UPDATE= `${PLOTS_PREFIX}.PlotProgressUpdate`; const API_TOOLS_VIEW= `${PLOTS_PREFIX}.apiToolsView`; const ADD_PROCESSED_TILES= `${PLOTS_PREFIX}.addProcessedTiles`; /** Action Type: enable/disable wcs matching*/ export const IMAGE_PLOT_KEY= 'allPlots'; /** * @returns {VisRoot} * * @public * @function visRoot * @memberof firefly.action * */ export function visRoot() { return flux.getState()[IMAGE_PLOT_KEY]; } /** * * @returns {VisRoot} */ const initState= function() { /** * @global * @public * @typedef {Object} VisRoot * * @summary The state of the Image visualization. * The state contains an array of PlotView each have a plotId and tie to an Image Viewer, * one might be active (PlotView.js) * A PlotView has an array of WebPlots, one is primary (WebPlot.js) * An ImageViewer shows the primary plot of a plotView. (ImageView.js) * * @prop {String} activePlotId the id of the active plot * @prop {PlotView[]} plotViewAry view array * @prop {PlotGroup[]} plotGroupAry view array * @prop {object} plotRequestDefaults - can have multiple values * @prop {ExpandType} expandedMode status of expand mode * @prop {ExpandType} previousExpandedMode the value last time it was expanded * @prop {boolean} singleAutoPlay true if auto play on in expanded mode * @prop {boolean} apiToolsView true if working in api mode * @prop {boolean} positionLock plots are locked together for scrolling and rotation. */ return { activePlotId: null, plotViewAry : [], //there is one plot view for every ImageViewer, a plotView will have a plotId plotGroupAry : [], // there is one for each group, a plot group may have multiple plotViews // plotHistoryRequest: [], //todo prevActivePlotId: null, plotRequestDefaults : {}, // object: if normal request; // {plotId : {threeColor:boolean, wpRequest : object, } // if 3 color: // {plotId : {threeColor:boolean, // redReq : object, // greenReq : object, // blueReq : object } positionLock: false, //-- expanded settings expandedMode: ExpandType.COLLAPSE, previousExpandedMode: ExpandType.GRID, // must be SINGLE OR GRID singleAutoPlay : false, //-- misc pointSelEnableAry : [], apiToolsView: false, autoScrollToHighlightedTableRow: true, useAutoScrollToHighlightedTableRow: true, // this is not a use option, it is use to handle temporary disabling auto scroll` //-- wcs match parameters wcsMatchCenterWP: null, wcsMatchType: false, mpwWcsPrimId: null, processedTiles: [] }; }; /** * @global * @public * @typedef {Object} ProcessedTiles * * @prop {string} plotId * @prop {string} plotImageId * @prop {string} imageOverlayId * @prop {number} zoomFactor * @prop {Array.<ClientTile>} clientTileAry */ /** * @global * @public * @typedef {Object} ClientTile * * @prop {Object} tileAttributes * @prop {String} dataUrl * @prop {number} width - width of this tile * @prop {number} height - height of this tile * @prop {number} index - index of this tile * @prop {string} url - original file key to use in the service to retrieve this tile * @prop {number} x - pixel offset of this tile * @prop {number} y - pixel offset of this tile */ function reducers() { return { [IMAGE_PLOT_KEY]: reducer, }; } function actionCreators() { return { [PLOT_HIPS_OR_IMAGE]: makeImageOrHiPSAction, [PLOT_HIPS]: makePlotHiPSAction, [CHANGE_HIPS]: makeChangeHiPSAction, [PLOT_IMAGE]: makePlotImageAction, [PLOT_MASK]: plotImageMaskActionCreator, [PLOT_MASK_LAZY_LOAD]: plotImageMaskLazyActionCreator, [OVERLAY_PLOT_CHANGE_ATTRIBUTES]: overlayPlotChangeAttributeActionCreator, [ZOOM_IMAGE]: zoomActionCreator, [COLOR_CHANGE]: colorChangeActionCreator, [STRETCH_CHANGE]: stretchChangeActionCreator, [CROP]: cropActionCreator, [CHANGE_PRIME_PLOT] : changePrimeActionCreator, [CHANGE_POINT_SELECTION]: changePointSelectionActionCreator, [RESTORE_DEFAULTS]: restoreDefaultsActionCreator, [EXPANDED_AUTO_PLAY]: autoPlayActionCreator, [WCS_MATCH]: wcsMatchActionCreator, [DELETE_PLOT_VIEW]: deletePlotViewActionCreator, [RECENTER]: recenterActionCreator, [PROCESS_SCROLL]: processScrollActionCreator, }; } export default { reducers, actionCreators, ANY_REPLOT, PLOT_IMAGE_START, PLOT_IMAGE_FAIL, PLOT_IMAGE, PLOT_HIPS, PLOT_HIPS_FAIL, CHANGE_HIPS, ZOOM_IMAGE_START, ZOOM_IMAGE_FAIL, ZOOM_IMAGE,ZOOM_LOCKING, CHANGE_CENTER_OF_PROJECTION, ROTATE, FLIP, CROP_START, CROP, CROP_FAIL, COLOR_CHANGE_START, COLOR_CHANGE, COLOR_CHANGE_FAIL, STRETCH_CHANGE_START, STRETCH_CHANGE, STRETCH_CHANGE_FAIL, CHANGE_POINT_SELECTION, CHANGE_EXPANDED_MODE, PLOT_PROGRESS_UPDATE, UPDATE_VIEW_SIZE, PROCESS_SCROLL, RECENTER, OVERLAY_COLOR_LOCKING, POSITION_LOCKING, RESTORE_DEFAULTS, CHANGE_PLOT_ATTRIBUTE,EXPANDED_AUTO_PLAY, DELETE_PLOT_VIEW, CHANGE_ACTIVE_PLOT_VIEW, CHANGE_PRIME_PLOT, CHANGE_IMAGE_VISIBILITY, PLOT_MASK, PLOT_MASK_START, PLOT_MASK_FAIL, PLOT_MASK_LAZY_LOAD, DELETE_OVERLAY_PLOT, OVERLAY_PLOT_CHANGE_ATTRIBUTES, WCS_MATCH, ADD_PROCESSED_TILES, API_TOOLS_VIEW, CHANGE_MOUSE_READOUT_MODE, CHANGE_HIPS_IMAGE_CONVERSION, CHANGE_TABLE_AUTO_SCROLL, USE_TABLE_AUTO_SCROLL }; const KEY_ROOT= 'progress-'; let keyCnt= 0; export function makeUniqueRequestKey(prefix= KEY_ROOT) { const requestKey= `${prefix}-${keyCnt}-${Date.now()}`; keyCnt++; return requestKey; } //======================================== Dispatch Functions ============================= //======================================== Dispatch Functions ============================= //======================================== Dispatch Functions ============================= /** * Tweek how the API image view works * @param {boolean} apiToolsView * @param {boolean} useFloatToolbar * * @public * @function dispatchApiToolsView * @memberof firefly.action */ export function dispatchApiToolsView(apiToolsView, useFloatToolbar= true) { flux.process({ type: API_TOOLS_VIEW , payload: { apiToolsView, useFloatToolbar}}); } /** * * @param {String} plotId - * @param {String} message - the message if working or failure * @param {boolean} done - true if completed, false if still working * @param {String} requestKey * @param {boolean} [callSuccess=true] - true if success, false otherwise, parameter ignored if done is false */ export function dispatchPlotProgressUpdate(plotId, message, done, requestKey, callSuccess= true ) { flux.process({ type: PLOT_PROGRESS_UPDATE, payload: { plotId, done, message, requestKey, callSuccess }}); } /** * Notify that the size of the plot viewing area has changed * * @param {string} plotId * @param {number} [width] this parameter should be the offsetWidth of the dom element * @param {number} [height] this parameter should be the offsetHeight of the dom element */ export function dispatchUpdateViewSize(plotId,width,height) { flux.process({type: UPDATE_VIEW_SIZE, payload: {plotId, width, height} }); } /** * change overlay/color lock for color change and overlays * * @param {string} plotId is required * @param {boolean} overlayColorLock true to set group lockRelated on */ export function dispatchOverlayColorLocking(plotId,overlayColorLock) { flux.process({ type: OVERLAY_COLOR_LOCKING, payload :{ plotId, overlayColorLock}}); } /** * change position lock for zoom and scrolling * * @param {string} plotId is required * @param {boolean} positionLock true to set group lockRelated on */ export function dispatchPositionLocking(plotId,positionLock) { flux.process({ type: POSITION_LOCKING, payload :{ plotId, positionLock }}); } /** * Change the primary plot for a multi image fits display * Note - function parameter is a single object * @param {Object} p * @param {string} p.plotId * @param {number} p.primeIdx * @param {Function} p.dispatcher only for special dispatching uses such as remote */ export function dispatchChangePrimePlot({plotId, primeIdx, dispatcher= flux.process}) { dispatcher({ type: CHANGE_PRIME_PLOT , payload: { plotId, primeIdx }}); } /** * set if the base image is visible * @param {Object} p * @param {string} p.plotId * @param {boolean} p.visible - true if visible * @param {Function} p.dispatcher only for special dispatching uses such as remote */ export function dispatchChangeImageVisibility({plotId, visible, dispatcher= flux.process}) { dispatcher({ type: CHANGE_IMAGE_VISIBILITY, payload: { plotId, visible}}); } /** * Show image with new color table loaded * Note - function parameter is a single object * * * @param {Object} obj * @param {string} obj.plotId * @param {number} obj.cbarId must be in the range, 0 - 21, each number represents different colorbar * @param {string|ActionScope} [obj.actionScope] default to group * @param {Function} [obj.dispatcher] only for special dispatching uses such as remote * * * @public * @function dispatchColorChange * @memberof firefly.action */ export function dispatchColorChange({plotId, cbarId, actionScope=ActionScope.GROUP, dispatcher= flux.process} ) { dispatcher({ type: COLOR_CHANGE, payload: { plotId, cbarId, actionScope }}); } /** * Change the image stretch * Note - function parameter is a single object * @param {Object} obj - object literal with dispatcher parameters * @param {string} obj.plotId * @param {Array.<Object.<band:Band,rv:RangeValues,bandVisible:boolean>>} obj.stretchData * @param {ActionScope} [obj.actionScope] default to group * @param {Function} [obj.dispatcher] only for special dispatching uses such as remote * * @public * @function dispatchStretchChange * @memberof firefly.action * * @example * // Example of stretch 2 - 98 percent, log stretch * var rv= RangeValues.makeSimple(‘percent’, 2, 98, ‘log’); * const stretchData= [{ band : 'NO_BAND', rv : rv, bandVisible: true }]; * action.dispatchStretchChange({plotId:’myplot’, strechData:stretchData }); * @example * // Example of stretch -2 - 5 sigma, linear stretch * var rv= RangeValues.makeSimple(’sigma’, -2, 5, 'linear’); * const stretchData= [{ band : 'NO_BAND', rv : rv, bandVisible: true }]; * action.dispatchStretchChange({plotId:’myplot’, strechData:stretchData }); * */ export function dispatchStretchChange({plotId, stretchData, actionScope=ActionScope.GROUP, dispatcher= flux.process} ) { dispatcher({ type: STRETCH_CHANGE, payload: { plotId, stretchData, actionScope }}); } /** * Enable / Disable WCS Match * @param {Object} p * @param {string} p.plotId * @param {Enum|string|boolean} p.matchType one of 'Standard', 'Off', or you may pass false * @param {boolean} [p.lockMatch] * @param {Function} [p.dispatcher] */ export function dispatchWcsMatch({plotId, matchType, lockMatch= true, dispatcher= flux.process} ) { dispatcher({ type: WCS_MATCH, payload: { plotId, matchType, lockMatch}}); } /** * Rotate image, do it client side * * Note - function parameter is a single object * @param {Object} p * @param {string} p.plotId * @param {Enum} p.rotateType enum RotateType * @param {number} p.angle * @param {string|ActionScope} p.actionScope enum ActionScope * @param {Function} p.dispatcher only for special dispatching uses such as remote * * @public * @function dispatchRotate * @memberof firefly.action */ export function dispatchRotate({plotId, rotateType, angle=-1, actionScope=ActionScope.GROUP, dispatcher= flux.process} ) { dispatcher({ type: ROTATE, payload: { plotId, angle, rotateType, actionScope}}); } /** * @summary Flip * Note - function parameter is a single object * @param {Object} p * @param {string} p.plotId * @param {boolean} p.isY * @param {string|ActionScope} p.actionScope enum ActionScope * @param {Function} [p.dispatcher] only for special dispatching uses such as remote * * @public * @function dispatchFlip * @memberof firefly.action */ export function dispatchFlip({plotId, isY=true, actionScope=ActionScope.GROUP, dispatcher= flux.process}) { dispatcher({ type: FLIP, payload: { plotId, isY, actionScope}}); } /** * @summary Crop * Note - function parameter is a single object * @param {Object} p * @param {string} p.plotId * @param {Object} p.imagePt1 image point of corner 1 * @param {Object} p.imagePt2 image point of corner 2 * @param {boolean} p.cropMultiAll * @param {Function} [p.dispatcher] only for special dispatching uses such as remote * * @public * @function dispatchCrop * @memberof firefly.action */ export function dispatchCrop({plotId, imagePt1, imagePt2, cropMultiAll, dispatcher= flux.process}) { dispatcher({ type: CROP, payload: { plotId, imagePt1, imagePt2, cropMultiAll}}); } /** * @summary Move the scroll point on this plotId and possible others if it is grouped. * * Note - function parameter is a single object * @param {Object} p * @param {string} p.plotId * @param {Object} p.scrollPt a new point to scroll * @param {Object} p.disableBoundCheck * @param {Function} p.dispatcher only for special dispatching uses such as remote */ export function dispatchProcessScroll({plotId,scrollPt, disableBoundCheck=false, dispatcher= flux.process}) { dispatcher({type: PROCESS_SCROLL, payload: {plotId, scrollPt,disableBoundCheck} }); } /** * * @param {Object} p * @param {string} p.plotId * @param {WorldPt} p.centerProjPt * @param {Function} p.dispatcher only for special dispatching uses such as remote */ export function dispatchChangeCenterOfProjection({plotId,centerProjPt, dispatcher= flux.process}) { dispatcher({type: CHANGE_CENTER_OF_PROJECTION, payload: {plotId, centerProjPt} }); } /** * @summary recenter the images on the plot center or the ACTIVE_TARGET * * Note - function parameter is a single object * @param {Object} p * @param {string} p.plotId * @param {Point} [p.centerPt] Point to center on * @param {boolean} p.centerOnImage only used if centerPt is not defined. If true then the centering will be * the center of the image. If false, then the center point will be the * FIXED_TARGET attribute, if defined. Otherwise it will be the center of the image. * @param {Function} [p.dispatcher] only for special dispatching uses such as remote * * @public * @function dispatchRecenter * @memberof firefly.action */ export function dispatchRecenter({plotId, centerPt= undefined, centerOnImage=false, dispatcher= flux.process}) { dispatcher({type: RECENTER, payload: {plotId, centerPt, centerOnImage} }); } /** * @summary replot the image with the original plot parameters * * Note - function parameter is a single object * @param {Object} p this function takes a single parameter * @param {string} p.plotId * @param {Function} [p.dispatcher] only for special dispatching uses such as remote * @function dispatchRestoreDefaults */ export function dispatchRestoreDefaults({plotId, dispatcher= flux.process}) { dispatcher({type: RESTORE_DEFAULTS, payload: {plotId} }); } /** * @summary Plot an image. * @description Note - function parameter is a single object * @param {Object} p * @param {string} [p.plotId] is required unless defined in the WebPlotRequest * @param {WebPlotParams|WebPlotRequest|Array} p.wpRequest - plotting parameters, required or for 3 color pass an array of WebPlotParams or WebPlotRequest * @param {boolean} [p.threeColor] is a three color request, if true the wpRequest should be an array * @param {boolean} [p.useContextModifications=true] it true the request will be modified to use preferences, rotation, etc * should only be false when it is doing a 'restore to defaults' type plot * @param {Object} [p.attributes] meta data that is added the plot * @param {HipsImageConversionSettings} [p.hipsImageConversion= undefined] if defined, use these parameter to * convert between image and HiPS * @param {Object} [p.pvOptions] parameter specific to the plotView, only read the first time per plot id * @param {boolean} [p.setNewPlotAsActive= true] the new plot will be active * @param {boolean} [p.holdWcsMatch= false] if wcs match is on, then modify the request to hold the wcs match * @param {boolean} [p.enableRestore= true] if true the original request is saved for restore * @param {string} [p.viewerId] - viewer that this plot should be put into, only optional if * you have added the plot id manually to a viewer. * otherwise, you need to specify the viewer. * @param {string} [p.renderTreeId] - used only with multiple rendered tree, like slate in jupyter lab * @param {Function} [p.dispatcher] only for special dispatching uses such as remote * @public * @function dispatchPlotImage * @memberof firefly.action */ export function dispatchPlotImage({plotId,wpRequest, threeColor=isArray(wpRequest), useContextModifications= true, dispatcher= flux.process, attributes={}, pvOptions= {}, hipsImageConversion= undefined, setNewPlotAsActive= true, holdWcsMatch= true, enableRestore= true, viewerId, renderTreeId} ) { dispatcher({ type: PLOT_IMAGE, payload: {plotId,wpRequest, threeColor, pvOptions, hipsImageConversion, enableRestore, attributes, holdWcsMatch, setNewPlotAsActive, useContextModifications,viewerId, renderTreeId}}); } /** * @summary Plot a group of images. * Note - function parameter is a single object * @param {Object} p this function takes a single parameter * @param {WebPlotRequest[]} p.wpRequestAry * @param {string} p.viewerId * @param {PVCreateOptions} p.pvOptions PlotView init Options * @param {Object} [p.attributes] meta data that is added the plot * @param {boolean} [p.setNewPlotAsActive] the last completed plot will be active * @param {boolean} [p.holdWcsMatch= true] if wcs match is on, then modify the request to hold the wcs match * @param {boolean} [p.enableRestore= true] if true the original request is saved for restore * @param {string} [p.renderTreeId] - used only with multiple rendered tree, like slate in jupyter lab * @param {Function} [p.dispatcher] only for special dispatching uses such as remote */ export function dispatchPlotGroup({wpRequestAry, viewerId, pvOptions= {}, attributes={}, setNewPlotAsActive= true, holdWcsMatch= true, renderTreeId, enableRestore= true, dispatcher= flux.process}) { dispatcher( { type: PLOT_IMAGE, payload: { wpRequestAry, pvOptions, attributes, setNewPlotAsActive, enableRestore, holdWcsMatch, viewerId, renderTreeId} }); } /** * @summary Plot a HiPS display * @description Note - function parameter is a single object * @param {Object} p this function takes a single parameter * @param {string} p.plotId * @param {WebPlotParams|WebPlotRequest} p.wpRequest * @param {HipsImageConversionSettings} [p.hipsImageConversion= undefined] if defined, use these parameter to * @param {string} p.viewerId * @param {PVCreateOptions} p.pvOptions PlotView init Options * @param {Object} [p.attributes] meta data that is added the plot * @param {boolean} [p.setNewPlotAsActive] the last completed plot will be active * @param {boolean} [p.enableRestore= true] if true the original request is saved for restore * @param {string} [p.renderTreeId] - used only with multiple rendered tree, like slate in jupyter lab * @param {Function} [p.dispatcher] only for special dispatching uses such as remote * * @public * @function dispatchPlotHiPS * @memberof firefly.action */ export function dispatchPlotHiPS({plotId,wpRequest, viewerId, pvOptions= {}, attributes={}, hipsImageConversion= undefined, enableRestore= true, renderTreeId, setNewPlotAsActive= true, dispatcher= flux.process }) { dispatcher( { type: PLOT_HIPS, payload: {wpRequest, plotId, pvOptions, attributes, enableRestore, hipsImageConversion, setNewPlotAsActive, viewerId} }); } /** * @summary Plot a HiPS or a image depending on the FOV size * @description Note - function parameter is a single object * @param {Object} p this function takes a single parameter * @param {string} p.plotId * @param {WebPlotParams|WebPlotRequest} p.hipsRequest * @param {WebPlotParams|WebPlotRequest} p.imageRequest - must be a ServiceType request. * @param {WebPlotParams|WebPlotRequest} p.allSkyRequest - must be a allsky type request * @param {boolean} [p.plotAllSkyFirst= false] - if there is an all sky set up then plot that first * @param {number} [p.fovDegFallOver] - the size in degrees that the image will switch between hips and a image cutout * @param {number} [p.fovMaxFitsSize]- the max size the fits image service can support * @param {boolean} [p.autoConvertOnZoom]- convert between images and FITS on zoom * @param {string} [p.viewerId] * @param {string} [p.renderTreeId] - used only with multiple rendered tree, like slate in jupyter lab * @param {PVCreateOptions} [p.pvOptions] PlotView init Options * @param {Object} [p.attributes] meta data that is added the plot * @param {boolean} [p.setNewPlotAsActive] the last completed plot will be active * @param {Function} [p.dispatcher] only for special dispatching uses such as remote * * @public * @function dispatchPlotImageOrHiPS * @memberof firefly.action */ export function dispatchPlotImageOrHiPS({plotId,hipsRequest, imageRequest, allSkyRequest, viewerId, fovDegFallOver=.12, fovMaxFitsSize= .12, autoConvertOnZoom= false, pvOptions= {}, attributes={}, plotAllSkyFirst= false, setNewPlotAsActive= true, renderTreeId, dispatcher= flux.process }) { dispatcher( { type: PLOT_HIPS_OR_IMAGE, payload: {hipsRequest, imageRequest, allSkyRequest, plotId, fovDegFallOver, pvOptions, renderTreeId, fovMaxFitsSize, autoConvertOnZoom, attributes, setNewPlotAsActive, viewerId, plotAllSkyFirst} }); } /** * * @param {Object} p this function takes a single parameter * @param {string} p.plotId * @param {HipsImageConversionSettings} p.hipsImageConversionChanges changes to HipsImageConversionSettings, newOptions can contain any key in * HipsImageConversionSettings * @param {Function} [p.dispatcher] only for special dispatching uses such as remote */ export function dispatchChangeHipsImageConversion({plotId, hipsImageConversionChanges, dispatcher= flux.process}) { dispatcher( { type: CHANGE_HIPS_IMAGE_CONVERSION, payload: {plotId, hipsImageConversionChanges}}); } /** * * @summary change the hips repository or some other attribute * @param {Object} p this function takes a single parameter * @param {string} p.plotId * @param {string} [p.hipsUrlRoot] * @param {CoordinateSys} [p.coordSys] * @param {WorldPt} [p.centerProjPt] * @param {boolean} [p.applyToGroup], apply to the whole group it is locked * @param {Function} [p.dispatcher] only for special dispatching uses such as remote */ export function dispatchChangeHiPS({ plotId, hipsUrlRoot, coordSys, centerProjPt, cubeIdx, applyToGroup=true, dispatcher= flux.process }) { dispatcher( { type: CHANGE_HIPS, payload: {plotId, hipsUrlRoot, coordSys, cubeIdx, applyToGroup, centerProjPt} }); } /** * @summary Add a mask * @param {Object} p this function takes a single parameter * @param {string} p.plotId * @param {number} p.maskValue power of 2, e.g 4, 8, 32, 128, etc * @param {number} p.maskNumber 2, e.g 4, 8, 32, 128, etc * @param {string} p.imageOverlayId * @param {number} p.imageNumber hdu number of fits * @param {string} p.fileKey file on the server * @param {string} p.color - color is optional, if not specified, one is chosen * @param {string} p.title * @param {string} [p.relatedDataId] pass a related data id if one exist * @param {Function} [p.dispatcher] only for special dispatching uses such as remote * * @public * @function dispatchPlotMask * @memberof firefly.action */ export function dispatchPlotMask({plotId,imageOverlayId, maskValue, fileKey, imageNumber, maskNumber=-1, color, title, uiCanAugmentTitle, relatedDataId, lazyLoad, dispatcher= flux.process}) { dispatcher( { type: PLOT_MASK, payload: { plotId,imageOverlayId, fileKey, maskValue, uiCanAugmentTitle, imageNumber, maskNumber, color, title, relatedDataId, lazyLoad } }); } export function dispatchPlotMaskLazyLoad(payload ) { flux.process( { type: PLOT_MASK_LAZY_LOAD, payload}); } /** * * * @param {Object} p this function takes a single parameter * @param {string} p.plotId * @param {string} [p.imageOverlayId] the id of the overlay optional if deleteAll is true * @param {string} [p.deleteAll] delete all the overlay plot on the given plotId, defaults to false * @param {Function} [p.dispatcher] only for special dispatching uses such as remote */ export function dispatchDeleteOverlayPlot({plotId,imageOverlayId, deleteAll= false, dispatcher= flux.process}) { dispatcher( { type: DELETE_OVERLAY_PLOT, payload: { plotId,imageOverlayId, deleteAll} }); } /** * * @param {Object} p this function takes a single parameter * @param {string} p.plotId * @param {string} p.imageOverlayId * @param {Object} p.attributes any attribute in OverlayPlotView * @param {boolean} p.doReplot if false don't do a replot just change attributes * @param {Function} [p.dispatcher] only for special dispatching uses such as remote */ export function dispatchOverlayPlotChangeAttributes({plotId,imageOverlayId, attributes, doReplot=false, dispatcher= flux.process}) { dispatcher( { type: OVERLAY_PLOT_CHANGE_ATTRIBUTES, payload: { plotId,imageOverlayId, attributes, doReplot} }); } /** * @summary Zoom a image * Note - function parameter is a single object * @param {Object} p this function takes a single parameter * @param {string} p.plotId * @param {string|UserZoomTypes} p.userZoomType (one of ['UP','DOWN', 'FIT', 'FILL', 'ONE', 'LEVEL', 'WCS_MATCH_PREV') * @param {boolean} [p.maxCheck] * @param {boolean} [p.zoomLockingEnabled] * @param {boolean} [p.forceDelay] * @param {number} [p.level] the level to zoom to, used only userZoomType 'LEVEL' * @param {string|ActionScope} [p.actionScope] default to group * @param {Function} [p.dispatcher] only for special dispatching uses such as remote * * @public * @function dispatchZoom * @memberof firefly.action * * @example * // Example of zoom to level * action.dispatchZoom({plotId:’myplot’, userZoomType:’LEVEL’, level: .75 }); * * @example * // Example of zoom up * action.dispatchZoom({plotId:’myplot’, userZoomType:’UP’ }}; * @example * // Example of zoom to fit * action.dispatchZoom({plotId:’myplot’, userZoomType:’FIT’ }}; * @example * // Example of zoom to level, if you are connected to a widget that is changing the level fast, zlevel is the varible with the zoom level * action.dispatchZoom({plotId:’myplot’, userZoomType:’LEVEL’, level: zlevel, forceDelay: true }}; */ export function dispatchZoom({plotId, userZoomType, maxCheck= true, zoomLockingEnabled=false, forceDelay=false, level, actionScope=ActionScope.GROUP, dispatcher= flux.process} ) { dispatcher({ type: ZOOM_IMAGE, payload :{ plotId, userZoomType, actionScope, maxCheck, zoomLockingEnabled, forceDelay, level }}); } /** * @summary Delete a PlotView * Note - function parameter is a single object * @param {Object} p this function takes a single parameter * @param {string} p.plotId * @param {boolean} [p.holdWcsMatch= true] if wcs match is on, then modify the request to hold the wcs match * @param {Function} [p.dispatcher] only for special dispatching uses such as remote * @public * @function dispatchDeletePlotView * @memberof firefly.action */ export function dispatchDeletePlotView({plotId, holdWcsMatch= true, dispatcher= flux.process}) { dispatcher({ type: DELETE_PLOT_VIEW, payload: {plotId, holdWcsMatch} }); } /** * * @param {string} plotId * @param {boolean} zoomLockingEnabled * @param {UserZoomTypes|string} zoomLockingType should be 'FIT' or 'FILL' */ export function dispatchZoomLocking(plotId,zoomLockingEnabled, zoomLockingType) { flux.process({ type: ZOOM_LOCKING, payload :{ plotId, zoomLockingEnabled, zoomLockingType }}); } /** * Set the plotId of the active plot view * @param {string} plotId */ export function dispatchChangeActivePlotView(plotId) { if (!isActivePlotView(visRoot(),plotId)) { flux.process({ type: CHANGE_ACTIVE_PLOT_VIEW, payload: {plotId} }); } } /** * * @param {Object} p this function takes a single parameter * @param p.plotId * @param p.overlayColorScope * @param p.positionScope * @param p.attKey * @param p.attValue * @param p.toAllPlotsInPlotView if a multiImageFits apply to all the images */ export function dispatchAttributeChange({plotId,overlayColorScope=true,positionScope=false, attKey,attValue,toAllPlotsInPlotView=true}) { flux.process({ type: CHANGE_PLOT_ATTRIBUTE, payload: {plotId,attKey,attValue,overlayColorScope,positionScope, toAllPlotsInPlotView} }); } /** * * @param requester a string id of the requester * @param enabled true will add the request to the list, false will remove, when all requests are removed * Point selection will be turned off */ export function dispatchChangePointSelection(requester, enabled) { flux.process({ type: CHANGE_POINT_SELECTION, payload: {requester,enabled} }); } export function dispatchChangeTableAutoScroll(enabled) { flux.process({ type: CHANGE_TABLE_AUTO_SCROLL, payload: {enabled} }); } export function dispatchUseTableAutoScroll(useAutoScroll) { flux.process({ type: USE_TABLE_AUTO_SCROLL, payload: {useAutoScroll} }); } /** * * @param {ExpandType|boolean} expandedMode the mode to change to, it true the expand and match the last one, * if false colapse * */ export function dispatchChangeExpandedMode(expandedMode) { //todo: this code should be in a action creator const vr= visRoot(); if (!isImageExpanded(vr.expandedMode) && isImageExpanded(expandedMode)) { // if going from collapsed to expanded const plotId= vr.activePlotId; const pv= getPlotViewById(vr,plotId); if (pv) { const group= getPlotGroupById(vr,pv.plotGroupId); const plotIdAry= getOnePvOrGroup(vr.plotViewAry,plotId,group, true).map( (pv) => pv.plotId); dispatchReplaceViewerItems(EXPANDED_MODE_RESERVED,plotIdAry); } } flux.process({ type: CHANGE_EXPANDED_MODE, payload: {expandedMode} }); const enable= expandedMode!==ExpandType.COLLAPSE; visRoot().plotViewAry.forEach( (pv) => { const p= primePlot(pv); const zlEnabled= enable && p && p.plotState.getWebPlotRequest() && p.plotState.getWebPlotRequest().getZoomType()!==ZoomType.LEVEL; dispatchZoomLocking(pv.plotId,zlEnabled,pv.plotViewCtx.zoomLockingType); }); if (!enable) { visRoot().plotViewAry.forEach( (pv) => { const level= pv.plotViewCtx.lastCollapsedZoomLevel; if (level>0) { dispatchZoom({ plotId:pv.plotId, userZoomType:UserZoomTypes.LEVEL, level, maxCheck:false, actionScope:ActionScope.SINGLE}); } }); } } /** * Turn Auto play on * @param {boolean} autoPlayOn */ export function dispatchExpandedAutoPlay(autoPlayOn) { flux.process({ type: EXPANDED_AUTO_PLAY, payload: {autoPlayOn} }); } /** * * @param plotId * @param imageOverlayId * @param plotImageId * @param zoomFactor * @param clientTileAry */ export function dispatchAddProcessedTiles(plotId, imageOverlayId, plotImageId, zoomFactor, clientTileAry) { flux.process({ type: ADD_PROCESSED_TILES, payload: {plotId, imageOverlayId, plotImageId, zoomFactor, clientTileAry} }); } //======================================== Action Creators ============================= //======================================== Action Creators ============================= //======================================== Action Creators ============================= /** * @param {Action} rawAction * @returns {Function} */ const changePrimeActionCreator= (rawAction) => (dispatcher, getState) => changePrime(rawAction,dispatcher,getState); //======================================== Reducer ============================= //======================================== Reducer ============================= //======================================== Reducer ============================= const creationActions= convertToIdentityObj([ PLOT_IMAGE_START, PLOT_IMAGE_FAIL, PLOT_IMAGE, PLOT_HIPS, PLOT_HIPS_FAIL, CROP_START, CROP_FAIL, CROP, PLOT_MASK, PLOT_MASK_START, PLOT_MASK_FAIL, DELETE_OVERLAY_PLOT ]); const changeActions= convertToIdentityObj([ ZOOM_LOCKING, ZOOM_IMAGE_START, ZOOM_IMAGE_FAIL, ZOOM_IMAGE, UPDATE_VIEW_SIZE, PROCESS_SCROLL, CHANGE_PLOT_ATTRIBUTE, COLOR_CHANGE, COLOR_CHANGE_START, COLOR_CHANGE_FAIL, ROTATE, FLIP, STRETCH_CHANGE_START, STRETCH_CHANGE, STRETCH_CHANGE_FAIL, RECENTER, OVERLAY_COLOR_LOCKING, POSITION_LOCKING, PLOT_PROGRESS_UPDATE, OVERLAY_PLOT_CHANGE_ATTRIBUTES, CHANGE_PRIME_PLOT, CHANGE_CENTER_OF_PROJECTION, CHANGE_HIPS, ADD_PROCESSED_TILES, CHANGE_HIPS_IMAGE_CONVERSION, CHANGE_IMAGE_VISIBILITY ]); const adminActions= convertToIdentityObj([ API_TOOLS_VIEW, CHANGE_ACTIVE_PLOT_VIEW, CHANGE_EXPANDED_MODE, CHANGE_MOUSE_READOUT_MODE, EXPANDED_AUTO_PLAY, CHANGE_POINT_SELECTION, DELETE_PLOT_VIEW, WCS_MATCH, CHANGE_TABLE_AUTO_SCROLL, USE_TABLE_AUTO_SCROLL ]); /** * * @param {VisRoot} state * @param {Action} action * @returns {VisRoot} */ function reducer(state=initState(), action={}) { let retState= state; const {type}= action; if (!type || !type.startsWith(PLOTS_PREFIX)) return state; switch (type) { case REINIT_APP: return initState(); case creationActions[type]: retState= plotCreationReducer(state,action); validateState(retState,state,action); break; case changeActions[type]: retState= plotChangeReducer(state,action); validateState(retState,state,action); break; case adminActions[type]: retState= plotAdminReducer(state,action); validateState(retState,state,action); break; } return retState; } function validateState(state,originalState,action) { if (has(state,'activePlotId') && has(state,'plotViewAry') && has(state,'plotGroupAry')) { return state; } if (console.group) console.group('ImagePlotCntlr state invalid after: ' + action.type); console.log(action.type); console.log('originalState',originalState); console.log('new (bad) state',state); console.log('action', action); if (console.groupEnd) console.groupEnd(); } export const isImageExpanded = (expandedMode) => expandedMode===true || expandedMode===ExpandType.GRID || expandedMode===ExpandType.SINGLE;