@adaptabletools/adaptable
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
919 lines • 85.2 kB
JavaScript
import * as Redux from 'redux';
import * as PluginsRedux from '../ActionsReducers/PluginsRedux';
import * as PopupRedux from '../ActionsReducers/PopupRedux';
import { PROGRESS_INDICATOR_HIDE, PROGRESS_INDICATOR_SHOW } from '../ActionsReducers/PopupRedux';
import { createEngine as createEngineLocal } from './AdaptableReduxLocalStorageEngine';
import { mergeReducer } from './AdaptableReduxMerger';
import { isAdaptableCellChangedAlert, isAdaptableRowChangedAlert, } from '../../AdaptableState/Common/AdaptableAlert';
import { ERROR_LAYOUT } from '../../Utilities/Constants/GeneralConstants';
import * as ModuleConstants from '../../Utilities/Constants/ModuleConstants';
import Emitter from '../../Utilities/Emitter';
import { StringExtensions } from '../../Utilities/Extensions/StringExtensions';
import { PreviewHelper } from '../../Utilities/Helpers/PreviewHelper';
import { ObjectFactory } from '../../Utilities/ObjectFactory';
import { showToast } from '../../View/Components/Popups/AdaptableToaster';
import * as AlertRedux from '../ActionsReducers/AlertRedux';
import * as ApplicationRedux from '../ActionsReducers/ApplicationRedux';
import * as BulkUpdateRedux from '../ActionsReducers/BulkUpdateRedux';
import * as CalculatedColumnRedux from '../ActionsReducers/CalculatedColumnRedux';
import * as ChartingRedux from '../ActionsReducers/ChartingRedux';
import * as CommentsRedux from '../ActionsReducers/CommentsRedux';
import * as CustomSortRedux from '../ActionsReducers/CustomSortRedux';
import * as DashboardRedux from '../ActionsReducers/DashboardRedux';
import * as ExportRedux from '../ActionsReducers/ExportRedux';
import * as FlashingCellRedux from '../ActionsReducers/FlashingCellRedux';
import * as FormatColumnRedux from '../ActionsReducers/FormatColumnRedux';
import * as FreeTextColumnRedux from '../ActionsReducers/FreeTextColumnRedux';
import * as LayoutRedux from '../ActionsReducers/LayoutRedux';
import * as NamedQueryRedux from '../ActionsReducers/NamedQueryRedux';
import * as NoteRedux from '../ActionsReducers/NoteRedux';
import * as PlusMinusRedux from '../ActionsReducers/PlusMinusRedux';
import * as QuickSearchRedux from '../ActionsReducers/QuickSearchRedux';
import * as ScheduleRedux from '../ActionsReducers/ScheduleRedux';
import * as ShortcutRedux from '../ActionsReducers/ShortcutRedux';
import * as SmartEditRedux from '../ActionsReducers/SmartEditRedux';
import * as StatusBarRedux from '../ActionsReducers/StatusBarRedux';
import * as StyledColumnRedux from '../ActionsReducers/StyledColumnRedux';
import * as InternalRedux from '../ActionsReducers/InternalRedux';
import * as TeamSharingRedux from '../ActionsReducers/TeamSharingRedux';
import * as ThemeRedux from '../ActionsReducers/ThemeRedux';
import * as ToolPanelRedux from '../ActionsReducers/ToolPanelRedux';
import { isAdaptableSharedEntity, isCustomSharedEntity, } from '../../AdaptableState/TeamSharingState';
export const INIT_STATE = 'INIT_STATE';
export const LOAD_STATE = 'LOAD_STATE';
const NON_PERSIST_ACTIONS = {
'@@INIT': true,
'@@redux/init': true,
[LOAD_STATE]: true,
[INIT_STATE]: true,
// progress indicators should NOT interfere with state management as it may lead to race conditions due to load/persist state being async
[PROGRESS_INDICATOR_SHOW]: true,
[PROGRESS_INDICATOR_HIDE]: true,
};
const NON_PERSISTENT_STORE_KEYS = [
'Internal',
'Popup',
'Comment',
'Plugins',
];
export const InitState = () => ({
type: INIT_STATE,
});
export const LoadState = (State) => ({
type: LOAD_STATE,
State,
});
export class AdaptableStore {
/**
*
* @param adaptable The Adaptable instance
* @param postLoadHook A function that hydrates the state after it has been loaded from storage
*/
constructor(adaptable) {
/*
This is the main store for Adaptable State
*/
this.loadStorageInProgress = false;
this.loadStateOnStartup = true; // set to false if you want no state
this.on = (eventName, callback) => {
return this.emitter.on(eventName, callback);
};
this.onAny = (callback) => {
return this.emitter.onAny(callback);
};
this.emit = (eventName, data) => {
return this.emitter.emit(eventName, data);
};
this.loadStore = (config) => {
const { adaptable, adaptableStateKey, initialState, postLoadHook } = config;
const postProcessState = postLoadHook ?? ((state) => state);
this.storageEngine.setStateKey(adaptableStateKey);
// START STATE LOAD
this.loadStorageInProgress = true;
return (this.Load = this.storageEngine
.load(initialState)
.then((storedState) => {
if (storedState && this.loadStateOnStartup) {
this.TheStore.dispatch(LoadState(postProcessState(adaptable.adaptableOptions.stateOptions.applyState(storedState))));
}
})
.then(() => {
this.TheStore.dispatch(InitState());
// END STATE LOAD
this.loadStorageInProgress = false;
}, (e) => {
adaptable.api.consoleError('Failed to load previous Adaptable State : ', e);
//for now i'm still initializing Adaptable even if loading state has failed....
//we may revisit that later
this.TheStore.dispatch(InitState());
// END STATE LOAD
this.loadStorageInProgress = false;
this.TheStore.dispatch(PopupRedux.PopupShowAlert({
alertType: 'generic',
header: 'Configuration',
message: 'Error loading your configuration:' + e,
alertDefinition: ObjectFactory.CreateInternalAlertDefinitionForMessages('Error'),
}));
}));
};
let rootReducerObject = {
// Reducers for Non-Persisted State
Popup: PopupRedux.PopupReducer,
Internal: InternalRedux.InternalReducer,
Plugins: PluginsRedux.PluginsReducer,
Comment: CommentsRedux.CommentsReducer,
// Reducers for Persisted State
Alert: AlertRedux.AlertReducer,
Application: ApplicationRedux.ApplicationReducer,
CalculatedColumn: CalculatedColumnRedux.CalculatedColumnReducer,
Charting: ChartingRedux.ChartingReducer,
CustomSort: CustomSortRedux.CustomSortReducer,
Dashboard: DashboardRedux.DashboardReducer,
Export: ExportRedux.ExportReducer,
FlashingCell: FlashingCellRedux.FlashingCellReducer,
FormatColumn: FormatColumnRedux.FormatColumnReducer,
FreeTextColumn: FreeTextColumnRedux.FreeTextColumnReducer,
Layout: LayoutRedux.LayoutReducer,
NamedQuery: NamedQueryRedux.NamedQueryReducer,
Note: NoteRedux.NoteReducer,
PlusMinus: PlusMinusRedux.PlusMinusReducer,
QuickSearch: QuickSearchRedux.QuickSearchReducer,
Schedule: ScheduleRedux.ScheduleReducer,
Shortcut: ShortcutRedux.ShortcutReducer,
StatusBar: StatusBarRedux.StatusBarReducer,
StyledColumn: StyledColumnRedux.StyledColumnReducer,
TeamSharing: TeamSharingRedux.TeamSharingReducer,
Theme: ThemeRedux.ThemeReducer,
ToolPanel: ToolPanelRedux.ToolPanelReducer,
};
// allow plugins to participate in the root reducer
adaptable.forPlugins((plugin) => {
if (plugin.rootReducer) {
rootReducerObject = {
...rootReducerObject,
...plugin.rootReducer(rootReducerObject),
};
}
});
const initialRootReducer =
// @ts-ignore
Redux.combineReducers(rootReducerObject);
const rootReducerWithResetManagement = (state, action) => {
switch (action.type) {
case LOAD_STATE:
const { State } = action;
Object.keys(State).forEach((key) => {
state[key] = State[key];
});
break;
}
return initialRootReducer(state, action);
};
let storageEngine;
this.emitter = new Emitter();
// If the user has remote storage set then we use Remote Engine, otherwise we use Local Enginge
// not sure we can do this as we need to be backwardly compatible with existing users so need to stick with adaptable id (which should be unique)
// const localStorageKey = 'adaptable-adaptable-state-' + adaptable.adaptableOptions.primaryKey;
storageEngine = createEngineLocal({
adaptableId: adaptable.adaptableOptions.adaptableId,
adaptableStateKey: adaptable.adaptableOptions.adaptableStateKey,
userName: adaptable.adaptableOptions.userName,
initialState: adaptable.adaptableOptions.initialState,
loadState: adaptable.adaptableOptions.stateOptions.loadState,
persistState: adaptable.adaptableOptions.stateOptions.persistState,
debounceStateDelay: adaptable.adaptableOptions.stateOptions.debounceStateDelay,
});
const didPersistentStateChange = (state, newState) => {
return Object.keys(newState).some((key) => {
if (NON_PERSISTENT_STORE_KEYS.includes(key)) {
return false;
}
return state?.[key] !== newState?.[key];
});
};
// this is now VERY BADLY NAMED!
let rootReducer = mergeReducer(rootReducerWithResetManagement, LOAD_STATE);
const composeEnhancers = (x) => x;
const persistedReducer = (state, action) => {
if (adaptable.isDestroyed) {
return state;
}
const init = state === undefined;
const newState = rootReducer(state, action);
// ideally the reducer should be pure,
// but having the emitter emit the event here
// is really useful
const emitterArg = { action, state, newState };
this.emitter.emit(action.type, emitterArg);
const finalState = emitterArg.newState;
const shouldPersist = state !== finalState &&
didPersistentStateChange(state, finalState) &&
!state?.Internal?.License?.disablePersistence &&
!NON_PERSIST_ACTIONS[action.type] &&
!init &&
!this.loadStorageInProgress;
if (shouldPersist) {
const storageState = { ...finalState };
NON_PERSISTENT_STORE_KEYS.forEach((key) => {
delete storageState[key];
});
this.currentStorageState = storageState;
storageEngine.save(storageState, adaptable.adaptableOptions.stateOptions.saveState);
}
return finalState;
};
const pluginsMiddleware = [];
adaptable.forPlugins((plugin) => {
if (plugin.reduxMiddleware) {
pluginsMiddleware.push(plugin.reduxMiddleware(adaptable));
}
});
//TODO: need to check if we want the storage to be done before or after
//we enrich the state with the AB middleware
this.TheStore = Redux.createStore(persistedReducer, composeEnhancers(Redux.applyMiddleware(adaptableMiddleware(adaptable), // the main middleware that actually does stuff,
...pluginsMiddleware // the plugins middleware
)));
this.storageEngine = storageEngine;
}
destroy() {
this.emitter?.clearListeners();
this.emitter = null;
}
getCurrentStorageState() {
return this.currentStorageState;
}
saveStateNow(adaptable) {
const storageState = this.getCurrentStorageState();
if (storageState) {
return this.storageEngine.saveNow(storageState, adaptable.adaptableOptions.stateOptions.saveState);
}
return Promise.resolve(true);
}
}
// this is the main function for dealing with Redux Actions which require additional functionality to be triggered.
// Please document each use case where we have to use the Store rather than a module or a popup screen
// This should ideally be the ONLY place where we LISTEN to store changes
const adaptableMiddleware = (adaptable) => (function(middlewareAPI) {
return function (next) {
return function (action) {
switch (action.type) {
/*******************
* NAMED QUERY ACTIONS
*******************/
/**
* Use Case: User has deleted a Named Query
* Action: Check whether it is referenced elsewhere before deleting
*/
case NamedQueryRedux.NAMED_QUERY_DELETE: {
// check if Named Query is not referenced elsewhere
const actionTyped = action;
if (!adaptable.api.namedQueryApi.internalApi.validateDeletedNamedQuery(actionTyped.namedQuery.Name)) {
return;
}
const ret = next(action);
return ret;
}
/**
* Use Case: User has renamed a Named Query
* Action: Check whether it is referenced elsewhere before allowing rename
*/
case NamedQueryRedux.NAMED_QUERY_EDIT: {
const actionTyped = action;
const editedNamedQuery = actionTyped.namedQuery;
const previousNamedQueryName = middlewareAPI
.getState()
.NamedQuery.NamedQueries.find((namedQuery) => namedQuery.Uuid === editedNamedQuery.Uuid)?.Name ?? '';
if (editedNamedQuery.Name !== previousNamedQueryName) {
if (!adaptable.api.namedQueryApi.internalApi.validateRenamedNamedQuery(previousNamedQueryName)) {
return;
}
}
const ret = next(action);
return ret;
}
/*******************
* System Row Summary ACTIONS
*******************/
case InternalRedux.SUMMARY_ROW_SET: {
let nextAction = next(action);
adaptable.api.layoutApi.internalApi.setupRowSummaries();
return nextAction;
}
/*******************
* System Quick Filter ACTIONS
*******************/
case InternalRedux.QUICK_FILTER_BAR_SHOW: {
let nextAction = next(action);
adaptable.showQuickFilter();
return nextAction;
}
case InternalRedux.QUICK_FILTER_BAR_HIDE: {
let nextAction = next(action);
adaptable.hideQuickFilter();
return nextAction;
}
/*******************
* ALERT DEFINITION ACTIONS
*******************/
case AlertRedux.ALERT_DEFINITION_ADD:
case AlertRedux.ALERT_DEFINITION_EDIT:
case AlertRedux.ALERT_DEFINITION_DELETE:
case AlertRedux.ALERT_DEFINITION_UNSUSPEND:
case AlertRedux.ALERT_DEFINITION_SUSPEND:
case AlertRedux.ALERT_DEFINITION_UNSUSPEND_ALL:
case AlertRedux.ALERT_DEFINITION_SUSPEND_ALL: {
const returnAction = next(action);
const alertDefinition = returnAction
.alertDefinition;
if (returnAction.type === AlertRedux.ALERT_DEFINITION_ADD ||
returnAction.type === AlertRedux.ALERT_DEFINITION_EDIT ||
returnAction.type === AlertRedux.ALERT_DEFINITION_UNSUSPEND) {
// in case of edit, the existing reactive alert will be deleted and recreated
adaptable.api.internalApi.getAlertService().createReactiveAlert(alertDefinition);
}
if (returnAction.type === AlertRedux.ALERT_DEFINITION_DELETE ||
returnAction.type === AlertRedux.ALERT_DEFINITION_SUSPEND) {
adaptable.api.internalApi.getAlertService().deleteReactiveAlert(alertDefinition);
}
if (returnAction.type === AlertRedux.ALERT_DEFINITION_SUSPEND_ALL) {
adaptable.api.internalApi
.getAlertService()
.getReactiveActiveAlerts()
.forEach((alertDefinition) => {
adaptable.api.internalApi.getAlertService().deleteReactiveAlert(alertDefinition);
});
}
if (returnAction.type === AlertRedux.ALERT_DEFINITION_UNSUSPEND_ALL) {
adaptable.api.alertApi.internalApi
.getReactiveAlertDefinitions()
.forEach((alertDefinition) => {
if (!adaptable.api.internalApi
.getAlertService()
.isReactiveAlertActive(alertDefinition)) {
adaptable.api.internalApi
.getAlertService()
.createReactiveAlert(alertDefinition);
}
});
}
// called also for rendered column actions, see RENDERED COLUMN ACTIONS block
adaptable.updateColumnModelAndRefreshGrid();
// tell Alert Module to check if need to listen to Cell Change
let module = (adaptable.adaptableModules.get(ModuleConstants.AlertModuleId));
if (module) {
module.checkListenToCellDataChanged();
}
return returnAction;
}
/*******************
* Flashing Cell ACTIONS
*******************/
case FlashingCellRedux.FLASHING_CELL_DEFINITION_ADD:
case FlashingCellRedux.FLASHING_CELL_DEFINITION_EDIT:
case FlashingCellRedux.FLASHING_CELL_DEFINITION_DELETE:
case FlashingCellRedux.FLASHING_CELL_DEFINITION_UNSUSPEND:
case FlashingCellRedux.FLASHING_CELL_DEFINITION_SUSPEND:
case FlashingCellRedux.FLASHING_CELL_DEFINITION_UNSUSPEND_ALL:
case FlashingCellRedux.FLASHING_CELL_DEFINITION_SUSPEND_ALL: {
const returnAction = next(action);
// called also for rendered column actions, see RENDERED COLUMN ACTIONS block
adaptable.updateColumnModelAndRefreshGrid();
// check if cell data change listening needs to be activated
let module = (adaptable.adaptableModules.get(ModuleConstants.FlashingCellModuleId));
if (module) {
module.checkListenToCellDataChanged();
}
return returnAction;
}
/*******************
* FREE TEXT COLUMN ACTIONS
*******************/
/**
* Use Case: We have added / edited / deleted a Free Text Column
* Actions: We check if a deleted Column has references and return if so
* Actions: We update the Special ColumnDefs and refresh the Layout
*/
case FreeTextColumnRedux.FREE_TEXT_COLUMN_ADD:
case FreeTextColumnRedux.FREE_TEXT_COLUMN_EDIT:
case FreeTextColumnRedux.FREE_TEXT_COLUMN_DELETE: {
// First check to see Deleted Free Text Column is referenced elsewhwere and return if so
if (action.type === FreeTextColumnRedux.FREE_TEXT_COLUMN_DELETE) {
const actionTyped = action;
if (!adaptable.api.freeTextColumnApi.internalApi.validateDeletedFreeTextColumn(actionTyped.freeTextColumn)) {
return;
}
}
const returnAction = next(action);
// Check listen to Cell Data Changes and refresh the layout
let module = (adaptable.adaptableModules.get(ModuleConstants.FreeTextColumnModuleId));
if (module) {
module.checkListenToCellDataChanged();
}
adaptable.api.layoutApi.internalApi.refreshLayout();
return returnAction;
}
/*******************
* CALCULATED COLUMN ACTIONS
*******************/
/**
* Use Case: We have added / edited / deleted a Calculated Column
* Action: We update the Special ColumnDefs and refresh the Layout
*/
case CalculatedColumnRedux.CALCULATED_COLUMN_ADD:
case CalculatedColumnRedux.CALCULATED_COLUMN_EDIT:
case CalculatedColumnRedux.CALCULATED_COLUMN_DELETE: {
const actionTypedCC = action;
const calculatedColumn = actionTypedCC.calculatedColumn;
// First check for Deleted Calculated Columns with references and return if found
if (action.type === CalculatedColumnRedux.CALCULATED_COLUMN_DELETE) {
if (!adaptable.api.calculatedColumnApi.internalApi.validateDeletedCalculatedColumn(calculatedColumn)) {
return;
}
}
const returnAction = next(action);
// Manage Aggregations, fire Event, set up listener and refresh Layout
if (action.type === CalculatedColumnRedux.CALCULATED_COLUMN_ADD ||
action.type === CalculatedColumnRedux.CALCULATED_COLUMN_EDIT) {
adaptable.api.internalApi
.getCalculatedColumnExpressionService()
.createAggregatedScalarLiveValue(calculatedColumn);
}
else {
adaptable.api.internalApi
.getCalculatedColumnExpressionService()
.destroyAggregatedScalarLiveValue(calculatedColumn);
}
adaptable.api.eventApi.internalApi.fireCalculatedColumnChangedEvent(action.type, calculatedColumn);
let module = (adaptable.adaptableModules.get(ModuleConstants.CalculatedColumnModuleId));
if (module) {
module.checkListenToCellDataChanged();
}
adaptable.api.layoutApi.internalApi.refreshLayout();
return returnAction;
}
/*******************
* RENDERED COLUMN ACTIONS
*******************/
/**
* Use Case: We have updated an AdapTable Module that affects rendering
* Action: We set up all columns again
*/
case QuickSearchRedux.QUICK_SEARCH_SET_STYLE:
case FormatColumnRedux.FORMAT_COLUMN_ADD:
case FormatColumnRedux.FORMAT_COLUMN_EDIT:
case FormatColumnRedux.FORMAT_COLUMN_DELETE:
case FormatColumnRedux.FORMAT_COLUMN_DELETE_ALL:
case FormatColumnRedux.FORMAT_COLUMN_MOVE_DOWN:
case FormatColumnRedux.FORMAT_COLUMN_MOVE_UP:
case FormatColumnRedux.FORMAT_COLUMN_SUSPEND:
case FormatColumnRedux.FORMAT_COLUMN_UNSUSPEND:
case FormatColumnRedux.FORMAT_COLUMN_SUSPEND_ALL:
case FormatColumnRedux.FORMAT_COLUMN_UNSUSPEND_ALL:
case StyledColumnRedux.STYLED_COLUMN_ADD:
case StyledColumnRedux.STYLED_COLUMN_EDIT:
case StyledColumnRedux.STYLED_COLUMN_DELETE:
case StyledColumnRedux.STYLED_COLUMN_SUSPEND:
case StyledColumnRedux.STYLED_COLUMN_UNSUSPEND:
case StyledColumnRedux.STYLED_COLUMN_SUSPEND_ALL:
case StyledColumnRedux.STYLED_COLUMN_UNSUSPEND_ALL:
case CustomSortRedux.CUSTOM_SORT_ADD:
case CustomSortRedux.CUSTOM_SORT_EDIT:
case CustomSortRedux.CUSTOM_SORT_DELETE:
case CustomSortRedux.CUSTOM_SORT_SUSPEND:
case CustomSortRedux.CUSTOM_SORT_UNSUSPEND:
case CustomSortRedux.CUSTOM_SORT_SUSPEND_ALL:
case CustomSortRedux.CUSTOM_SORT_UNSUSPEND_ALL: {
const returnAction = next(action);
// called also for alert actions, see ALERT ACTIONS block
adaptable.updateColumnModelAndRefreshGrid();
return returnAction;
}
/*******************
* QUICK SEARCH ACTIONS
*******************/
/**
* Use Case: User has run a Quick Search
* Action1: Call Adaptable to redraw body so cells can be highlighted
* Action2: Run Query using Quick Search text
*/
case QuickSearchRedux.QUICK_SEARCH_RUN: {
let returnAction = next(action);
adaptable.api.gridApi.refreshAllCells(true);
// if set then return a query on the text
if (adaptable.adaptableOptions.quickSearchOptions.filterGridAfterQuickSearch) {
const actionTyped = action;
const searchText = actionTyped.quickSearchText;
if (StringExtensions.IsNotNullOrEmpty(searchText)) {
adaptable.setAgGridQuickSearch(searchText);
}
else {
adaptable.clearAgGridQuickSearch();
}
}
return returnAction;
}
/*******************
* INTERNAL ACTIONS
*******************/
/**
* Use Case: User has deleted a System Alert which has a Highlight Cell or Highlight Row
* Action: Refresh the cell / Row (to clear the Highlight)
*/
case InternalRedux.ALERT_DELETE: {
const actionTyped = action;
let ret = next(action);
const adaptableAlert = actionTyped.alert;
if (adaptableAlert.alertDefinition.AlertProperties?.HighlightCell &&
isAdaptableCellChangedAlert(adaptableAlert) &&
adaptableAlert.cellDataChangedInfo) {
const rowNode = adaptableAlert.cellDataChangedInfo.rowNode;
adaptable.api.gridApi.refreshCell(rowNode, adaptableAlert.cellDataChangedInfo.column.columnId, true);
}
if (adaptableAlert.alertDefinition.AlertProperties?.HighlightRow &&
isAdaptableRowChangedAlert(adaptableAlert) &&
adaptableAlert.rowDataChangedInfo) {
adaptable.api.gridApi.refreshRowNodes(adaptableAlert.rowDataChangedInfo.rowNodes);
}
return ret;
}
/**
* Use Case: User has deleted all System Alerts some of which have a Highlight Cell
* Action: Refresh the cell (to clear the style)
*/
case InternalRedux.ALERT_DELETE_ALL: {
const actionTyped = action;
let ret = next(action);
let alerts = actionTyped.alerts;
alerts.forEach((alert) => {
if (alert.alertDefinition.AlertProperties?.HighlightCell &&
isAdaptableCellChangedAlert(alert) &&
alert.cellDataChangedInfo) {
let rowNode = alert.cellDataChangedInfo.rowNode;
adaptable.api.gridApi.refreshCell(rowNode, alert.cellDataChangedInfo.column.columnId, true);
}
if (alert.alertDefinition.AlertProperties?.HighlightRow &&
isAdaptableRowChangedAlert(alert) &&
alert.rowDataChangedInfo) {
adaptable.api.gridApi.refreshRowNodes(alert.rowDataChangedInfo.rowNodes);
}
});
return ret;
}
/**
* Use Case: A System Alert had a Highlight Cell with a limited duration
* Action: Refresh the cell (to clear the style)
*/
case InternalRedux.ALERT_REMOVE_CELL_HIGHLIGHT: {
let ret = next(action);
const actionTyped = action;
const adaptableAlert = actionTyped.alert;
if (adaptableAlert.alertDefinition.AlertProperties?.HighlightCell) {
if (isAdaptableCellChangedAlert(adaptableAlert) &&
adaptableAlert.cellDataChangedInfo) {
const rowNode = adaptableAlert.cellDataChangedInfo.rowNode;
adaptable.api.gridApi.refreshCell(rowNode, adaptableAlert.cellDataChangedInfo.column.columnId, true);
}
}
return ret;
}
case InternalRedux.ALERT_REMOVE_ROW_HIGHLIGHT: {
let ret = next(action);
const actionTyped = action;
const adaptableAlert = actionTyped.alert;
if (adaptableAlert.alertDefinition.AlertProperties?.HighlightRow) {
if (isAdaptableCellChangedAlert(adaptableAlert) &&
adaptableAlert.cellDataChangedInfo) {
adaptable.api.gridApi.refreshRowNodes([adaptableAlert.cellDataChangedInfo.rowNode]);
}
if (isAdaptableRowChangedAlert(adaptableAlert) && adaptableAlert.rowDataChangedInfo) {
adaptable.api.gridApi.refreshRowNodes(adaptableAlert.rowDataChangedInfo.rowNodes);
}
}
return ret;
}
case InternalRedux.HIGHLIGHT_CELL_ADD: {
const actionTyped = action;
const ret = next(action);
const cellHighlightInfo = actionTyped.cellHighlightInfo;
const rowNode = adaptable.api.gridApi.getRowNodeForPrimaryKey(cellHighlightInfo.primaryKeyValue);
if (rowNode) {
adaptable.api.gridApi.refreshCell(rowNode, cellHighlightInfo.columnId, true);
}
return ret;
}
case InternalRedux.HIGHLIGHT_CELL_DELETE: {
const actionTyped = action;
const ret = next(action);
const rowNode = adaptable.api.gridApi.getRowNodeForPrimaryKey(actionTyped.primaryKeyValue);
if (rowNode) {
adaptable.api.gridApi.refreshCell(rowNode, actionTyped.columnId, true);
}
return ret;
}
case InternalRedux.HIGHLIGHT_CELL_DELETE_ALL: {
const cellHighlightInfos = middlewareAPI.getState().Internal.CellHighlightInfo;
const ret = next(action);
cellHighlightInfos.forEach((cellHighlightInfo) => {
const rowNode = adaptable.api.gridApi.getRowNodeForPrimaryKey(cellHighlightInfo.primaryKeyValue);
if (!rowNode) {
return;
}
adaptable.api.gridApi.refreshCell(rowNode, cellHighlightInfo.columnId, true);
});
return ret;
}
case InternalRedux.HIGHLIGHT_ROW_ADD: {
const actionTyped = action;
const ret = next(action);
const rowHighlightInfo = actionTyped.rowHighlightInfo;
const rowNode = adaptable.api.gridApi.getRowNodeForPrimaryKey(rowHighlightInfo.primaryKeyValue);
if (rowNode) {
adaptable.api.gridApi.refreshRowNode(rowNode);
}
return ret;
}
case InternalRedux.HIGHLIGHT_ROW_DELETE: {
const actionTyped = action;
const ret = next(action);
const rowNode = adaptable.api.gridApi.getRowNodeForPrimaryKey(actionTyped.primaryKeyValue);
if (rowNode) {
adaptable.api.gridApi.refreshRowNode(rowNode);
}
return ret;
}
case InternalRedux.HIGHLIGHT_ROWS_ADD: {
const actionTyped = action;
const ret = next(action);
const rowsHighlightInfo = actionTyped.rowsHighlightInfo;
const rowNodes = adaptable.api.gridApi.getRowNodesForPrimaryKeys(rowsHighlightInfo.primaryKeyValues);
if (rowNodes.length) {
adaptable.api.gridApi.refreshRowNodes(rowNodes);
}
return ret;
}
case InternalRedux.HIGHLIGHT_ROWS_DELETE: {
const actionTyped = action;
const ret = next(action);
const rowNodes = adaptable.api.gridApi.getRowNodesForPrimaryKeys(actionTyped.primaryKeyValues);
if (rowNodes.length) {
adaptable.api.gridApi.refreshRowNodes(rowNodes);
}
return ret;
}
case InternalRedux.HIGHLIGHT_ROW_DELETE_ALL: {
const rowHighlightInfos = middlewareAPI.getState().Internal.RowHighlightInfo;
const ret = next(action);
rowHighlightInfos.forEach((rowHighlightInfo) => {
const rowNode = adaptable.api.gridApi.getRowNodeForPrimaryKey(rowHighlightInfo.primaryKeyValue);
if (!rowNode) {
return;
}
adaptable.api.gridApi.refreshRowNode(rowNode);
});
return ret;
}
case InternalRedux.DATA_CHANGE_HISTORY_UNDO:
const actionTypedUndo = action;
const cellDataChangedInfo = actionTypedUndo.changeInfo;
adaptable.api.gridApi.undoCellEdit(cellDataChangedInfo);
return next(action);
case InternalRedux.DATA_CHANGE_HISTORY_ENABLE:
case InternalRedux.DATA_CHANGE_HISTORY_DISABLE:
case InternalRedux.DATA_CHANGE_HISTORY_SUSPEND:
case InternalRedux.DATA_CHANGE_HISTORY_RESUME:
const returnAction = next(action);
let module = (adaptable.adaptableModules.get(ModuleConstants.DataChangeHistoryModuleId));
if (module) {
module.checkListenToCellDataChanged();
}
return returnAction;
case InternalRedux.CREATE_CELL_SUMMARY_INFO: {
let module = (adaptable.adaptableModules.get(ModuleConstants.CellSummaryModuleId));
let returnAction = next(action);
let selectedCellInfo = middlewareAPI.getState().Internal.SelectedCellInfo;
let apiSummaryReturn = module.createCellSummaryInfo(selectedCellInfo);
adaptable.api.internalApi.setCellSummaryInfo(apiSummaryReturn);
return returnAction;
}
/*******************
* DATA SOURCE ACTIONS
*******************/
/**
* Use Case: Data Sources have been amended
* Action1: Fire Data Source Changed event
* Action2: Display a DataSet form if supplied
*/
case InternalRedux.DATA_SET_SELECT: {
let returnAction = next(action);
const dataSet = adaptable.api.dataSetApi.getCurrentDataSet();
adaptable.api.eventApi.internalApi.fireDataSetSelectedEvent(dataSet);
requestAnimationFrame(() => {
adaptable.api.dataSetApi.internalApi.showDataSetForm(dataSet);
});
return returnAction;
}
/*******************
* THEME ACTIONS
*******************/
/**
* Use Case: Theme has changed
* Action: Apply new Theme
*/
case ThemeRedux.THEME_SELECT: {
let returnAction = next(action);
adaptable.api.themeApi.applyCurrentTheme();
return returnAction;
}
/*******************
* Note ACTIONS
*******************/
/**
* Use Case: Note has been edited/deleted/added
* Action: Triangle can be removed/added
*/
case NoteRedux.NOTE_ADD:
case NoteRedux.NOTE_EDIT:
case NoteRedux.NOTE_DELETE: {
let returnAction = next(action);
adaptable.AnnotationsService.checkListenToEvents();
const rowNode = adaptable.api.gridApi.getRowNodeForPrimaryKey(returnAction.adaptableNote.PrimaryKeyValue);
adaptable.api.gridApi.refreshCell(rowNode, returnAction.adaptableNote.ColumnId, true);
return returnAction;
}
/*******************
* Comment ACTIONS
*******************/
/**
* Use Case: Comments has been edited/deleted/added
* Action: Triangle can be removed/added
*/
case CommentsRedux.COMMENTS_ADD:
case CommentsRedux.COMMENTS_EDIT:
case CommentsRedux.COMMENTS_DELETE:
case CommentsRedux.COMMENTS_CELL_DELETE:
case CommentsRedux.COMMENTS_CELL_ADD: {
let returnAction = next(action);
adaptable.AnnotationsService.checkListenToEvents();
let rowNode = null;
let columnId = null;
if ('cellAddress' in returnAction) {
rowNode = adaptable.api.gridApi.getRowNodeForPrimaryKey(returnAction.cellAddress.PrimaryKeyValue);
columnId = returnAction.cellAddress.ColumnId;
}
else if ('cellComments' in returnAction) {
rowNode = adaptable.api.gridApi.getRowNodeForPrimaryKey(returnAction.cellComments.PrimaryKeyValue);
columnId = returnAction.cellComments.ColumnId;
}
if (rowNode && columnId) {
adaptable.api.gridApi.refreshCell(rowNode, columnId, true);
requestAnimationFrame(() => {
const commentThreads = adaptable.api.commentApi.getAllComments();
adaptable.api.eventApi.emit('CommentChanged', commentThreads);
adaptable.api.optionsApi
.getCommentOptions()
?.persistCommentThreads?.(commentThreads);
});
}
return returnAction;
}
case CommentsRedux.COMMENTS_LOAD: {
const previousCommentThreads = adaptable.api.commentApi.getAllComments();
let returnAction = next(action);
const newCommentThreads = adaptable.api.commentApi.getAllComments() ?? [];
requestAnimationFrame(() => {
let addedCommentThreads = [];
let deletedCommentThreads = [];
const prevCommentThreadsSet = new Set((previousCommentThreads ?? []).map((c) => c.Uuid));
const newCommentThreadsSet = new Set((newCommentThreads ?? []).map((c) => c.Uuid));
for (const commentThread of newCommentThreads) {
if (!prevCommentThreadsSet.has(commentThread.Uuid)) {
addedCommentThreads.push(commentThread);
}
}
for (const commentThread of previousCommentThreads) {
if (!newCommentThreadsSet.has(commentThread.Uuid)) {
deletedCommentThreads.push(commentThread);
}
}
// This cannot be called because it would trigger an infinite loop
// adaptable.api.eventApi.emit('CommentChanged', commentThreads);
// refresh all comments cells to show or hide the triangle
[...addedCommentThreads, ...deletedCommentThreads].forEach((commentThread) => {
const node = adaptable.api.gridApi.getRowNodeForPrimaryKey(commentThread.PrimaryKeyValue);
if (node && commentThread.ColumnId) {
adaptable.api.gridApi.refreshCell(node, commentThread.ColumnId, true);
}
});
});
return returnAction;
}
/*******************
* SCHEDULE ACTIONS
*******************/
/**
* Use Case: Schedule has changed
* Action: Set up ALL jobs
*/
case ScheduleRedux.REMINDER_SCHEDULE_ADD:
case ScheduleRedux.REMINDER_SCHEDULE_EDIT:
case ScheduleRedux.REMINDER_SCHEDULE_DELETE:
case ScheduleRedux.REMINDER_SCHEDULE_UNSUSPEND:
case ScheduleRedux.REMINDER_SCHEDULE_SUSPEND:
case ScheduleRedux.REMINDER_SCHEDULE_UNSUSPEND_ALL:
case ScheduleRedux.REMINDER_SCHEDULE_SUSPEND_ALL:
case ScheduleRedux.REPORT_SCHEDULE_ADD:
case ScheduleRedux.REPORT_SCHEDULE_EDIT:
case ScheduleRedux.REPORT_SCHEDULE_DELETE:
case ScheduleRedux.REPORT_SCHEDULE_SUSPEND:
case ScheduleRedux.REPORT_SCHEDULE_UNSUSPEND:
case ScheduleRedux.REPORT_SCHEDULE_SUSPEND_ALL:
case ScheduleRedux.REPORT_SCHEDULE_UNSUSPEND_ALL:
case ScheduleRedux.IPUSHPULL_SCHEDULE_ADD:
case ScheduleRedux.IPUSHPULL_SCHEDULE_EDIT:
case ScheduleRedux.IPUSHPULL_SCHEDULE_DELETE:
case ScheduleRedux.IPUSHPULL_SCHEDULE_SUSPEND:
case ScheduleRedux.IPUSHPULL_SCHEDULE_UNSUSPEND:
case ScheduleRedux.IPUSHPULL_SCHEDULE_SUSPEND_ALL:
case ScheduleRedux.IPUSHPULL_SCHEDULE_UNSUSPEND_ALL:
case ScheduleRedux.OPENFIN_SCHEDULE_ADD:
case ScheduleRedux.OPENFIN_SCHEDULE_EDIT:
case ScheduleRedux.OPENFIN_SCHEDULE_DELETE:
case ScheduleRedux.OPENFIN_SCHEDULE_SUSPEND:
case ScheduleRedux.OPENFIN_SCHEDULE_UNSUSPEND:
case ScheduleRedux.OPENFIN_SCHEDULE_SUSPEND_ALL:
case ScheduleRedux.OPENFIN_SCHEDULE_UNSUSPEND_ALL: {
let returnAction = next(action);
let module = (adaptable.adaptableModules.get(ModuleConstants.ScheduleModuleId));
module.setUpScheduleJobs();
return returnAction;
}
/*******************
* DASHBOARD ACTIONS
*******************/
case DashboardRedux.DASHBOARD_SET_IS_COLLAPSED:
case DashboardRedux.DASHBOARD_SET_MODULE_BUTTONS:
case DashboardRedux.DASHBOARD_ACTIVE_TAB_INDEX_CHANGE:
case DashboardRedux.DASHBOARD_SET_IS_FLOATING:
case DashboardRedux.DASHBOARD_SET_IS_INLINE:
case DashboardRedux.DASHBOARD_SET_IS_HIDDEN:
case DashboardRedux.DASHBOARD_SET_FLOATING_POSITION:
case DashboardRedux.DASHBOARD_SET_TABS:
case DashboardRedux.DASHBOARD_CLOSE_TOOLBAR: {
const oldDashboardState = middlewareAPI.getState().Dashboard;
let returnAction = next(action);
const newDashboardState = middlewareAPI.getState().Dashboard;
adaptable.api.eventApi.internalApi.fireDashboardChangedEvent(action.type, oldDashboardState, newDashboardState);
return returnAction;
}
/*******************
* LAYOUT ACTIONS
*******************/
case LayoutRedux.LAYOUT_ADD:
case LayoutRedux.LAYOUT_EDIT:
case LayoutRedux.LAYOUT_SAVE:
case LayoutRedux.LAYOUT_DELETE:
case LayoutRedux.LAYOUT_COLUMN_SET_CAPTION:
case LayoutRedux.LAYOUT_SELECT: {
const oldLayoutState = middlewareAPI.getState().Layout;
// this must be called before 'next(action)'
const previousLayout = adaptable.api.layoutApi.getCurrentLayout();
let returnAction = next(action);
const newLayoutState = middlewareAPI.getState().Layout;
adaptable.api.eventApi.internalApi.fireLayoutChangedEvent(action.type, oldLayoutState, newLayoutState);
const oldLayout = (oldLayoutState.Layouts || []).find((l) => l.Name == oldLayoutState.CurrentLayout) || ERROR_LAYOUT;
const newLayout = (newLayoutState.Layouts || []).find((l) => l.Name == newLayoutState.CurrentLayout) ||
newLayoutState.Layouts[0] ||
ERROR_LAYOUT;
// Apply filtering if Column or Grid filters have been changed
let refreshFilters = false;
if (adaptable.api.filterApi.columnFilterApi.internalApi.areColumnFiltersDifferent(oldLayout.ColumnFilters, newLayout.ColumnFilters)) {
refreshFilters = true;
}
// Apply Grid filtering if Grid filter has been changed
if (!refreshFilters &&
adaptable.api.filterApi.gridFilterApi.internalApi.isGridFilterDifferent(oldLayout.GridFilter, newLayout.GridFilter)) {
refreshFilters = true;
}
if (refreshFilters) {
adaptable.applyFiltering();
}
if (returnAction.type == LayoutRedux.LAYOUT_SELECT ||
returnAction.type == LayoutRedux.LAYOUT_DELETE ||
returnAction.type == LayoutRedux.LAYOUT_COLUMN_SET_CAPTION) {
// tell AdapTable the Layout has been selected
if (newLayout) {
adaptable.setLayout(newLayout);
}
}
// when changing current layout via the api, the layout should update
if (returnAction.type == LayoutRedux.LAYOUT_SAVE) {
const savingLayout = returnAction.layout;
if (previousLayout.Name === savingLayout.Name &&
previousLayout !== savingLayout &&
// objects may have changed, but have the same contents
// this prevents pivot layout from infinite set-layout
!adaptable.api.layoutApi.internalApi.areLayoutsEqual(previousLayout, savingLayout)) {
adaptable.setLayout(savingLayout);
}
}
return returnAct