/*
* 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
};
};