/* * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt */ import {flux} from '../Firefly.js'; import Enum from 'enum'; import {getPlotViewIdList, getPlotViewById, getDrawLayerById, getConnectedPlotsIds, getAllPlotViewIdByOverlayLock} from './PlotViewUtil.js'; import ImagePlotCntlr, {visRoot} from './ImagePlotCntlr.js'; import DrawLayerReducer from './reducer/DrawLayerReducer.js'; import {flatten, uniqBy, without,union,isEmpty} from 'lodash'; import {clone, toBoolean} from '../util/WebUtil.js'; import {selectAreaEndActionCreator} from '../drawingLayers/SelectArea.js'; import {distanceToolEndActionCreator} from '../drawingLayers/DistanceTool.js'; import {markerToolStartActionCreator, markerToolMoveActionCreator, markerToolEndActionCreator, markerToolCreateLayerActionCreator} from '../drawingLayers/MarkerTool.js'; import {regionCreateLayerActionCreator, regionDeleteLayerActionCreator, regionUpdateEntryActionCreator} from './region/RegionTask.js'; import {footprintCreateLayerActionCreator, footprintStartActionCreator, footprintMoveActionCreator, footprintEndActionCreator } from '../drawingLayers/FootprintTool.js'; import {dispatchAddActionWatcher} from '../core/MasterSaga.js'; import {imageLineBasedfootprintActionCreator} from './task/LSSTFootprintTask.js'; import {REINIT_APP} from '../core/AppDataCntlr.js'; export const DRAWLAYER_PREFIX = 'DrawLayerCntlr'; export const SUBGROUP= 'subgroup'; /** {Enum} can be 'GROUP', 'SUBGROUP', 'SINGLE' */ export const GroupingScope= new Enum(['STANDARD', 'SUBGROUP', 'SINGLE']); const CREATE_DRAWING_LAYER= `${DRAWLAYER_PREFIX}.createDrawLayer`; const UPDATE_DRAWING_LAYER= `${DRAWLAYER_PREFIX}.updateDrawLayer`; const DESTROY_DRAWING_LAYER= `${DRAWLAYER_PREFIX}.destroyDrawLayer`; const CHANGE_VISIBILITY= `${DRAWLAYER_PREFIX}.changeVisibility`; const CHANGE_DRAWING_DEF= `${DRAWLAYER_PREFIX}.changeDrawingDef`; const ATTACH_LAYER_TO_PLOT= `${DRAWLAYER_PREFIX}.attachLayerToPlot`; const PRE_ATTACH_LAYER_TO_PLOT= `${DRAWLAYER_PREFIX}.attachLayerToPlot`; const DETACH_LAYER_FROM_PLOT= `${DRAWLAYER_PREFIX}.detachLayerFromPlot`; const MODIFY_CUSTOM_FIELD= `${DRAWLAYER_PREFIX}.modifyCustomField`; const FORCE_DRAW_LAYER_UPDATE= `${DRAWLAYER_PREFIX}.forceDrawLayerUpdate`; // _- select const SELECT_AREA_START= `${DRAWLAYER_PREFIX}.SelectArea.selectAreaStart`; const SELECT_AREA_MOVE= `${DRAWLAYER_PREFIX}.SelectArea.selectAreaMove`; const SELECT_AREA_END= `${DRAWLAYER_PREFIX}.SelectArea.selectAreaEnd`; const SELECT_MOUSE_LOC= `${DRAWLAYER_PREFIX}.SelectArea.selectMouseLoc`; const SELECT_POINT= `${DRAWLAYER_PREFIX}.SelectPoint.selectPoint`; // _- Distance tool const DT_START= `${DRAWLAYER_PREFIX}.DistanceTool.distanceToolStart`; const DT_MOVE= `${DRAWLAYER_PREFIX}.DistanceTool.distanceToolMove`; const DT_END= `${DRAWLAYER_PREFIX}.DistanceTool.distanceToolEnd`; // region const REGION_CREATE_LAYER = `${DRAWLAYER_PREFIX}.RegionPlot.createLayer`; const REGION_DELETE_LAYER = `${DRAWLAYER_PREFIX}.RegionPlot.deleteLayer`; const REGION_ADD_ENTRY = `${DRAWLAYER_PREFIX}.RegionPlot.addRegion`; const REGION_REMOVE_ENTRY = `${DRAWLAYER_PREFIX}.RegionPlot.removeRegion`; const REGION_SELECT = `${DRAWLAYER_PREFIX}.RegionPlot.selectRegion`; // marker and footprint const MARKER_START = `${DRAWLAYER_PREFIX}.MarkerTool.markerStart`; const MARKER_MOVE = `${DRAWLAYER_PREFIX}.MarkerTool.markerMove`; const MARKER_END = `${DRAWLAYER_PREFIX}.MarkerTool.markerEnd`; const MARKER_CREATE= `${DRAWLAYER_PREFIX}.MarkerTool.markerCreate`; const FOOTPRINT_CREATE = `${DRAWLAYER_PREFIX}.FootprintTool.footprintCreate`; const FOOTPRINT_START = `${DRAWLAYER_PREFIX}.FootprintTool.footprintStart`; const FOOTPRINT_END = `${DRAWLAYER_PREFIX}.FootprintTool.footprintEnd`; const FOOTPRINT_MOVE = `${DRAWLAYER_PREFIX}.FootprintTool.footprintMove`; const IMAGELINEBASEDFP_CREATE = `${DRAWLAYER_PREFIX}.ImageLineBasedFP.imagelineBasedFPCreate`; export const DRAWING_LAYER_KEY= 'drawLayers'; export function dlRoot() { return flux.getState()[DRAWING_LAYER_KEY]; } export const RegionSelectStyle = ['UprightBox', 'DottedOverlay', 'SolidOverlay', 'DottedReplace', 'SolidReplace']; export const defaultRegionSelectColor = '#DAA520'; // golden export const defaultRegionSelectStyle = RegionSelectStyle[0]; export const RegionSelColor = 'selectColor'; export const RegionSelStyle = 'selectStyle'; export function getRegionSelectStyle(style = defaultRegionSelectStyle) { const idx = RegionSelectStyle.findIndex((val) => { return val.toLowerCase() === style.toLowerCase(); }); return (idx < 0) ? defaultRegionSelectStyle : RegionSelectStyle[idx]; } /** * Return, from the store, the master array of all the drawing layers on all the plots * @returns {DrawLayer[]} * @memberof firefly.action * @function getDlAry */ export function getDlAry() { return flux.getState()[DRAWING_LAYER_KEY].drawLayerAry; } /** * Return the draw layer store * @returns {DrawLayerRoot} * @memberof firefly.action * @function getDlRoot */ export function getDlRoot() { return flux.getState()[DRAWING_LAYER_KEY]; } export function getDrawLayerCntlrDef(drawLayerFactory) { setTimeout( () => { dispatchAddActionWatcher({ actions:[CHANGE_VISIBILITY, CHANGE_DRAWING_DEF, ATTACH_LAYER_TO_PLOT, DETACH_LAYER_FROM_PLOT, FORCE_DRAW_LAYER_UPDATE, MODIFY_CUSTOM_FIELD, ImagePlotCntlr.ANY_REPLOT, ImagePlotCntlr.CHANGE_HIPS, ImagePlotCntlr.CHANGE_CENTER_OF_PROJECTION ], callback: asyncDrawDataWatcher, params: {drawLayerFactory} }); },10); return { reducers() {return {[DRAWING_LAYER_KEY]: makeReducer(drawLayerFactory)}; }, actionCreators() { return { [DETACH_LAYER_FROM_PLOT] : makeDetachLayerActionCreator(drawLayerFactory), [CHANGE_VISIBILITY] : makeChangeVisibilityActionCreator(drawLayerFactory), [SELECT_AREA_END] : selectAreaEndActionCreator, [DT_END] : distanceToolEndActionCreator, [MARKER_START] : markerToolStartActionCreator, [MARKER_MOVE] : markerToolMoveActionCreator, [MARKER_END] : markerToolEndActionCreator, [MARKER_CREATE] : markerToolCreateLayerActionCreator, [FOOTPRINT_CREATE] : footprintCreateLayerActionCreator, [FOOTPRINT_START] : footprintStartActionCreator, [FOOTPRINT_END] : footprintEndActionCreator, [FOOTPRINT_MOVE] : footprintMoveActionCreator, [REGION_CREATE_LAYER] : regionCreateLayerActionCreator, [REGION_DELETE_LAYER] : regionDeleteLayerActionCreator, [REGION_ADD_ENTRY] : regionUpdateEntryActionCreator, [REGION_REMOVE_ENTRY] : regionUpdateEntryActionCreator, [IMAGELINEBASEDFP_CREATE] : imageLineBasedfootprintActionCreator }; } }; } export default { getDrawLayerCntlrDef, CHANGE_VISIBILITY, ATTACH_LAYER_TO_PLOT, DETACH_LAYER_FROM_PLOT,CHANGE_DRAWING_DEF, CREATE_DRAWING_LAYER,DESTROY_DRAWING_LAYER, MODIFY_CUSTOM_FIELD, SELECT_AREA_START, SELECT_AREA_MOVE, SELECT_AREA_END, SELECT_MOUSE_LOC, SELECT_POINT, FORCE_DRAW_LAYER_UPDATE, DT_START, DT_MOVE, DT_END, REGION_CREATE_LAYER, REGION_DELETE_LAYER, REGION_ADD_ENTRY, REGION_REMOVE_ENTRY, REGION_SELECT, MARKER_START, MARKER_MOVE, MARKER_END, MARKER_CREATE, FOOTPRINT_CREATE, FOOTPRINT_START, FOOTPRINT_END, FOOTPRINT_MOVE, IMAGELINEBASEDFP_CREATE, makeReducer, dispatchCreateDrawLayer, dispatchCreateRegionLayer, dispatchDeleteRegionLayer, dispatchAddRegionEntry, dispatchRemoveRegionEntry, dispatchCreateImageLineBasedFootprintLayer }; /** * @summary create drawing layer * @param {string} drawLayerTypeId - id of drawing layer * @param {Object} params * @public * @memberof firefly.action * @function dispatchCreateDrawLayer */ export function dispatchCreateDrawLayer(drawLayerTypeId, params={}) { const drawLayer= flux.createDrawLayer(drawLayerTypeId,params); flux.process({type: CREATE_DRAWING_LAYER, payload: {drawLayer}} ); const plotIdAry= dlRoot().preAttachedTypes[drawLayerTypeId]; if (plotIdAry) { dispatchAttachLayerToPlot(drawLayerTypeId,plotIdAry); } return drawLayer; } /** * @summary change the visibility of one more drawing layers on a set of WebPlots. * This function can match the drawing layers using drawLayerId, drawLayerTypeId and further * match using the title of the draw layer. it can match hhe plots by plotId, then all the plots in the group, and then * can limit the plots in the group using subgroupId * @param {Object} p * @param {string|string[]} p.id - make the drawLayerId or drawLayerTypeId, this may be an array * @param {boolean} p.visible * @param {string} p.plotId - the plotId to change the visibility on, if used group is defined then visibility will be * change for all the plotIds in the PlotGroup * @param {boolean} [p.useGroup] - If true, get all the plotViews in the group of the plotId, if false use only the one * @param {string} [p.subGroupId] - if defined the list of PlotViews affected will be filtered by the subGroupId. In other words * it will only change the visibility on PlotView that have a matching subGroupId. * @param {boolean} [p.matchTitle] - matches any draw layers that have the same title as the one specified by the id * @public * @memberof firefly.action * @function dispatchChangeVisibility */ export function dispatchChangeVisibility({id,visible, plotId, useGroup= true, subGroupId, matchTitle= false}) { let plotIdAry= useGroup ? getPlotViewIdList(visRoot(), plotId) : [plotId]; if (subGroupId) { const vr= visRoot(); plotIdAry= plotIdAry.filter( (plotId) => { const pv= getPlotViewById(vr,plotId); return (pv && subGroupId===pv.drawingSubGroupId); }); } if (plotIdAry.length) { getDrawLayerIdAry(dlRoot(),id, matchTitle) .forEach( (drawLayerId) => { flux.process({type: CHANGE_VISIBILITY, payload: {drawLayerId, visible, plotIdAry} }); }); } } /** * @summary change the drawing definition of the drawing layer * @param {string|string[]} id make the drawLayerId or drawLayerTypeId, this may be an array * @param drawingDef * @param plotId * @param {boolean} [matchTitle] - matches any draw layers that have the same title as the one specified by the id * @public * @memberof firefly.action * @function dispatchChangeDrawingDef */ export function dispatchChangeDrawingDef(id,drawingDef, plotId, matchTitle= false) { const plotIdAry= getPlotViewIdList(visRoot(), plotId); getDrawLayerIdAry(dlRoot(),id, matchTitle) .forEach( (drawLayerId) => { flux.process({type: CHANGE_DRAWING_DEF, payload: {drawLayerId, drawingDef, plotIdAry}}); }); } /** * @summary create custom changes to the drawing layer * @param {string|string[]} id make the drawLayerId or drawLayerTypeId, this may be an array * @param {Object} changes any object of changes * @param {string} [plotId] a plotId * @public * @memberof firefly.action * @function dispatchModifyCustomField */ export function dispatchModifyCustomField(id,changes, plotId) { const plotIdAry= getPlotViewIdList(visRoot(), plotId); getDrawLayerIdAry(dlRoot(),id) .forEach( (drawLayerId) => { flux.process({type: MODIFY_CUSTOM_FIELD, payload: {drawLayerId, changes, plotIdAry}}); }); } /** * * @param {DrawLayer} drawLayer */ export function dispatchUpdateDrawLayer(drawLayer) { flux.process({type: UPDATE_DRAWING_LAYER, payload: {drawLayer}}); } /** * @summary force to update the drawing layer * @param id * @param plotId * @public * @memberof firefly.action * @function dispatchForceDrawLayerUpdate */ export function dispatchForceDrawLayerUpdate(id,plotId) { const plotIdAry= getPlotViewIdList(visRoot(), plotId); getDrawLayerIdAry(dlRoot(),id) .forEach( (drawLayerId) => { flux.process({type: FORCE_DRAW_LAYER_UPDATE, payload: {drawLayerId, plotIdAry}}); }); } /** * @summary destroy the drawing layer * @param {string} id make the drawLayerId or drawLayerTypeId * @public * @memberof firefly.action * @function dispatchDestroyDrawLayer */ export function dispatchDestroyDrawLayer(id) { const drawLayerId= getDrawLayerId(dlRoot(),id); if (drawLayerId) { flux.process({type: DESTROY_DRAWING_LAYER, payload: {drawLayerId} }); } } /** * @summary attach drawing layer to plot * @param {string|string[]} id make the drawLayerId or drawLayerTypeId, this may be an array * @param {string|string[]} plotId to attach this may by a string or an array of strings * @param {boolean} attachAllPlot * @param {boolean|string} visible - Can have three values: true: layer is attach visible, false: attach not-visible, * value (string) 'inherit' layer is visible * @param {boolean} plotTypeMustMatch * @memberof firefly.action * @public * @function dispatchAttachLayerToPlot */ export function dispatchAttachLayerToPlot(id,plotId, attachAllPlot=false, visible= true, plotTypeMustMatch=false) { let plotIdAry; let layerVisible; if (visible==='inherit') { layerVisible= getDrawLayerIdAry(dlRoot(),id, true).some( (drawLayerId) => getDrawLayerById(dlRoot(),drawLayerId).visiblePlotIdAry.length); } else { layerVisible= toBoolean(visible); } if (Array.isArray(plotId)) { plotIdAry= plotId; } else { plotIdAry = attachAllPlot ? getAllPlotViewIdByOverlayLock(visRoot(), plotId, false, plotTypeMustMatch) : [plotId]; } getDrawLayerIdAry(dlRoot(),id) .forEach( (drawLayerId) => { flux.process({type: ATTACH_LAYER_TO_PLOT, payload: {drawLayerId, plotIdAry, visible:layerVisible} }); }); } /** * @summary Detach drawing layer from the plot * @param {string|string[]} id make the drawLayerId or drawLayerTypeId, this may be an array * @param {string|string[]} plotId to attach this may by a string or an array of string * @param detachAllPlot * @param destroyWhenAllDetached if all plots are detached then destroy this plot * @public * @memberof firefly.action * @function dispatchDetachLayerFromPlot */ export function dispatchDetachLayerFromPlot(id,plotId, detachAllPlot=false, destroyWhenAllDetached=false) { let plotIdAry; if (Array.isArray(plotId)) { plotIdAry= plotId; } else { plotIdAry= detachAllPlot ? getAllPlotViewIdByOverlayLock(visRoot(), plotId) : [plotId]; } getDrawLayerIdAry(dlRoot(),id) .forEach( (drawLayerId) => { flux.process({type: DETACH_LAYER_FROM_PLOT, payload: {drawLayerId,plotIdAry, destroyWhenAllDetached} }); }); } /** * check and create selectMode with valid property and value. * @param selectMode * @returns {{selectStyle, selectColor, lineWidth}} */ function validateSelectMode(selectMode) { const {selectStyle = defaultRegionSelectStyle, selectColor = defaultRegionSelectColor, lineWidth = 0 } = selectMode; const regSelectStyle = getRegionSelectStyle(selectStyle); return {selectStyle:regSelectStyle , selectColor, lineWidth}; } /** * @global * @public * @typedef {Object} RegionSelectMode * @summary shallow object with the rendering parameters for selected region * @prop {string} [selectStyle='UprightBox'] - rendering style for the selected region including 'UprightBox', 'DottedOverlay', * 'SolidOverlay', 'DottedReplace', and 'SolidReplace' * @prop {string} [selectColor='#DAA520'] - rendering color for the selected region, CSS color values, such as '#DAA520' 'red'. * are valid for rendering. * @prop {int} [lineWidth=0] - rendering line width for the selected region. 0 or less means the line width * is the same as that of the selected region */ /** * @summary Create drawing layer based on region file or region description * @param {string} drawLayerId - id of the drawing layer to be created, required * @param {string} layerTitle - if it is empty, it will be created internally * @param {string} fileOnServer - region file name on server * @param {string[]|string} regionAry - array or string of region description * @param {string[]|string} plotId - array or string of plot id. If plotId is empty, all plots of the active group are applied * @param {RegionSelectMode} selectMode - rendering features for the selected region * @param {Function} dispatcher * @public * @function dispatchCreateRegionLayer * @memberof firefly.action */ export function dispatchCreateRegionLayer(drawLayerId, layerTitle, fileOnServer='', regionAry=[], plotId='', selectMode = {}, dispatcher = flux.process) { dispatcher({type: REGION_CREATE_LAYER, payload: {drawLayerId, fileOnServer, plotId, layerTitle, regionAry, selectMode: validateSelectMode(selectMode)}}); } /** * @summary Delete the region drawing layer * @param {string} drawLayerId - id of the drawing layer to be deleted, required * @param {string[]|string} plotId - array or string of plot id. If plotId is empty, all plots of the active group are applied * @param {Function} dispatcher * @public * @function dispatchDeleteRegionLayer * @memberof firefly.action */ export function dispatchDeleteRegionLayer(drawLayerId, plotId, dispatcher = flux.process) { dispatcher({type: REGION_DELETE_LAYER, payload: {drawLayerId, plotId}}); } /** * @summary Add regions to drawing layer * @param {string} drawLayerId - id of the drawing layer where the region(s) are added to * if the layer doesn't exist, a new drawing layer is created by either using the specified drawLayerId or * creating a new id based on the setting of 'layerTitle' in case drawLayerId is undefined * @param {string[]|string} regionChanges - array or string of region description * @param {string[]|string} plotId - array or string of plot id. If plotId is empty, all plots of the active group are applied * @param {string} layerTitle - will replace the original title if the drawing layer exists and layerTitle is non-empty * @param {RegionSelectMode} selectMode - rendering features for the selected region * @param {Function} dispatcher * @public * @function dispatchAddRegionEntry * @memberof firefly.action */ export function dispatchAddRegionEntry(drawLayerId, regionChanges, plotId=[], layerTitle='', selectMode = {}, dispatcher = flux.process) { dispatcher({type: REGION_ADD_ENTRY, payload: {drawLayerId, regionChanges, plotId, layerTitle, selectMode: validateSelectMode(selectMode)}}); } /** * @summary remove region(s) from the drawing layer * @param {string} drawLayerId - id of the drawing layer where the region(s) are removed from, required * @param {string[]|string} regionChanges - array or string of region description * @param {Function} dispatcher * @public * @function dispatchRemoveRegionEntry * @memberof firefly.action */ export function dispatchRemoveRegionEntry(drawLayerId, regionChanges, dispatcher = flux.process) { dispatcher({type: REGION_REMOVE_ENTRY, payload: {drawLayerId, regionChanges}}); } /** * @summary select region from a drawing layer containing regions * @param {string} drawLayerId - id of drawing layer where the region is selected from, required * @param {string[]|string|Object} selectedRegion - array or string of region description or region object (drawObj) * currently only single region is allowed to be selected if the array contains the description of multiple regions. * If 'null' or empty array is passed, the function works as de-select the region. * @param {Function} dispatcher * @public * @function dispatchSelectRegion * @memberof firefly.action * @see {@link firefly.util.image.getSelectedRegion} to get the string describing the selected region */ export function dispatchSelectRegion(drawLayerId, selectedRegion, dispatcher = flux.process) { dispatcher({type: REGION_SELECT, payload: {drawLayerId, selectedRegion}}); } /** * @summary create drawing layer with marker * @param {string} markerId - id of the drawing layer * @param {string} layerTitle - title of the drawing layer * @param {string[]|string} plotId - array or string of plot id. If plotId is empty, all plots of the active group are applied * @param {bool} attachPlotGroup - attach all plots of the same plot group * @param dispatcher * @public * @function dispatchCreateMarkerLayer * @memberof firefly.action */ export function dispatchCreateMarkerLayer(markerId, layerTitle, plotId = [], attachPlotGroup=true, dispatcher = flux.process) { dispatcher({type: MARKER_CREATE, payload: {plotId, markerId, layerTitle, attachPlotGroup}}); } /** * Footprint Info. The data object containing footprint info. * @typedef {object} footprintInfo * @prop {string} footprint - name of footprint project, such as 'HST', 'WFIRST', etc. or footprint file at the server * @prop {string} instrument - name of instrument for the footprint * @prop {string} relocateBy - name of instrument for the footprint from the server, method of relocation for the uploaded footprint * @prop {string} fromFile - filename, not including the extension, of the uploaded file * @prop {string[]} fromRegionAry - array or string of region description * * @public */ /** * @summary create drawing layer with footprint * @param {string} footprintId - id of the drawing layer * @param {string} layerTitle - title of the drawing layer * @param {footprintInfo} footprintData footprint information for footprint layer, * relocateBy: 'origin' means relocating footprint origin to the target location * 'center' means relocating footprint center to the target location * @param {string[]|string} plotId - array or string of plot id. If plotId is empty, all plots of the active group are applied * @param {bool} attachPlotGroup - attach all plots of the same plot group * @param dispatcher * @public * @function dispatchCreateFootprintLayer * @memberof firefly.action */ export function dispatchCreateFootprintLayer(footprintId, layerTitle, {footprint=null, instrument=null, relocateBy='origin', fromFile=null, fromRegionAry=null}, plotId = [], attachPlotGroup=true, dispatcher = flux.process) { dispatcher({type: FOOTPRINT_CREATE, payload: {plotId, footprintId, layerTitle, footprint, instrument, relocateBy, attachPlotGroup, fromFile, fromRegionAry}}); } export function dispatchCreateImageLineBasedFootprintLayer(drawLayerId, title, fpData, plotId = [], footprintFile, footprintImageFile, tbl_index, attachPlotGroup=true, dispatcher = flux.process) { dispatcher({ type: IMAGELINEBASEDFP_CREATE, payload: {plotId, drawLayerId, title, footprintData: fpData, footprintFile, footprintImageFile, tbl_index, attachPlotGroup} }); } function getDrawLayerId(dlRoot,id) { let drawLayer= dlRoot.drawLayerAry.find( (dl) => id===dl.drawLayerId); if (!drawLayer) { drawLayer= dlRoot.drawLayerAry.find( (dl) => id===dl.drawLayerTypeId); } return drawLayer ? drawLayer.drawLayerId : null; } /** * * @param dlRoot * @param id - drawLayerId or drawLayerTypeId * @param matchTitles * @return {Array.<String>} the list of drawLayerIds */ function getDrawLayerIdAry(dlRoot,id, matchTitles= false) { const idAry= Array.isArray(id) ? id: [id]; const dlAry= dlRoot.drawLayerAry .filter( (dl) => idAry .filter( (id) => id===dl.drawLayerId || id===dl.drawLayerTypeId) .length>0); let retDlAry= dlAry; if (matchTitles) { //look for any other DrawLayers with titles that match the already found list of layers const matchTitleAry= uniqBy(flatten(dlAry .map( (dl) => dlRoot.drawLayerAry .filter( (nextDl) => dl.title===nextDl.title && nextDl!==dl) )), 'drawLayerId'); retDlAry= [...dlAry, ...matchTitleAry]; } return retDlAry.map( (dl) => dl.drawLayerId); } //============================================= //============================================= //============================================= function makeDetachLayerActionCreator(factory) { return (action) => { return (dispatcher) => { const {drawLayerId}= action.payload; const drawLayer= getDrawLayerById(getDlAry(), drawLayerId); factory.onDetachAction(drawLayer,action); dispatcher(action); }; }; } function makeChangeVisibilityActionCreator(factory) { return (action) => { return (dispatcher) => { const {drawLayerId}= action.payload; const drawLayer= getDrawLayerById(getDlAry(), drawLayerId); dispatcher(action); factory.onVisibilityChange(drawLayer,action); }; }; } function asyncDrawDataWatcher(action, cancelSelf, params) { const {drawLayerId, plotId}= action.payload; const drawLayerAry= getDlAry(); const drawLayer= getDrawLayerById(drawLayerAry, drawLayerId); const {drawLayerFactory}= params; if (drawLayer) { drawLayerFactory.asyncComputeDrawData(drawLayer,action); } else if (plotId) { drawLayerAry .filter( (dl) => dl.visiblePlotIdAry .find( (testPlotId) => testPlotId===plotId)) .forEach( (dl) => drawLayerFactory.asyncComputeDrawData(dl,action)); } } //============================================= //============================================= //============================================= /** * * @param factory * @ignore */ function makeReducer(factory) { const dlReducer= DrawLayerReducer.makeReducer(factory); return (state=initState(), action={}) => { if (action.type===REINIT_APP) return initState(); if (!action.payload || !action.type) return state; if (!state.allowedActions.includes(action.type)) return state; let retState = state; switch (action.type) { case CHANGE_VISIBILITY: case CHANGE_DRAWING_DEF: case FORCE_DRAW_LAYER_UPDATE: case MODIFY_CUSTOM_FIELD: retState = deferToLayerReducer(state, action, dlReducer); break; case UPDATE_DRAWING_LAYER: retState = doUpdateDrawLayer(state, action); break; case CREATE_DRAWING_LAYER: retState = createDrawLayer(state, action); break; case DESTROY_DRAWING_LAYER: retState = destroyDrawLayer(state, action); break; case ATTACH_LAYER_TO_PLOT: retState = deferToLayerReducer(state, action, dlReducer); break; case DETACH_LAYER_FROM_PLOT: retState = deferToLayerReducer(state, action, dlReducer); const {payload}= action; if (payload.destroyWhenAllDetached && isEmpty(getConnectedPlotsIds(retState,payload.drawLayerId))) { retState = destroyDrawLayer(retState, action); } break; case PRE_ATTACH_LAYER_TO_PLOT: retState = preattachLayerToPlot(state,action); break; case ImagePlotCntlr.DELETE_PLOT_VIEW: retState = deletePlotView(state, action, dlReducer); break; case ImagePlotCntlr.CHANGE_HIPS: case ImagePlotCntlr.ANY_REPLOT: case ImagePlotCntlr.CHANGE_CENTER_OF_PROJECTION: retState = determineAndCallLayerReducer(state, action, dlReducer, true); break; default: retState = determineAndCallLayerReducer(state, action, dlReducer); break; } return retState; }; } /** * Create a drawing layer * @param state * @param {{type:string,payload:object}} action * @returns {Object} the new state; * @ignore */ function createDrawLayer(state,action) { const {drawLayer}= action.payload; const allowedActions= union(state.allowedActions, drawLayer.actionTypeAry); return Object.assign({}, state, {allowedActions, drawLayerAry: [...state.drawLayerAry, drawLayer] }); } function doUpdateDrawLayer(state,action) { const {drawLayer}= action.payload; const drawLayerAry= state.drawLayerAry.map( (dl) => dl.drawLayerId===drawLayer.drawLayerId ? drawLayer : dl); return Object.assign({}, state, {drawLayerAry} ); } /** * Destroy the drawing layer * @param state * @param {{type:string,payload:object}} action * @returns {Object} the new state; * @ignore */ function destroyDrawLayer(state,action) { const {drawLayerId}= action.payload; return Object.assign({}, state, {drawLayerAry: state.drawLayerAry.filter( (c) => c.drawLayerId!==drawLayerId) }); } /** * Call the reducer for the drawing layer defined by the action * @param state * @param {{type:string,payload:object}} action * @param dlReducer drawinglayer subreducer{string|string[]} * @returns {Object} the new state; * @ignore */ function deferToLayerReducer(state,action,dlReducer) { const {drawLayerId}= action.payload; const drawLayer= state.drawLayerAry.find( (dl) => drawLayerId===dl.drawLayerId); if (drawLayer) { const newDl= dlReducer(drawLayer,action); if (newDl!==drawLayer) { return Object.assign({}, state, {drawLayerAry: state.drawLayerAry.map( (dl) => dl.drawLayerId===drawLayerId ? newDl : dl) }); } } return state; } /** * Call all the drawing layers that are interested in the action. Since this function will be called often it does * a lot of checking for change. * If nothing has changed it returns the original state. * @param state * @param {{type:string,payload:object}} action * @param dlReducer drawinglayer subreducer * @param force * @returns {Object} the new state; * @ignore */ function determineAndCallLayerReducer(state,action,dlReducer,force) { const newAry= state.drawLayerAry.map( (dl) => { if (force || (dl.actionTypeAry && dl.actionTypeAry.includes(action.type))) { const newdl= dlReducer(dl,action); return (newdl===dl) ? dl : newdl; // check to see if there was a change } else { return dl; } } ); if (without(state.drawLayerAry,...newAry).length) { // if there are changes return Object.assign({},state, {drawLayerAry:newAry}); } else { return state; } } function preattachLayerToPlot(state,action) { const {drawLayerTypeId,plotIdAry}= action.payload; const currentAry= state.preAttachedTypes[drawLayerTypeId] || []; const preAttachedTypes= clone( state.preAttachedTypes, {[drawLayerTypeId]: union(currentAry,plotIdAry)}); return clone(state, {preAttachedTypes}); } function deletePlotView(state,action, dlReducer) { const {plotId} = action.payload; const drawLayerAry= state.drawLayerAry .map( (dl) => dlReducer(dl, {type:DETACH_LAYER_FROM_PLOT, payload:{plotIdAry:[plotId]}})) .filter( (dl) => !(dl.destroyWhenAllDetached && isEmpty(dl.plotIdAry))); return clone(state, {drawLayerAry}); } /** * * @return {DrawLayerRoot} */ const initState= function() { /** * @global * @public * @typedef {Object} DrawLayerRoot * * @summary The state of the Drawing layers store. * @prop {DrawLayer[]} drawLayerAry the array of all the drawing layers * @prop {string[]} allowedActions the actions the go to the drawing layers by default */ return { allowedActions: [ CREATE_DRAWING_LAYER, DESTROY_DRAWING_LAYER, CHANGE_VISIBILITY, ATTACH_LAYER_TO_PLOT, DETACH_LAYER_FROM_PLOT, MODIFY_CUSTOM_FIELD, CHANGE_DRAWING_DEF,FORCE_DRAW_LAYER_UPDATE, ImagePlotCntlr.ANY_REPLOT, ImagePlotCntlr.DELETE_PLOT_VIEW, ImagePlotCntlr.CHANGE_CENTER_OF_PROJECTION, ImagePlotCntlr.CHANGE_HIPS, UPDATE_DRAWING_LAYER ], drawLayerAry : [], preAttachedTypes : {} // {futureDrawLayerTypeId : [string] } // i.e. an object: keys are futureDrawLayerTypeId, values: array of plot id }; };