diff --git a/buildScript/depends.gincl b/buildScript/depends.gincl index 7203e76183..bdf3641f54 100644 --- a/buildScript/depends.gincl +++ b/buildScript/depends.gincl @@ -104,7 +104,9 @@ jar.destinationDir = file ("$rootDir/jars/build") task buildClient (dependsOn: loadConfig) { - outputs.upToDateWhen { false } + outputs.dir "${buildDir}/gwt/${project['app-name']}" + inputs.dir "${projectDir}/js" + inputs.dir "${fireflyPath}/src/firefly/js" doLast { def res = project.ext.NPM 'run', 'build' diff --git a/src/firefly/java/edu/caltech/ipac/firefly/data/TableServerRequest.java b/src/firefly/java/edu/caltech/ipac/firefly/data/TableServerRequest.java index e36ed90bdd..994c86a1bf 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/data/TableServerRequest.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/data/TableServerRequest.java @@ -17,7 +17,7 @@ public class TableServerRequest extends ServerRequest implements Serializable, D public static final String DECIMATE_INFO = "decimate"; public static final String TBL_FILE_PATH = "tblFilePath"; public static final String TBL_ID = "tbl_id"; - public static final String TITLE = "tbl_table"; + public static final String TITLE = "title"; public static final String FILTERS = "filters"; public static final String SORT_INFO = "sortInfo"; public static final String PAGE_SIZE = "pageSize"; diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/query/IpacTablePartProcessor.java b/src/firefly/java/edu/caltech/ipac/firefly/server/query/IpacTablePartProcessor.java index 5a7d55fadc..029c7c6542 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/query/IpacTablePartProcessor.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/query/IpacTablePartProcessor.java @@ -199,8 +199,7 @@ public DataGroupPart getData(ServerRequest sr) throws DataAccessException { try { dgFile = postProcessData(dgFile, request); page = IpacTableParser.getData(dgFile, request.getStartIndex(), request.getPageSize()); - page.getTableDef().ensureStatus(); // make sure there's a status line so - page.getTableDef().setAttribute(TableServerRequest.TBL_FILE_PATH, ServerContext.replaceWithPrefix(dgFile)); // set table's meta tblFilePath to the file it came from. + ensureTableMeta(page, request, dgFile); // inspect/edit meta info needed by client. } catch (Exception e) { LOGGER.error(e, "Fail to parse ipac table file: " + dgFile); throw e; @@ -216,15 +215,14 @@ public DataGroupPart getData(ServerRequest sr) throws DataAccessException { LOGGER.error(e, "Error while processing request:" + StringUtils.truncate(sr, 512)); throw new DataAccessException("Unexpected error", e); } -// finally { -// if (!doCache()) { -// do not delete file even if it's not to be cached. download still relies on it. -// if (dgFile != null) { -// dgFile.delete(); -// } -// } -// } + } + private void ensureTableMeta(DataGroupPart page, TableServerRequest request, File dgFile) { + page.getTableDef().ensureStatus(); // make sure there's a status line so + page.getTableDef().setAttribute(TableServerRequest.TBL_FILE_PATH, ServerContext.replaceWithPrefix(dgFile)); // set table's meta tblFilePath to the file it came from. + if (!StringUtils.isEmpty(request.getTblTitle())) { + page.getData().setTitle(request.getTblTitle()); // set the datagroup's title to the request title. + } } protected File postProcessData(File dgFile, TableServerRequest request) throws Exception { @@ -648,3 +646,4 @@ protected static File convertToIpacTable(File tblFile, TableServerRequest reques } +// some random comments \ No newline at end of file diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/util/ipactable/IpacTableParser.java b/src/firefly/java/edu/caltech/ipac/firefly/server/util/ipactable/IpacTableParser.java index dacbaeb33e..6193e4b283 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/util/ipactable/IpacTableParser.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/util/ipactable/IpacTableParser.java @@ -87,8 +87,6 @@ public static DataGroupPart getData(File inf, int start, int rows) throws IOExce DataGroup dg = new DataGroup(null, tableDef.getCols()); dg.setRowIdxOffset(start); - dg.setAttributes(tableDef.getAllAttributes()); - RandomAccessFile reader = new RandomAccessFile(inf, "r"); long skip = ((long)start * (long)tableDef.getLineWidth()) + (long)tableDef.getRowStartOffset(); int count = 0; @@ -108,6 +106,13 @@ public static DataGroupPart getData(File inf, int start, int rows) throws IOExce reader.close(); } + // sync attributes in datagroup with tabledef. + Map attribs = dg.getAttributes(); + if (attribs.size() > 0) { + tableDef.addAttributes(attribs.values().toArray(new DataGroup.Attribute[attribs.size()])); + } + dg.setAttributes(tableDef.getAllAttributes()); + long totalRow = tableDef.getLineWidth() == 0 ? 0 : (inf.length() - tableDef.getRowStartOffset())/tableDef.getLineWidth(); return new DataGroupPart(tableDef, dg, start, (int) totalRow); diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/util/ipactable/JsonTableUtil.java b/src/firefly/java/edu/caltech/ipac/firefly/server/util/ipactable/JsonTableUtil.java index 92de013697..3997f41176 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/util/ipactable/JsonTableUtil.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/util/ipactable/JsonTableUtil.java @@ -182,7 +182,7 @@ private static List toJsonTableColumn(DataGroup dataGroup, TableDef } String sortable = getColAttr(tableDef, SORTABLE_TAG, cname); if (!StringUtils.isEmpty(sortable)) { - c.put("sortable", sortable); + c.put("sortable", Boolean.parseBoolean(sortable)); } String units = getColAttr(tableDef, UNIT_TAG, cname); if (!StringUtils.isEmpty(units)) { diff --git a/src/firefly/java/edu/caltech/ipac/util/DataGroup.java b/src/firefly/java/edu/caltech/ipac/util/DataGroup.java index 9a6fe4d6e2..dce7569e9c 100644 --- a/src/firefly/java/edu/caltech/ipac/util/DataGroup.java +++ b/src/firefly/java/edu/caltech/ipac/util/DataGroup.java @@ -183,6 +183,7 @@ public void remove(DataObject s) { */ public void addAttribute(String key, String value) { _attributes.add(new Attribute(key, value)); + _cachedAttributesMap = null; } /** diff --git a/src/firefly/js/charts/HistogramCntlr.js b/src/firefly/js/charts/HistogramCntlr.js index c72623832c..59a4743da8 100644 --- a/src/firefly/js/charts/HistogramCntlr.js +++ b/src/firefly/js/charts/HistogramCntlr.js @@ -6,7 +6,7 @@ import {flux} from '../Firefly.js'; import {get, has, omit} from 'lodash'; import {updateSet, updateMerge} from '../util/WebUtil.js'; -import {doFetchTable, getTblById, isFullyLoaded, makeTblRequest} from '../tables/TableUtil.js'; +import {doFetchTable, getTblById, isFullyLoaded, makeTblRequest, cloneRequest} from '../tables/TableUtil.js'; import {HISTOGRAM, getChartSpace} from './ChartUtil.js'; import * as TablesCntlr from '../tables/TablesCntlr.js'; import {DELETE} from './ChartsCntlr.js'; @@ -188,8 +188,7 @@ function fetchColData(dispatch, tblId, histogramParams, chartId) { const activeTableServerRequest = activeTableModel['request']; const tblSource = get(activeTableModel, 'tableMeta.tblFilePath'); - const sreq = Object.assign({}, omit(activeTableServerRequest, ['tbl_id', 'META_INFO']), - {'startIdx' : 0, 'pageSize' : 1000000}); + const sreq = cloneRequest(activeTableServerRequest, {'startIdx' : 0, 'pageSize' : 1000000}); const req = makeTblRequest('HistogramProcessor'); req.searchRequest = JSON.stringify(sreq); diff --git a/src/firefly/js/charts/TableStatsCntlr.js b/src/firefly/js/charts/TableStatsCntlr.js index a2dcdb4834..1eba321c09 100644 --- a/src/firefly/js/charts/TableStatsCntlr.js +++ b/src/firefly/js/charts/TableStatsCntlr.js @@ -110,8 +110,7 @@ function fetchTblStats(dispatch, activeTableServerRequest) { const {tbl_id} = activeTableServerRequest; // searchRequest - const sreq = Object.assign({}, omit(activeTableServerRequest, ['tbl_id', 'META_INFO']), - {'startIdx': 0, 'pageSize': 1000000}); + const sreq = TableUtil.cloneRequest(activeTableServerRequest, {'startIdx': 0, 'pageSize': 1000000}); const req = TableUtil.makeTblRequest('StatisticsProcessor', `Statistics for ${tbl_id}`, { searchRequest: JSON.stringify(sreq) }, diff --git a/src/firefly/js/charts/XYPlotCntlr.js b/src/firefly/js/charts/XYPlotCntlr.js index 1a04c35da7..bec9bf024b 100644 --- a/src/firefly/js/charts/XYPlotCntlr.js +++ b/src/firefly/js/charts/XYPlotCntlr.js @@ -6,7 +6,7 @@ import {flux} from '../Firefly.js'; import {updateSet, updateMerge} from '../util/WebUtil.js'; import {get, has, omit, omitBy, isUndefined, isString} from 'lodash'; -import {doFetchTable, getColumn, getTblById, isFullyLoaded} from '../tables/TableUtil.js'; +import {doFetchTable, getColumn, getTblById, isFullyLoaded, cloneRequest} from '../tables/TableUtil.js'; import * as TablesCntlr from '../tables/TablesCntlr.js'; import {DELETE} from './ChartsCntlr.js'; import {serializeDecimateInfo} from '../tables/Decimate.js'; @@ -308,7 +308,7 @@ function fetchPlotData(dispatch, tblId, xyPlotParams, chartId) { if (!xyPlotParams) { xyPlotParams = getDefaultXYPlotParams(tblId); } - const req = Object.assign({}, omit(activeTableServerRequest, ['tbl_id', 'META_INFO']), { + const req = cloneRequest(activeTableServerRequest, { 'startIdx' : 0, 'pageSize' : 1000000, //'inclCols' : `${xyPlotParams.x.columnOrExpr},${xyPlotParams.y.columnOrExpr}`, // ignored if 'decimate' is present diff --git a/src/firefly/js/charts/ui/ChartsTableViewPanel.jsx b/src/firefly/js/charts/ui/ChartsTableViewPanel.jsx index 4aeea71bb0..07db64f945 100644 --- a/src/firefly/js/charts/ui/ChartsTableViewPanel.jsx +++ b/src/firefly/js/charts/ui/ChartsTableViewPanel.jsx @@ -335,7 +335,7 @@ class ChartsPanel extends React.Component { filterInfoCls.setFilter(yCol, '> '+yMin); filterInfoCls.addFilter(yCol, '< '+yMax); const newRequest = Object.assign({}, tableModel.request, {filters: filterInfoCls.serialize()}); - TablesCntlr.dispatchTableFetch(newRequest, 0); + TablesCntlr.dispatchTableFilter(newRequest, 0); } } } @@ -344,7 +344,7 @@ class ChartsPanel extends React.Component { const request = get(this.props, 'tableModel.request'); if (request && request.filters) { const newRequest = Object.assign({}, request, {filters: ''}); - TablesCntlr.dispatchTableFetch(newRequest, 0); + TablesCntlr.dispatchTableFilter(newRequest, 0); } } @@ -688,7 +688,7 @@ export class FilterEditorWrapper extends React.Component { onChange={(obj) => { if (!isUndefined(obj.filterInfo)) { const newRequest = Object.assign({}, tableModel.request, {filters: obj.filterInfo}); - TablesCntlr.dispatchTableFetch(newRequest, 0); + TablesCntlr.dispatchTableFilter(newRequest, 0); } else if (!isUndefined(obj.sortInfo)) { this.setState({sortInfo: obj.sortInfo}); } diff --git a/src/firefly/js/core/ReduxFlux.js b/src/firefly/js/core/ReduxFlux.js index c3afd821ba..768d780a10 100644 --- a/src/firefly/js/core/ReduxFlux.js +++ b/src/firefly/js/core/ReduxFlux.js @@ -133,6 +133,7 @@ actionCreators.set(DrawLayerCntlr.DETACH_LAYER_FROM_PLOT, makeDetachLayerActionC actionCreators.set(TablesCntlr.TABLE_SEARCH, TablesCntlr.tableSearch); actionCreators.set(TablesCntlr.TABLE_FETCH, TablesCntlr.tableFetch); actionCreators.set(TablesCntlr.TABLE_SORT, TablesCntlr.tableFetch); +actionCreators.set(TablesCntlr.TABLE_FILTER, TablesCntlr.tableFetch); actionCreators.set(TablesCntlr.TABLE_HIGHLIGHT, TablesCntlr.highlightRow); actionCreators.set(TableStatsCntlr.LOAD_TBL_STATS, TableStatsCntlr.loadTblStats); diff --git a/src/firefly/js/core/layout/FireflyLayoutManager.js b/src/firefly/js/core/layout/FireflyLayoutManager.js index f54249a3a9..ae18749dd9 100644 --- a/src/firefly/js/core/layout/FireflyLayoutManager.js +++ b/src/firefly/js/core/layout/FireflyLayoutManager.js @@ -7,7 +7,7 @@ import {get, filter, omitBy, isNil} from 'lodash'; import {LO_VIEW, LO_MODE, SHOW_DROPDOWN, SET_LAYOUT_MODE, getLayouInfo, dispatchUpdateLayoutInfo} from '../LayoutCntlr.js'; import {clone} from '../../util/WebUtil.js'; -import {TBL_RESULTS_ADDED, TABLE_NEW, TABLE_REMOVE} from '../../tables/TablesCntlr.js'; +import {TBL_RESULTS_ADDED, TABLE_LOADED, TABLE_REMOVE} from '../../tables/TablesCntlr.js'; import ImagePlotCntlr from '../../visualize/ImagePlotCntlr.js'; import {isMetaDataTable, isCatalogTable} from '../../metaConvert/converterUtils.js'; import {META_VIEWER_ID} from '../../visualize/ui/TriViewImageSection.jsx'; @@ -29,7 +29,7 @@ export function* layoutManager({title, views='tables | images | xyPlots'}) { const action = yield take([ ImagePlotCntlr.PLOT_IMAGE_START, ImagePlotCntlr.PLOT_IMAGE, ImagePlotCntlr.DELETE_PLOT_VIEW, REPLACE_IMAGES, - TBL_RESULTS_ADDED, TABLE_REMOVE, TABLE_NEW, + TBL_RESULTS_ADDED, TABLE_REMOVE, TABLE_LOADED, SHOW_DROPDOWN, SET_LAYOUT_MODE ]); @@ -55,7 +55,7 @@ export function* layoutManager({title, views='tables | images | xyPlots'}) { ignore = handleLayoutChanges(action); break; - case TABLE_NEW : + case TABLE_LOADED : [showImages, images] = handleNewTable(action, images, showImages); break; diff --git a/src/firefly/js/drawingLayers/Catalog.js b/src/firefly/js/drawingLayers/Catalog.js index cf74912bd5..cf6d648c40 100644 --- a/src/firefly/js/drawingLayers/Catalog.js +++ b/src/firefly/js/drawingLayers/Catalog.js @@ -15,7 +15,7 @@ import DrawLayerCntlr from '../visualize/DrawLayerCntlr.js'; import {MouseState} from '../visualize/VisMouseSync.js'; import DrawOp from '../visualize/draw/DrawOp.js'; import {makeWorldPt} from '../visualize/Point.js'; -import {dispatchTableHighlight,dispatchTableFetch} from '../tables/TablesCntlr.js'; +import {dispatchTableHighlight,dispatchTableFilter} from '../tables/TablesCntlr.js'; import {COLOR_HIGHLIGHTED_PT} from '../visualize/draw/DrawingDef.js'; import {MetaConst} from '../data/MetaConst.js'; import {SelectInfo} from '../tables/SelectInfo.js'; @@ -338,7 +338,7 @@ function doClearFilter(dl) { const tbl= getTblById(dl.drawLayerId); if (!tbl) return; const newRequest = Object.assign({}, tbl.request, {filters: ''}); - dispatchTableFetch(newRequest); + dispatchTableFilter(newRequest); } @@ -358,7 +358,7 @@ function doFilter(dl,p,sel) { filter= `IN (${idxs.toString()})`; filterInfoCls.addFilter(dl.tableMeta['decimate_key'], filter); newRequest = Object.assign({}, tbl.request, {filters: filterInfoCls.serialize()}); - dispatchTableFetch(newRequest); + dispatchTableFilter(newRequest); console.log(newRequest); console.log(idxs); } @@ -368,7 +368,7 @@ function doFilter(dl,p,sel) { // filterInfoCls.setFilter(filter); filterInfoCls.setFilter('ROWID', filter); newRequest = Object.assign({}, tbl.request, {filters: filterInfoCls.serialize()}); - dispatchTableFetch(newRequest); + dispatchTableFilter(newRequest); } } diff --git a/src/firefly/js/tables/TableConnector.js b/src/firefly/js/tables/TableConnector.js index 7757bf7e32..90f6988818 100644 --- a/src/firefly/js/tables/TableConnector.js +++ b/src/firefly/js/tables/TableConnector.js @@ -35,7 +35,7 @@ export class TableConnector { // not implemented yet } else { request = Object.assign({}, request, {filters: filterIntoString}); - TblCntlr.dispatchTableFetch(request); + TblCntlr.dispatchTableFilter(request); } } @@ -59,7 +59,7 @@ export class TableConnector { }, 'IN (') + ')'; filterInfoCls.addFilter('ROWID', value); request = Object.assign({}, request, {filters: filterInfoCls.serialize()}); - TblCntlr.dispatchTableFetch(request); + TblCntlr.dispatchTableFilter(request); }); } } diff --git a/src/firefly/js/tables/TableUtil.js b/src/firefly/js/tables/TableUtil.js index 16ecff8984..24362e478a 100644 --- a/src/firefly/js/tables/TableUtil.js +++ b/src/firefly/js/tables/TableUtil.js @@ -2,7 +2,7 @@ * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt */ -import {get, set, isEmpty, uniqueId, cloneDeep, omit, omitBy, isNil, isPlainObject, isArray} from 'lodash'; +import {get, set, unset, isEmpty, uniqueId, cloneDeep, omit, omitBy, isNil, isPlainObject, isArray} from 'lodash'; import * as TblCntlr from './TablesCntlr.js'; import {SortInfo, SORT_ASC, UNSORTED} from './SortInfo.js'; import {flux} from '../Firefly.js'; @@ -132,6 +132,18 @@ export function makeVOCatalogRequest(title, params={}, options={}) { return omitBy(Object.assign(req, options, params, {id, tbl_id, META_INFO, UserTargetWorldPt}), isNil); } +/** + * create a deep clone of the given request. tbl_id is removed from the cloned request. + * @param {Object} request the original request to clone + * @param {Object} params additional parameters to add to the cloned request + * @returns {object} + */ +export function cloneRequest(request, params = {}) { + const req = cloneDeep(omit(request, 'tbl_id')); + unset(req, 'META_INFO.tbl_id'); + return Object.assign(req, params); +} + /*---------------------------- creator functions >----------------------------*/ @@ -478,7 +490,8 @@ export function getTableSourceUrl(columns, request, filename) { } /** - * returns a map of cname -> width. The width is the number of characters needed to display + * returns an array of width indexed corresponding to the given columns. + * The width is the number of characters needed to display * the header and the data as a table given columns and dataAry. * @param columns array of column object * @param dataAry array of array. @@ -491,7 +504,7 @@ export function calcColumnWidths(columns, dataAry) { width = dataAry.reduce( (maxWidth, row) => { return Math.max(maxWidth, get(row, [idx, 'length'], 0)); }, width); // max width of data - pv[cname] = width; + pv[idx] = width; return pv; }, {ROWID: 8}); } diff --git a/src/firefly/js/tables/TablesCntlr.js b/src/firefly/js/tables/TablesCntlr.js index c8345897b6..1159181a38 100644 --- a/src/firefly/js/tables/TablesCntlr.js +++ b/src/firefly/js/tables/TablesCntlr.js @@ -11,6 +11,7 @@ import {dataReducer} from './reducer/TableDataReducer.js'; import {uiReducer} from './reducer/TableUiReducer.js'; import {resultsReducer} from './reducer/TableResultsReducer.js'; import {dispatchAddSaga} from '../core/MasterSaga.js'; +import {updateMerge} from '../util/WebUtil.js'; export const TABLE_SPACE_PATH = 'table_space'; export const TABLE_RESULTS_PATH = 'table_space.results.tables'; @@ -19,24 +20,87 @@ export const RESULTS_PREFIX = 'tableResults'; export const UI_PREFIX = 'tableUi'; /*---------------------------- ACTIONS -----------------------------*/ -export const TABLE_SEARCH = `${DATA_PREFIX}.search`; -export const TABLE_NEW = `${DATA_PREFIX}.new`; -export const TABLE_NEW_LOADED = `${DATA_PREFIX}.newLoaded`; -export const TABLE_SORT = `${DATA_PREFIX}.sort`; -export const TABLE_REMOVE = `${DATA_PREFIX}.remove`; +/** + * Fetch table data. If tbl_id exists, data will be cleared. + * Sequence of actions: TABLE_FETCH -> TABLE_UPDATE(+) -> TABLE_LOADED, with invokedBy = TABLE_FETCH + */ +export const TABLE_FETCH = `${DATA_PREFIX}.fetch`; + +/** + * Fired when table is completely loaded on the server. + * Payload contains getTblInfo() + invokedBy. + * invokedBy is the action that invoke this table load. ie, fetch, sort, filter, etc. + */ +export const TABLE_LOADED = `${DATA_PREFIX}.loaded`; + +/** + * Removes table data and all UI elements associated with the data. + */ +export const TABLE_REMOVE = `${DATA_PREFIX}.remove`; -export const TABLE_FETCH = `${DATA_PREFIX}.fetch`; -export const TABLE_SELECT = `${DATA_PREFIX}.select`; -export const TABLE_HIGHLIGHT = `${DATA_PREFIX}.highlight`; -export const TABLE_UPDATE = `${DATA_PREFIX}.update`; -export const TABLE_REPLACE = `${DATA_PREFIX}.replace`; +/** + * Sort table data. Sequence of actions: TABLE_SORT -> TABLE_UPDATE(+) -> TABLE_LOADED, with invokedBy = TABLE_SORT + */ +export const TABLE_SORT = `${DATA_PREFIX}.sort`; -export const TBL_RESULTS_ADDED = `${RESULTS_PREFIX}.added`; -export const TBL_RESULTS_UPDATE = `${RESULTS_PREFIX}.update`; -export const TBL_RESULTS_ACTIVE = `${RESULTS_PREFIX}.active`; +/** + * Filter table data. Sequence of actions: TABLE_FILTER -> TABLE_UPDATE(+) -> TABLE_LOADED, with invokedBy = TABLE_FILTER + */ +export const TABLE_FILTER = `${DATA_PREFIX}.filter`; + +/** + * Fired when table selection changes. + */ +export const TABLE_SELECT = `${DATA_PREFIX}.select`; + +/** + * Fired when highlighted row changes. + * @type {string} + */ +export const TABLE_HIGHLIGHT = `${DATA_PREFIX}.highlight`; + +/** + * This action does a fetch and then add the results into the UI. + * Sequence of actions: TABLE_FETCH -> TABLE_UPDATE -> TABLE_LOADED -> TBL_RESULTS_ADDED -> TBL_RESULTS_ACTIVE + */ +export const TABLE_SEARCH = `${DATA_PREFIX}.search`; + +/** + * Add the table into the UI given information from the payload. + */ +export const TBL_RESULTS_ADDED = `${RESULTS_PREFIX}.added`; + +/** + * Add the table into the UI given information from the payload. + */ +export const TBL_RESULTS_UPDATE = `${RESULTS_PREFIX}.update`; + +/** + * Active table changes. There is only one active table per table group. + */ +export const TBL_RESULTS_ACTIVE = `${RESULTS_PREFIX}.active`; + + +/** + * Fired when UI information have changed. Used mainly by UI elements. + */ +export const TBL_UI_UPDATE = `${UI_PREFIX}.update`; + +/** + * Fired when table expanded/collapsed state changes. + */ +export const TBL_UI_EXPANDED = `${UI_PREFIX}.expanded`; + +/** + * Fired when partial table data is updated. Used internally to maintain state. + */ +export const TABLE_UPDATE = `${DATA_PREFIX}.update`; + +/** + * Fired when full table data is updated. Used internally to maintain state. + */ +export const TABLE_REPLACE = `${DATA_PREFIX}.replace`; -export const TBL_UI_UPDATE = `${UI_PREFIX}.update`; -export const TBL_UI_EXPANDED = `${UI_PREFIX}.expanded`; /*---------------------------- CREATORS ----------------------------*/ @@ -71,9 +135,9 @@ export function highlightRow(action) { set(request, 'META_INFO.padResults', true); Object.assign(request, {startIdx, pageSize}); TblUtil.doFetchTable(request, startIdx+hlRowIdx).then ( (tableModel) => { - dispatch( {type:TABLE_UPDATE, payload: tableModel} ); + dispatch( {type:TABLE_HIGHLIGHT, payload: tableModel} ); }).catch( (error) => { - dispatch({type: TABLE_UPDATE, payload: {tbl_id, error: `Fail to load table. \n ${error}`}}); + dispatch({type: TABLE_HIGHLIGHT, payload: createErrorTbl(tbl_id, `Fail to load table. \n ${error}`)}); }); } }; @@ -84,18 +148,14 @@ export function tableFetch(action) { if (!action.err) { var {request, hlRowIdx} = action.payload; const {tbl_id} = request; - var actionType = action.type; - if (action.type === TABLE_FETCH) { - actionType = TABLE_NEW; - dispatchAddSaga(doOnTblLoaded, {tbl_id, callback:() => dispatchTableLoaded(TblUtil.getTblInfoById(tbl_id))}); - } - + + dispatchAddSaga( doOnTblLoaded, {tbl_id, callback:() => dispatchTableLoaded( Object.assign(TblUtil.getTblInfoById(tbl_id), {invokedBy: action.type}) )}); + dispatch( updateMerge(action, 'payload', {tbl_id, isFetching: true}) ); request.startIdx = 0; - dispatch({type: TABLE_REPLACE, payload: {tbl_id, isFetching: true}}); TblUtil.doFetchTable(request, hlRowIdx).then ( (tableModel) => { - dispatch( {type: actionType, payload: tableModel} ); + dispatch( {type: TABLE_UPDATE, payload: tableModel} ); }).catch( (error) => { - dispatch({type: TABLE_UPDATE, payload: {tbl_id, error: `Fail to load table. \n ${error}`}}); + dispatch({type: TABLE_UPDATE, payload: createErrorTbl(tbl_id, `Fail to load table. \n ${error}`)}); }); } }; @@ -149,12 +209,22 @@ export function dispatchTableSort(request, hlRowIdx, dispatcher= flux.process) { dispatcher( {type: TABLE_SORT, payload: {request, hlRowIdx} }); } +/** + * Filter the table given the request. + * @param request a table request params object. + * @param hlRowIdx set the highlightedRow. default to startIdx. + * @param {function} dispatcher only for special dispatching uses such as remote + */ +export function dispatchTableFilter(request, hlRowIdx, dispatcher= flux.process) { + dispatcher( {type: TABLE_FILTER, payload: {request, hlRowIdx} }); +} + /** * Notify that a new table has been added and it is fully loaded. * @param tbl_info table info. see TableUtil.getTblInfo for details. */ export function dispatchTableLoaded(tbl_info) { - flux.process( {type: TABLE_NEW_LOADED, payload: tbl_info }); + flux.process( {type: TABLE_LOADED, payload: tbl_info }); } /** @@ -186,7 +256,7 @@ export function dispatchTableRemove(tbl_id) { /** * replace the tableModel matching the given tableModel.tbl_id. - * If one does not exists, it will be added. This add does not dispatch TABLE_NEW. + * If one does not exists, it will be added. This add does not dispatch TABLE_FETCH. * @param tableModel the tableModel to replace with */ export function dispatchTableReplace(tableModel) { @@ -263,7 +333,7 @@ export function* doOnTblLoaded({tbl_id, callback}) { var isLoaded = false, hasData = false; while (!(isLoaded && hasData)) { - const action = yield take([TABLE_NEW, TABLE_UPDATE]); + const action = yield take([TABLE_UPDATE]); const a_id = get(action, 'payload.tbl_id'); if (tbl_id === a_id) { isLoaded = isLoaded || TblUtil.isTableLoaded(action.payload); @@ -271,10 +341,14 @@ export function* doOnTblLoaded({tbl_id, callback}) { hasData = hasData || get(tableModel, 'tableData.columns.length'); if (get(tableModel, 'error')) { // there was an error loading this table. - // exit without callback + callback(createErrorTbl(tbl_id, tableModel.error)); return; } } } callback && callback(TblUtil.getTblInfoById(tbl_id)); } + +function createErrorTbl(tbl_id, error) { + return {tbl_id, error}; +} \ No newline at end of file diff --git a/src/firefly/js/tables/reducer/TableDataReducer.js b/src/firefly/js/tables/reducer/TableDataReducer.js index c01266d9a6..3c58690d9d 100644 --- a/src/firefly/js/tables/reducer/TableDataReducer.js +++ b/src/firefly/js/tables/reducer/TableDataReducer.js @@ -20,7 +20,7 @@ export function dataReducer(state={data:{}}, action={}) { return updateSet(root, [tbl_id, 'selectInfo'], selectInfo); } else return root; - case (Cntlr.TABLE_NEW_LOADED) : + case (Cntlr.TABLE_LOADED) : const statusPath = [tbl_id, 'tableMeta', 'Loading-Status']; if (get(root, statusPath, 'COMPLETED') !== 'COMPLETED') { return updateSet(root, statusPath, 'COMPLETED'); @@ -28,13 +28,14 @@ export function dataReducer(state={data:{}}, action={}) { case (Cntlr.TABLE_HIGHLIGHT) : case (Cntlr.TABLE_UPDATE) : - return TblUtil.smartMerge(root, {[tbl_id] : action.payload}); + return TblUtil.smartMerge(root, {[tbl_id] : {isFetching:false, ...action.payload}}); - case (Cntlr.TABLE_NEW) : + case (Cntlr.TABLE_FETCH) : + case (Cntlr.TABLE_FILTER) : case (Cntlr.TABLE_SORT) : case (Cntlr.TABLE_REPLACE) : const rowCount = action.payload.totalRows || get(action, 'payload.tableData.data.length', 0); - const nTable = Object.assign({isFetching:false, selectInfo: SelectInfo.newInstance({rowCount}).data},action.payload); + const nTable = Object.assign({isFetching:false, selectInfo: SelectInfo.newInstance({rowCount}).data}, action.payload); return updateSet(root, [tbl_id], nTable); case (Cntlr.TABLE_REMOVE) : diff --git a/src/firefly/js/tables/reducer/TableUiReducer.js b/src/firefly/js/tables/reducer/TableUiReducer.js index 68f6a2a9a4..d8476b825a 100644 --- a/src/firefly/js/tables/reducer/TableUiReducer.js +++ b/src/firefly/js/tables/reducer/TableUiReducer.js @@ -24,12 +24,13 @@ export function uiReducer(state={ui:{}}, action={}) { const {options} = action.payload || {}; return Object.assign(root, {[tbl_ui_id]:{tbl_ui_id, tbl_id, ...options}}); - case (Cntlr.TABLE_NEW) : + case (Cntlr.TABLE_FETCH) : + case (Cntlr.TABLE_FILTER) : case (Cntlr.TABLE_SORT) : case (Cntlr.TABLE_UPDATE) : case (Cntlr.TABLE_REPLACE) : case (Cntlr.TABLE_SELECT) : - case (Cntlr.TABLE_NEW_LOADED) : + case (Cntlr.TABLE_LOADED) : case (Cntlr.TABLE_HIGHLIGHT) : // state is in-progress(fresh) data.. use it to reduce ui state. return uiStateReducer(root, get(state, ['data', tbl_id])); diff --git a/src/firefly/js/tables/ui/BasicTableView.jsx b/src/firefly/js/tables/ui/BasicTableView.jsx index 57d4838c6f..d17dbe49ad 100644 --- a/src/firefly/js/tables/ui/BasicTableView.jsx +++ b/src/firefly/js/tables/ui/BasicTableView.jsx @@ -245,7 +245,7 @@ function makeColWidth(columns, showUnits) { if (!nchar) { nchar = Math.max(label.length+2, unitLength+2, get(col,'width', 0)); // 2 is for padding and sort symbol } - widths[col.name] = nchar * 8; + widths[col.name] = nchar * 7; return widths; }, {}); } @@ -296,12 +296,12 @@ function tableToText(columns, dataAry, showUnits=false) { const colWidths = calcColumnWidths(columns, dataAry); var textHead = columns.reduce( (pval, col, idx) => { - return pval + (get(columns, [idx,'visibility'], 'show') === 'show' ? `${padEnd(col.name, colWidths[col.name])}|` : ''); + return pval + (get(columns, [idx,'visibility'], 'show') === 'show' ? `${padEnd(col.name, colWidths[idx])}|` : ''); }, '|'); if (showUnits) { textHead += '\n' + columns.reduce( (pval, col, idx) => { - return pval + (get(columns, [idx,'visibility'], 'show') === 'show' ? `${padEnd(col.units || '', colWidths[col.name])}|` : ''); + return pval + (get(columns, [idx,'visibility'], 'show') === 'show' ? `${padEnd(col.units || '', colWidths[idx])}|` : ''); }, '|'); } @@ -310,7 +310,7 @@ function tableToText(columns, dataAry, showUnits=false) { row.reduce( (pv, cv, idx) => { const cname = get(columns, [idx, 'name']); if (!cname) return pv; // not defined in columns.. can ignore - return pv + (get(columns, [idx,'visibility'], 'show') === 'show' ? `${padEnd(cv || '', colWidths[cname])} ` : ''); + return pv + (get(columns, [idx,'visibility'], 'show') === 'show' ? `${padEnd(cv || '', colWidths[idx])} ` : ''); }, ' ') + '\n'; }, ''); return textHead + '\n' + textData; diff --git a/src/firefly/js/tables/ui/FilterEditor.jsx b/src/firefly/js/tables/ui/FilterEditor.jsx index 0f04da7998..add11fbe5f 100644 --- a/src/firefly/js/tables/ui/FilterEditor.jsx +++ b/src/firefly/js/tables/ui/FilterEditor.jsx @@ -36,7 +36,7 @@ export class FilterEditor extends React.Component { const {columns, selectable, onChange, sortInfo, filterInfo= ''} = this.props; if (isEmpty(columns)) return false; - const {cols, data, selectInfoCls} = prepareOptionData(columns, sortInfo, filterInfo); + const {cols, data, selectInfoCls} = prepareOptionData(columns, sortInfo, filterInfo, selectable); const callbacks = makeCallbacks(onChange, columns, data, filterInfo); const renderers = makeRenderers(callbacks.onFilter); return ( @@ -82,13 +82,13 @@ FilterEditor.defaultProps = { selectable: true }; -function prepareOptionData(columns, sortInfo, filterInfo) { +function prepareOptionData(columns, sortInfo, filterInfo, selectable) { var cols = [ {name: 'Column', visibility: 'show', fixed: true}, - {name: 'Filter', visibility: 'show', prefWidth: 12}, + {name: 'Filter', visibility: 'show'}, {name: 'Units', visibility: 'show'}, - {name: 'Description', visibility: 'show', prefWidth: 60}, + {name: '', visibility: 'hidden'}, {name: 'Selected', visibility: 'hidden'} ]; @@ -98,11 +98,16 @@ function prepareOptionData(columns, sortInfo, filterInfo) { const filter = filterInfoCls.getFilter(v.name) || ''; return [v.name||'', filter, v.units||'', v.desc||'', v.visibility !== 'hide']; } ); - const widths = calcColumnWidths(cols, data); - cols[0].prefWidth = Math.min(widths['Columns'], 12); // adjust width of column for optimum display. - cols[2].prefWidth = Math.min(widths['Units'], 12); sortTableData(data, cols, sortInfo); + const widths = calcColumnWidths(cols, data); + cols[0].prefWidth = Math.min(widths[0], 20); // adjust width of column for optimum display. + cols[1].prefWidth = Math.max(46 - widths[0] - widths[2] - widths[3] - (selectable? 3 : 0), 12); // expand filter field to fill in empty space. + cols[2].prefWidth = Math.min(widths[2], 12); + if (widths[3]) { + cols[3] = {name: 'Description', prefWidth: widths[3], visibility: 'show'}; + } + var selectInfoCls = SelectInfo.newInstance({rowCount: data.length}); selectInfoCls.data.rowCount = data.length; data.forEach( (v, idx) => { diff --git a/src/firefly/js/ui/SearchPanel.jsx b/src/firefly/js/ui/SearchPanel.jsx index 67ef44dee2..625dd7d6f6 100644 --- a/src/firefly/js/ui/SearchPanel.jsx +++ b/src/firefly/js/ui/SearchPanel.jsx @@ -82,7 +82,10 @@ function hideSearchPanel() { } function onSearchSubmit(request) { - if (request.srcTable) { + if (request.fileUpload) { + const treq = TblUtil.makeFileRequest(null, request.fileUpload, null, {filters: request.filters}); + dispatchTableSearch(treq); + } else if (request.srcTable) { const treq = TblUtil.makeFileRequest(null, request.srcTable, null, {filters: request.filters}); dispatchTableSearch(treq); } diff --git a/src/firefly/js/visualize/saga/CatalogWatcher.js b/src/firefly/js/visualize/saga/CatalogWatcher.js index fa27a55649..aeab67c46a 100644 --- a/src/firefly/js/visualize/saga/CatalogWatcher.js +++ b/src/firefly/js/visualize/saga/CatalogWatcher.js @@ -3,11 +3,11 @@ */ import {take} from 'redux-saga/effects'; -import {isEmpty, get, omit} from 'lodash'; -import {TABLE_NEW_LOADED,TABLE_SORT, TABLE_SELECT,TABLE_HIGHLIGHT,TABLE_REMOVE,TABLE_UPDATE} from '../../tables/TablesCntlr.js'; +import {isEmpty, get} from 'lodash'; +import {TABLE_LOADED, TABLE_SORT, TABLE_SELECT,TABLE_HIGHLIGHT,TABLE_REMOVE,TABLE_UPDATE} from '../../tables/TablesCntlr.js'; import {dispatchCreateDrawLayer,dispatchAttachLayerToPlot,dispatchDestroyDrawLayer, dispatchModifyCustomField} from '../DrawLayerCntlr.js'; import ImagePlotCntlr, {visRoot} from '../ImagePlotCntlr.js'; -import {getTblById, doFetchTable, getTableGroup} from '../../tables/TableUtil.js'; +import {getTblById, doFetchTable, getTableGroup, cloneRequest} from '../../tables/TableUtil.js'; import {serializeDecimateInfo} from '../../tables/Decimate.js'; import {getDrawLayerById} from '../PlotViewUtil.js'; import {dlRoot} from '../DrawLayerCntlr.js'; @@ -42,12 +42,11 @@ export function* watchCatalogs() { while (true) { - const action= yield take([TABLE_NEW_LOADED, TABLE_SORT, TABLE_SELECT,TABLE_HIGHLIGHT, TABLE_UPDATE, + const action= yield take([TABLE_LOADED, TABLE_SELECT,TABLE_HIGHLIGHT, TABLE_UPDATE, TABLE_REMOVE, ImagePlotCntlr.PLOT_IMAGE]); const {tbl_id}= action.payload; switch (action.type) { - case TABLE_SORT: - case TABLE_NEW_LOADED: + case TABLE_LOADED: handleCatalogUpdate(tbl_id); break; @@ -112,13 +111,13 @@ function handleCatalogUpdate(tbl_id) { dataTooBigForSelection= true; } - const req = Object.assign(omit(sourceTable.request, ['tbl_id', 'META_INFO']), params); + const req = cloneRequest(sourceTable.request, params); req.tbl_id = `cat-${tbl_id}`; doFetchTable(req).then( (tableModel) => { if (tableModel.tableData && tableModel.tableData.data) { - updateDrawingLayer(tbl_id, + updateDrawingLayer(tbl_id, tableModel.title, tableModel.tableData, tableModel.tableMeta, request, highlightedRow, selectInfo, columns, dataTooBigForSelection); } @@ -130,7 +129,7 @@ function handleCatalogUpdate(tbl_id) { ); } -function updateDrawingLayer(tbl_id, tableData, tableMeta, tableRequest, +function updateDrawingLayer(tbl_id, title, tableData, tableMeta, tableRequest, highlightedRow, selectInfo, columns, dataTooBigForSelection) { const plotIdAry= visRoot().plotViewAry @@ -139,13 +138,13 @@ function updateDrawingLayer(tbl_id, tableData, tableMeta, tableRequest, const dl= getDrawLayerById(dlRoot(),tbl_id); if (dl) { // update drawing layer - dispatchModifyCustomField(tbl_id, {tableData, tableMeta, tableRequest, + dispatchModifyCustomField(tbl_id, {title, tableData, tableMeta, tableRequest, highlightedRow, selectInfo, columns, dataTooBigForSelection}); } else { // new drawing layer dispatchCreateDrawLayer(Catalog.TYPE_ID, - {catalogId:tbl_id, tableData, tableMeta, tableRequest, highlightedRow, + {catalogId:tbl_id, title, tableData, tableMeta, tableRequest, highlightedRow, selectInfo, columns, dataTooBigForSelection, catalog:true}); dispatchAttachLayerToPlot(tbl_id, plotIdAry); } diff --git a/src/firefly/js/visualize/saga/ChartsSync.js b/src/firefly/js/visualize/saga/ChartsSync.js index 938e5d6921..12722921a6 100644 --- a/src/firefly/js/visualize/saga/ChartsSync.js +++ b/src/firefly/js/visualize/saga/ChartsSync.js @@ -23,7 +23,7 @@ export function* syncCharts() { var xyPlotState, histogramState; while (true) { - const action= yield take([ChartsCntlr.CHART_MOUNTED, TablesCntlr.TABLE_NEW_LOADED, TablesCntlr.TABLE_SORT]); + const action= yield take([ChartsCntlr.CHART_MOUNTED, TablesCntlr.TABLE_LOADED]); if (!ChartsCntlr.getNumRelatedCharts()) { continue; } const request= action.payload.request; switch (action.type) { @@ -44,8 +44,7 @@ export function* syncCharts() { } break; - case TablesCntlr.TABLE_SORT: - case TablesCntlr.TABLE_NEW_LOADED: + case TablesCntlr.TABLE_LOADED: const {tbl_id} = action.payload; // check if there are any mounted charts related to this table @@ -63,7 +62,8 @@ export function* syncCharts() { }); // table statistics and histogram data do not change on table sort - if (action.type !== TablesCntlr.TABLE_SORT) { + const {invokedBy} = action.payload; + if (invokedBy !== TablesCntlr.TABLE_SORT) { histogramState = getChartSpace(HISTOGRAM); Object.keys(histogramState).forEach((cid) => { if (histogramState[cid].tblId === tbl_id) { diff --git a/src/firefly/js/visualize/saga/CoverageWatcher.js b/src/firefly/js/visualize/saga/CoverageWatcher.js index 684a192391..92835fa3a2 100644 --- a/src/firefly/js/visualize/saga/CoverageWatcher.js +++ b/src/firefly/js/visualize/saga/CoverageWatcher.js @@ -4,12 +4,13 @@ import {take} from 'redux-saga/effects'; import Enum from 'enum'; -import {get,isEmpty,flattenDeep,values, omit} from 'lodash'; +import {get,isEmpty,flattenDeep,values} from 'lodash'; import {MetaConst} from '../../data/MetaConst.js'; import {TitleOptions} from '../WebPlotRequest.js'; import {CoordinateSys} from '../CoordSys.js'; -import {TABLE_NEW,TABLE_SELECT,TABLE_HIGHLIGHT, - TABLE_REMOVE,TABLE_UPDATE, TBL_RESULTS_ACTIVE} from '../../tables/TablesCntlr.js'; +import {cloneRequest} from '../../tables/TableUtil.js'; +import {TABLE_LOADED, TABLE_SELECT,TABLE_HIGHLIGHT, + TABLE_REMOVE, TBL_RESULTS_ACTIVE, TABLE_SORT} from '../../tables/TablesCntlr.js'; import ImagePlotCntlr, {visRoot, dispatchPlotImage, dispatchDeletePlotView} from '../ImagePlotCntlr.js'; import {primePlot} from '../PlotViewUtil.js'; import {REINIT_RESULT_VIEW} from '../../core/AppDataCntlr.js'; @@ -84,7 +85,7 @@ export function* watchCoverage({viewerId, options= {}}) { var previousDisplayedTableId; while (true) { previousDisplayedTableId= displayedTableId; - const action= yield take([TABLE_NEW,TABLE_SELECT,TABLE_HIGHLIGHT, TABLE_UPDATE, TABLE_REMOVE, + const action= yield take([TABLE_LOADED, TABLE_SELECT,TABLE_HIGHLIGHT, TABLE_REMOVE, TBL_RESULTS_ACTIVE, REINIT_RESULT_VIEW, DrawLayerCntlr.ATTACH_LAYER_TO_PLOT, ImagePlotCntlr.PLOT_IMAGE, @@ -111,11 +112,14 @@ export function* watchCoverage({viewerId, options= {}}) { switch (action.type) { - case TABLE_NEW: + case TABLE_LOADED: if (!getTableInGroup(tbl_id)) continue; - decimatedTables[tbl_id]= null; - displayedTableId = updateCoverage(tbl_id, viewerId, decimatedTables, options); - break; + if (get(action, 'payload.invokedBy') !== TABLE_SORT) { + // no need to update coverage on table sort.. data have not changed. + decimatedTables[tbl_id]= null; + displayedTableId = updateCoverage(tbl_id, viewerId, decimatedTables, options); + break; + } case TBL_RESULTS_ACTIVE: if (!getTableInGroup(tbl_id)) continue; @@ -186,7 +190,7 @@ function updateCoverage(tbl_id, viewerId, decimatedTables, options) { }; - const req = Object.assign(omit(table.request, ['tbl_id', 'META_INFO']), params); + const req = cloneRequest(table.request, params); req.tbl_id = `cov-${tbl_id}`; if (decimatedTables[tbl_id] /*&& decimatedTables[tbl_id].tableMeta.tblFilePath===table.tableMeta.tblFilePath*/) { //todo support decimated data @@ -323,7 +327,7 @@ function addToCoverageDrawing(plotId, options, table, allRowsTable, color) { const columns = boxData ? options.getCornersColumns(table) : options.getCenterColumns(table); dispatchCreateDrawLayer(Catalog.TYPE_ID, { catalogId: table.tbl_id, - title: `Coverage: ${tableMeta.title || table.tbl_id}`, + title: `Coverage: ${table.title || table.tbl_id}`, color, tableData, tableMeta, diff --git a/src/firefly/js/visualize/saga/ImageMetaDataWatcher.js b/src/firefly/js/visualize/saga/ImageMetaDataWatcher.js index a44c303451..267756181d 100644 --- a/src/firefly/js/visualize/saga/ImageMetaDataWatcher.js +++ b/src/firefly/js/visualize/saga/ImageMetaDataWatcher.js @@ -5,7 +5,7 @@ import {take} from 'redux-saga/effects'; import {union,get,isEmpty,difference} from 'lodash'; import {Band,allBandAry} from '../Band.js'; -import {TABLE_NEW,TABLE_SELECT,TABLE_HIGHLIGHT, +import {TABLE_SELECT,TABLE_HIGHLIGHT, TABLE_REMOVE,TABLE_UPDATE, TBL_RESULTS_ACTIVE, dispatchTableHighlight} from '../../tables/TablesCntlr.js'; import ImagePlotCntlr, {visRoot, dispatchPlotImage, dispatchDeletePlotView, dispatchPlotGroup, dispatchChangeActivePlotView} from '../ImagePlotCntlr.js'; @@ -37,7 +37,7 @@ export function* watchImageMetaData({viewerId}) { var tbl_id; var paused= true; while (true) { - const action= yield take([TABLE_NEW,TABLE_SELECT,TABLE_HIGHLIGHT, TABLE_UPDATE, TABLE_REMOVE, + const action= yield take([TABLE_SELECT,TABLE_HIGHLIGHT, TABLE_UPDATE, TABLE_REMOVE, TBL_RESULTS_ACTIVE, MultiViewCntlr.ADD_VIEWER, MultiViewCntlr.VIEWER_MOUNTED, MultiViewCntlr.VIEWER_UNMOUNTED, @@ -59,7 +59,6 @@ export function* watchImageMetaData({viewerId}) { switch (action.type) { - case TABLE_NEW: case TABLE_REMOVE: case TABLE_HIGHLIGHT: case TABLE_UPDATE: