chore: Table performance improvement (#20983)
## Description Several changes made to enhance the table performance: - Batch updates of meta properties to limit the number of rerenders - Removed expensive comparator operations. - Memoised components which are not susceptible to updates. - Table filter code optimisation to limit the number of times setState is triggered. Fixes #20910
This commit is contained in:
parent
425a9f2220
commit
99afdcc2c4
|
|
@ -110,7 +110,7 @@
|
||||||
"lottie-web": "^5.7.4",
|
"lottie-web": "^5.7.4",
|
||||||
"mammoth": "^1.5.1",
|
"mammoth": "^1.5.1",
|
||||||
"marked": "^4.0.18",
|
"marked": "^4.0.18",
|
||||||
"memoize-one": "^5.2.1",
|
"memoize-one": "^6.0.0",
|
||||||
"micro-memoize": "^4.0.10",
|
"micro-memoize": "^4.0.10",
|
||||||
"moment": "2.29.4",
|
"moment": "2.29.4",
|
||||||
"moment-timezone": "^0.5.35",
|
"moment-timezone": "^0.5.35",
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ export interface UpdateWidgetMetaPropertyPayload {
|
||||||
propertyValue: unknown;
|
propertyValue: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BatchUpdateWidgetMetaPropertyPayload {
|
||||||
|
batchMetaUpdates: UpdateWidgetMetaPropertyPayload[];
|
||||||
|
}
|
||||||
export const updateWidgetMetaPropAndEval = (
|
export const updateWidgetMetaPropAndEval = (
|
||||||
widgetId: string,
|
widgetId: string,
|
||||||
propertyName: string,
|
propertyName: string,
|
||||||
|
|
@ -79,6 +82,15 @@ export const triggerEvalOnMetaUpdate = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const syncBatchUpdateWidgetMetaProperties = (
|
||||||
|
batchMetaUpdates: UpdateWidgetMetaPropertyPayload[],
|
||||||
|
): ReduxAction<BatchUpdateWidgetMetaPropertyPayload> => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.BATCH_UPDATE_META_PROPS,
|
||||||
|
payload: { batchMetaUpdates },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const syncUpdateWidgetMetaProperty = (
|
export const syncUpdateWidgetMetaProperty = (
|
||||||
widgetId: string,
|
widgetId: string,
|
||||||
propertyName: string,
|
propertyName: string,
|
||||||
|
|
|
||||||
|
|
@ -382,6 +382,7 @@ export const ReduxActionTypes = {
|
||||||
CREATE_WORKSPACE_SUCCESS: "CREATE_WORKSPACE_SUCCESS",
|
CREATE_WORKSPACE_SUCCESS: "CREATE_WORKSPACE_SUCCESS",
|
||||||
ADD_USER_TO_WORKSPACE_INIT: "ADD_USER_TO_WORKSPACE_INIT",
|
ADD_USER_TO_WORKSPACE_INIT: "ADD_USER_TO_WORKSPACE_INIT",
|
||||||
ADD_USER_TO_WORKSPACE_SUCCESS: "ADD_USER_TO_WORKSPACE_ERROR",
|
ADD_USER_TO_WORKSPACE_SUCCESS: "ADD_USER_TO_WORKSPACE_ERROR",
|
||||||
|
BATCH_UPDATE_META_PROPS: "BATCH_UPDATE_META_PROPS",
|
||||||
SET_META_PROP: "SET_META_PROP",
|
SET_META_PROP: "SET_META_PROP",
|
||||||
SET_META_PROP_AND_EVAL: "SET_META_PROP_AND_EVAL",
|
SET_META_PROP_AND_EVAL: "SET_META_PROP_AND_EVAL",
|
||||||
META_UPDATE_DEBOUNCED_EVAL: "META_UPDATE_DEBOUNCED_EVAL",
|
META_UPDATE_DEBOUNCED_EVAL: "META_UPDATE_DEBOUNCED_EVAL",
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ describe("EditorContextProvider", () => {
|
||||||
"setWidgetCache",
|
"setWidgetCache",
|
||||||
"updateMetaWidgetProperty",
|
"updateMetaWidgetProperty",
|
||||||
"syncUpdateWidgetMetaProperty",
|
"syncUpdateWidgetMetaProperty",
|
||||||
|
"syncBatchUpdateWidgetMetaProperties",
|
||||||
"triggerEvalOnMetaUpdate",
|
"triggerEvalOnMetaUpdate",
|
||||||
"deleteMetaWidgets",
|
"deleteMetaWidgets",
|
||||||
"deleteWidgetProperty",
|
"deleteWidgetProperty",
|
||||||
|
|
@ -69,6 +70,7 @@ describe("EditorContextProvider", () => {
|
||||||
"setWidgetCache",
|
"setWidgetCache",
|
||||||
"updateMetaWidgetProperty",
|
"updateMetaWidgetProperty",
|
||||||
"syncUpdateWidgetMetaProperty",
|
"syncUpdateWidgetMetaProperty",
|
||||||
|
"syncBatchUpdateWidgetMetaProperties",
|
||||||
"triggerEvalOnMetaUpdate",
|
"triggerEvalOnMetaUpdate",
|
||||||
"updateWidgetAutoHeight",
|
"updateWidgetAutoHeight",
|
||||||
"checkContainersForAutoHeight",
|
"checkContainersForAutoHeight",
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@ import {
|
||||||
import type { ExecuteTriggerPayload } from "constants/AppsmithActionConstants/ActionConstants";
|
import type { ExecuteTriggerPayload } from "constants/AppsmithActionConstants/ActionConstants";
|
||||||
import type { OccupiedSpace } from "constants/CanvasEditorConstants";
|
import type { OccupiedSpace } from "constants/CanvasEditorConstants";
|
||||||
|
|
||||||
|
import type { UpdateWidgetMetaPropertyPayload } from "actions/metaActions";
|
||||||
import {
|
import {
|
||||||
resetChildrenMetaProperty,
|
resetChildrenMetaProperty,
|
||||||
|
syncBatchUpdateWidgetMetaProperties,
|
||||||
syncUpdateWidgetMetaProperty,
|
syncUpdateWidgetMetaProperty,
|
||||||
triggerEvalOnMetaUpdate,
|
triggerEvalOnMetaUpdate,
|
||||||
} from "actions/metaActions";
|
} from "actions/metaActions";
|
||||||
|
|
@ -69,6 +71,9 @@ export type EditorContextType<TCache = unknown> = {
|
||||||
propertyName: string,
|
propertyName: string,
|
||||||
propertyValue: any,
|
propertyValue: any,
|
||||||
) => void;
|
) => void;
|
||||||
|
syncBatchUpdateWidgetMetaProperties?: (
|
||||||
|
batchMetaUpdates: UpdateWidgetMetaPropertyPayload[],
|
||||||
|
) => void;
|
||||||
updateWidgetAutoHeight?: (widgetId: string, height: number) => void;
|
updateWidgetAutoHeight?: (widgetId: string, height: number) => void;
|
||||||
checkContainersForAutoHeight?: () => void;
|
checkContainersForAutoHeight?: () => void;
|
||||||
modifyMetaWidgets?: (modifications: ModifyMetaWidgetPayload) => void;
|
modifyMetaWidgets?: (modifications: ModifyMetaWidgetPayload) => void;
|
||||||
|
|
@ -102,6 +107,7 @@ const COMMON_API_METHODS: EditorContextTypeKey[] = [
|
||||||
"setWidgetCache",
|
"setWidgetCache",
|
||||||
"updateMetaWidgetProperty",
|
"updateMetaWidgetProperty",
|
||||||
"syncUpdateWidgetMetaProperty",
|
"syncUpdateWidgetMetaProperty",
|
||||||
|
"syncBatchUpdateWidgetMetaProperties",
|
||||||
"triggerEvalOnMetaUpdate",
|
"triggerEvalOnMetaUpdate",
|
||||||
"updateWidgetAutoHeight",
|
"updateWidgetAutoHeight",
|
||||||
"checkContainersForAutoHeight",
|
"checkContainersForAutoHeight",
|
||||||
|
|
@ -191,6 +197,9 @@ const mapDispatchToProps = {
|
||||||
propertyName: string,
|
propertyName: string,
|
||||||
propertyValue: any,
|
propertyValue: any,
|
||||||
) => syncUpdateWidgetMetaProperty(widgetId, propertyName, propertyValue),
|
) => syncUpdateWidgetMetaProperty(widgetId, propertyName, propertyValue),
|
||||||
|
syncBatchUpdateWidgetMetaProperties: (
|
||||||
|
batchMetaUpdates: UpdateWidgetMetaPropertyPayload[],
|
||||||
|
) => syncBatchUpdateWidgetMetaProperties(batchMetaUpdates),
|
||||||
resetChildrenMetaProperty,
|
resetChildrenMetaProperty,
|
||||||
disableDrag: disableDragAction,
|
disableDrag: disableDragAction,
|
||||||
deleteWidgetProperty: deletePropertyAction,
|
deleteWidgetProperty: deletePropertyAction,
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ export interface LogActionPayload {
|
||||||
analytics?: Record<string, any>;
|
analytics?: Record<string, any>;
|
||||||
// plugin error details if any (only for plugin errors).
|
// plugin error details if any (only for plugin errors).
|
||||||
pluginErrorDetails?: any;
|
pluginErrorDetails?: any;
|
||||||
|
meta?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { createReducer } from "utils/ReducerUtils";
|
||||||
import type {
|
import type {
|
||||||
UpdateWidgetMetaPropertyPayload,
|
UpdateWidgetMetaPropertyPayload,
|
||||||
ResetWidgetMetaPayload,
|
ResetWidgetMetaPayload,
|
||||||
|
BatchUpdateWidgetMetaPropertyPayload,
|
||||||
} from "actions/metaActions";
|
} from "actions/metaActions";
|
||||||
|
|
||||||
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
|
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
|
||||||
|
|
@ -53,6 +54,20 @@ export const metaReducer = createReducer(initialState, {
|
||||||
|
|
||||||
return nextState;
|
return nextState;
|
||||||
},
|
},
|
||||||
|
[ReduxActionTypes.BATCH_UPDATE_META_PROPS]: (
|
||||||
|
state: MetaState,
|
||||||
|
action: ReduxAction<BatchUpdateWidgetMetaPropertyPayload>,
|
||||||
|
) => {
|
||||||
|
const nextState = produce(state, (draftMetaState) => {
|
||||||
|
const { batchMetaUpdates } = action.payload;
|
||||||
|
batchMetaUpdates.forEach(({ propertyName, propertyValue, widgetId }) => {
|
||||||
|
set(draftMetaState, `${widgetId}.${propertyName}`, propertyValue);
|
||||||
|
});
|
||||||
|
return draftMetaState;
|
||||||
|
});
|
||||||
|
|
||||||
|
return nextState;
|
||||||
|
},
|
||||||
[ReduxActionTypes.SET_META_PROP_AND_EVAL]: (
|
[ReduxActionTypes.SET_META_PROP_AND_EVAL]: (
|
||||||
state: MetaState,
|
state: MetaState,
|
||||||
action: ReduxAction<UpdateWidgetMetaPropertyPayload>,
|
action: ReduxAction<UpdateWidgetMetaPropertyPayload>,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { WidgetProps } from "./BaseWidget";
|
import type { WidgetProps } from "./BaseWidget";
|
||||||
import type BaseWidget from "./BaseWidget";
|
import type BaseWidget from "./BaseWidget";
|
||||||
import { debounce, fromPairs } from "lodash";
|
import { debounce, fromPairs, isEmpty } from "lodash";
|
||||||
import { EditorContext } from "components/editorComponents/EditorContextProvider";
|
import { EditorContext } from "components/editorComponents/EditorContextProvider";
|
||||||
import AppsmithConsole from "utils/AppsmithConsole";
|
import AppsmithConsole from "utils/AppsmithConsole";
|
||||||
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
||||||
|
|
@ -10,6 +10,12 @@ import type { ExecuteTriggerPayload } from "constants/AppsmithActionConstants/Ac
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { getWidgetMetaProps } from "sagas/selectors";
|
import { getWidgetMetaProps } from "sagas/selectors";
|
||||||
import type { AppState } from "@appsmith/reducers";
|
import type { AppState } from "@appsmith/reducers";
|
||||||
|
import { error } from "loglevel";
|
||||||
|
export type pushAction = (
|
||||||
|
propertyName: string | batchUpdateWidgetMetaPropertyType,
|
||||||
|
propertyValue?: unknown,
|
||||||
|
actionExecution?: DebouncedExecuteActionPayload,
|
||||||
|
) => void;
|
||||||
|
|
||||||
export type DebouncedExecuteActionPayload = Omit<
|
export type DebouncedExecuteActionPayload = Omit<
|
||||||
ExecuteTriggerPayload,
|
ExecuteTriggerPayload,
|
||||||
|
|
@ -17,8 +23,15 @@ export type DebouncedExecuteActionPayload = Omit<
|
||||||
> & {
|
> & {
|
||||||
dynamicString?: string;
|
dynamicString?: string;
|
||||||
};
|
};
|
||||||
|
export type batchUpdateWidgetMetaPropertyType = {
|
||||||
|
propertyName: string;
|
||||||
|
propertyValue: unknown;
|
||||||
|
actionExecution?: DebouncedExecuteActionPayload;
|
||||||
|
}[];
|
||||||
|
|
||||||
export interface WithMeta {
|
export interface WithMeta {
|
||||||
|
commitBatchMetaUpdates: () => void;
|
||||||
|
pushBatchMetaUpdates: pushAction;
|
||||||
updateWidgetMetaProperty: (
|
updateWidgetMetaProperty: (
|
||||||
propertyName: string,
|
propertyName: string,
|
||||||
propertyValue: unknown,
|
propertyValue: unknown,
|
||||||
|
|
@ -36,6 +49,7 @@ function withMeta(WrappedWidget: typeof BaseWidget) {
|
||||||
|
|
||||||
initialMetaState: Record<string, unknown>;
|
initialMetaState: Record<string, unknown>;
|
||||||
actionsToExecute: Record<string, DebouncedExecuteActionPayload>;
|
actionsToExecute: Record<string, DebouncedExecuteActionPayload>;
|
||||||
|
batchMetaUpdates: batchUpdateWidgetMetaPropertyType;
|
||||||
updatedProperties: Record<string, boolean>;
|
updatedProperties: Record<string, boolean>;
|
||||||
constructor(props: metaHOCProps) {
|
constructor(props: metaHOCProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
@ -47,6 +61,7 @@ function withMeta(WrappedWidget: typeof BaseWidget) {
|
||||||
);
|
);
|
||||||
this.updatedProperties = {};
|
this.updatedProperties = {};
|
||||||
this.actionsToExecute = {};
|
this.actionsToExecute = {};
|
||||||
|
this.batchMetaUpdates = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
addPropertyForEval = (
|
addPropertyForEval = (
|
||||||
|
|
@ -140,6 +155,112 @@ function withMeta(WrappedWidget: typeof BaseWidget) {
|
||||||
actionExecution,
|
actionExecution,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
This function pushes meta updates that can be commited later.
|
||||||
|
If there are multiple updates, use this function to batch those updates together.
|
||||||
|
*/
|
||||||
|
pushBatchMetaUpdates: pushAction = (firstArgument, ...restArgs) => {
|
||||||
|
//if first argument is an array its a batch lets push it
|
||||||
|
if (Array.isArray(firstArgument)) {
|
||||||
|
this.batchMetaUpdates.push(...firstArgument);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//if first argument is a string its a propertyName arg and we are pushing a single action
|
||||||
|
if (typeof firstArgument === "string") {
|
||||||
|
const [propertyValue, actionExecution] = restArgs;
|
||||||
|
this.batchMetaUpdates.push({
|
||||||
|
propertyName: firstArgument,
|
||||||
|
propertyValue,
|
||||||
|
actionExecution,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const allArgs = [firstArgument, ...restArgs];
|
||||||
|
|
||||||
|
error("unknown args ", allArgs);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
This function commits all batched updates in one go.
|
||||||
|
*/
|
||||||
|
commitBatchMetaUpdates = () => {
|
||||||
|
//ignore commit if batch array is empty
|
||||||
|
if (!this.batchMetaUpdates || !this.batchMetaUpdates.length) return;
|
||||||
|
|
||||||
|
const metaUpdates = this.batchMetaUpdates.reduce(
|
||||||
|
(acc: any, { propertyName, propertyValue }) => {
|
||||||
|
acc[propertyName] = propertyValue;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
AppsmithConsole.info({
|
||||||
|
logType: LOG_TYPE.WIDGET_UPDATE,
|
||||||
|
text: "Widget property was updated",
|
||||||
|
source: {
|
||||||
|
type: ENTITY_TYPE.WIDGET,
|
||||||
|
id: this.props.widgetId,
|
||||||
|
name: this.props.widgetName,
|
||||||
|
},
|
||||||
|
meta: metaUpdates,
|
||||||
|
});
|
||||||
|
// extract payload from updates
|
||||||
|
const payload = [...this.batchMetaUpdates];
|
||||||
|
//clear batch updates
|
||||||
|
this.batchMetaUpdates = [];
|
||||||
|
|
||||||
|
this.handleBatchUpdateWidgetMetaProperties(payload);
|
||||||
|
};
|
||||||
|
getMetaPropPath = (propertyName: string | undefined) => {
|
||||||
|
// look at this.props.__metaOptions, check for metaPropPath value
|
||||||
|
// if they exist, then update the propertyName
|
||||||
|
// Below code of updating metaOptions can be removed once we have ListWidget v2 where we better manage meta values of ListWidget.
|
||||||
|
const metaOptions = this.props.__metaOptions;
|
||||||
|
|
||||||
|
if (!metaOptions) return;
|
||||||
|
|
||||||
|
return `${metaOptions.metaPropPrefix}.${this.props.widgetName}.${propertyName}[${metaOptions.index}]`;
|
||||||
|
};
|
||||||
|
handleBatchUpdateWidgetMetaProperties = (
|
||||||
|
batchMetaUpdates: batchUpdateWidgetMetaPropertyType,
|
||||||
|
) => {
|
||||||
|
//if no updates ignore update call
|
||||||
|
if (!batchMetaUpdates || isEmpty(batchMetaUpdates)) return;
|
||||||
|
|
||||||
|
const { syncBatchUpdateWidgetMetaProperties } = this.context;
|
||||||
|
|
||||||
|
const widgetId = this.props.metaWidgetId || this.props.widgetId;
|
||||||
|
|
||||||
|
if (syncBatchUpdateWidgetMetaProperties) {
|
||||||
|
const metaOptions = this.props.__metaOptions;
|
||||||
|
const consolidatedUpdates = batchMetaUpdates.reduce(
|
||||||
|
(acc: any, { propertyName, propertyValue }) => {
|
||||||
|
acc.push({ widgetId, propertyName, propertyValue });
|
||||||
|
if (metaOptions) {
|
||||||
|
acc.push({
|
||||||
|
widgetId: metaOptions.widgetId,
|
||||||
|
propertyName: this.getMetaPropPath(propertyName),
|
||||||
|
propertyValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
syncBatchUpdateWidgetMetaProperties(consolidatedUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
batchMetaUpdates.forEach(({ actionExecution, propertyName }) =>
|
||||||
|
this.addPropertyForEval(propertyName, actionExecution),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setState({}, () => {
|
||||||
|
// react batches the setState call
|
||||||
|
// this will result in batching multiple updateWidgetMetaProperty calls.
|
||||||
|
this.debouncedTriggerEvalOnMetaUpdate();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
handleUpdateWidgetMetaProperty = (
|
handleUpdateWidgetMetaProperty = (
|
||||||
propertyName: string,
|
propertyName: string,
|
||||||
|
|
@ -165,10 +286,11 @@ function withMeta(WrappedWidget: typeof BaseWidget) {
|
||||||
// if they exist, then update the propertyName
|
// if they exist, then update the propertyName
|
||||||
// Below code of updating metaOptions can be removed once we have ListWidget v2 where we better manage meta values of ListWidget.
|
// Below code of updating metaOptions can be removed once we have ListWidget v2 where we better manage meta values of ListWidget.
|
||||||
const metaOptions = this.props.__metaOptions;
|
const metaOptions = this.props.__metaOptions;
|
||||||
if (metaOptions) {
|
const metaPropPath = this.getMetaPropPath(propertyName);
|
||||||
|
if (metaOptions && metaPropPath) {
|
||||||
syncUpdateWidgetMetaProperty(
|
syncUpdateWidgetMetaProperty(
|
||||||
metaOptions.widgetId,
|
metaOptions.widgetId,
|
||||||
`${metaOptions.metaPropPrefix}.${this.props.widgetName}.${propertyName}[${metaOptions.index}]`,
|
metaPropPath,
|
||||||
propertyValue,
|
propertyValue,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -194,6 +316,8 @@ function withMeta(WrappedWidget: typeof BaseWidget) {
|
||||||
return (
|
return (
|
||||||
<WrappedWidget
|
<WrappedWidget
|
||||||
{...this.updatedProps()}
|
{...this.updatedProps()}
|
||||||
|
commitBatchMetaUpdates={this.commitBatchMetaUpdates}
|
||||||
|
pushBatchMetaUpdates={this.pushBatchMetaUpdates}
|
||||||
updateWidgetMetaProperty={this.updateWidgetMetaProperty}
|
updateWidgetMetaProperty={this.updateWidgetMetaProperty}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ function MetaWidgetContextProvider({
|
||||||
const updateWidgetProperty =
|
const updateWidgetProperty =
|
||||||
metaEditorContextProps.updateWidgetProperty ??
|
metaEditorContextProps.updateWidgetProperty ??
|
||||||
editorContextProps.updateWidgetProperty;
|
editorContextProps.updateWidgetProperty;
|
||||||
|
const syncBatchUpdateWidgetMetaProperties =
|
||||||
|
metaEditorContextProps.syncBatchUpdateWidgetMetaProperties ??
|
||||||
|
editorContextProps.syncBatchUpdateWidgetMetaProperties;
|
||||||
const syncUpdateWidgetMetaProperty =
|
const syncUpdateWidgetMetaProperty =
|
||||||
metaEditorContextProps.syncUpdateWidgetMetaProperty ??
|
metaEditorContextProps.syncUpdateWidgetMetaProperty ??
|
||||||
editorContextProps.syncUpdateWidgetMetaProperty;
|
editorContextProps.syncUpdateWidgetMetaProperty;
|
||||||
|
|
@ -77,6 +80,7 @@ function MetaWidgetContextProvider({
|
||||||
getWidgetCache,
|
getWidgetCache,
|
||||||
deleteMetaWidgets,
|
deleteMetaWidgets,
|
||||||
updateMetaWidgetProperty,
|
updateMetaWidgetProperty,
|
||||||
|
syncBatchUpdateWidgetMetaProperties,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
executeAction,
|
executeAction,
|
||||||
|
|
@ -93,6 +97,7 @@ function MetaWidgetContextProvider({
|
||||||
getWidgetCache,
|
getWidgetCache,
|
||||||
deleteMetaWidgets,
|
deleteMetaWidgets,
|
||||||
updateMetaWidgetProperty,
|
updateMetaWidgetProperty,
|
||||||
|
syncBatchUpdateWidgetMetaProperties,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useMemo, useRef } from "react";
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
import { pick, reduce } from "lodash";
|
import { reduce } from "lodash";
|
||||||
import type { Row as ReactTableRowType } from "react-table";
|
import type { Row as ReactTableRowType } from "react-table";
|
||||||
import {
|
import {
|
||||||
useTable,
|
useTable,
|
||||||
|
|
@ -157,6 +157,7 @@ export type HeaderComponentProps = {
|
||||||
widgetId: string;
|
widgetId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const emptyArr: any = [];
|
||||||
export function Table(props: TableProps) {
|
export function Table(props: TableProps) {
|
||||||
const isResizingColumn = React.useRef(false);
|
const isResizingColumn = React.useRef(false);
|
||||||
const handleResizeColumn = (columnWidths: Record<string, number>) => {
|
const handleResizeColumn = (columnWidths: Record<string, number>) => {
|
||||||
|
|
@ -176,17 +177,14 @@ export function Table(props: TableProps) {
|
||||||
}
|
}
|
||||||
props.handleResizeColumn(columnWidthMap);
|
props.handleResizeColumn(columnWidthMap);
|
||||||
};
|
};
|
||||||
const data = React.useMemo(() => props.data, [JSON.stringify(props.data)]);
|
const { columns, data, multiRowSelection, toggleAllRowSelect } = props;
|
||||||
const columnString = JSON.stringify(
|
|
||||||
pick(props, ["columns", "compactMode", "columnWidthMap"]),
|
|
||||||
);
|
|
||||||
const columns = React.useMemo(() => props.columns, [columnString]);
|
|
||||||
const tableHeadercolumns = React.useMemo(
|
const tableHeadercolumns = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
props.columns.filter((column: ReactTableColumnProps) => {
|
columns.filter((column: ReactTableColumnProps) => {
|
||||||
return column.alias !== "actions";
|
return column.alias !== "actions";
|
||||||
}),
|
}),
|
||||||
[columnString],
|
[columns],
|
||||||
);
|
);
|
||||||
/*
|
/*
|
||||||
For serverSidePaginationEnabled we are taking props.data.length as the page size.
|
For serverSidePaginationEnabled we are taking props.data.length as the page size.
|
||||||
|
|
@ -212,7 +210,8 @@ export function Table(props: TableProps) {
|
||||||
totalColumnsWidth,
|
totalColumnsWidth,
|
||||||
} = useTable(
|
} = useTable(
|
||||||
{
|
{
|
||||||
columns: columns,
|
//columns and data needs to be memoised as per useTable specs
|
||||||
|
columns,
|
||||||
data,
|
data,
|
||||||
defaultColumn,
|
defaultColumn,
|
||||||
initialState: {
|
initialState: {
|
||||||
|
|
@ -234,6 +233,7 @@ export function Table(props: TableProps) {
|
||||||
} else {
|
} else {
|
||||||
// We are updating column size since the drag is complete when we are changing value of isResizing from true to false
|
// We are updating column size since the drag is complete when we are changing value of isResizing from true to false
|
||||||
if (isResizingColumn.current) {
|
if (isResizingColumn.current) {
|
||||||
|
//clear timeout logic
|
||||||
//update isResizingColumn in next event loop so that dragEnd event does not trigger click event.
|
//update isResizingColumn in next event loop so that dragEnd event does not trigger click event.
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
isResizingColumn.current = false;
|
isResizingColumn.current = false;
|
||||||
|
|
@ -247,15 +247,18 @@ export function Table(props: TableProps) {
|
||||||
startIndex = 0;
|
startIndex = 0;
|
||||||
endIndex = props.data.length;
|
endIndex = props.data.length;
|
||||||
}
|
}
|
||||||
const subPage = page.slice(startIndex, endIndex);
|
const subPage = useMemo(
|
||||||
const selectedRowIndices = props.selectedRowIndices || [];
|
() => page.slice(startIndex, endIndex),
|
||||||
|
[page, startIndex, endIndex],
|
||||||
|
);
|
||||||
|
const selectedRowIndices = props.selectedRowIndices || emptyArr;
|
||||||
const tableSizes = TABLE_SIZES[props.compactMode || CompactModeTypes.DEFAULT];
|
const tableSizes = TABLE_SIZES[props.compactMode || CompactModeTypes.DEFAULT];
|
||||||
const tableWrapperRef = useRef<HTMLDivElement | null>(null);
|
const tableWrapperRef = useRef<HTMLDivElement | null>(null);
|
||||||
const scrollBarRef = useRef<SimpleBar | null>(null);
|
const scrollBarRef = useRef<SimpleBar | null>(null);
|
||||||
const tableHeaderWrapperRef = React.createRef<HTMLDivElement>();
|
const tableHeaderWrapperRef = React.createRef<HTMLDivElement>();
|
||||||
const rowSelectionState = React.useMemo(() => {
|
const rowSelectionState = React.useMemo(() => {
|
||||||
// return : 0; no row selected | 1; all row selected | 2: some rows selected
|
// return : 0; no row selected | 1; all row selected | 2: some rows selected
|
||||||
if (!props.multiRowSelection) return null;
|
if (!multiRowSelection) return null;
|
||||||
const selectedRowCount = reduce(
|
const selectedRowCount = reduce(
|
||||||
page,
|
page,
|
||||||
(count, row) => {
|
(count, row) => {
|
||||||
|
|
@ -266,16 +269,17 @@ export function Table(props: TableProps) {
|
||||||
const result =
|
const result =
|
||||||
selectedRowCount === 0 ? 0 : selectedRowCount === page.length ? 1 : 2;
|
selectedRowCount === 0 ? 0 : selectedRowCount === page.length ? 1 : 2;
|
||||||
return result;
|
return result;
|
||||||
}, [selectedRowIndices, page]);
|
}, [multiRowSelection, page, selectedRowIndices]);
|
||||||
const handleAllRowSelectClick = (
|
const handleAllRowSelectClick = useCallback(
|
||||||
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||||
) => {
|
// if all / some rows are selected we remove selection on click
|
||||||
// if all / some rows are selected we remove selection on click
|
// else select all rows
|
||||||
// else select all rows
|
toggleAllRowSelect(!Boolean(rowSelectionState), page);
|
||||||
props.toggleAllRowSelect(!Boolean(rowSelectionState), page);
|
// loop over subPage rows and toggleRowSelected if required
|
||||||
// loop over subPage rows and toggleRowSelected if required
|
e.stopPropagation();
|
||||||
e.stopPropagation();
|
},
|
||||||
};
|
[page, rowSelectionState, toggleAllRowSelect],
|
||||||
|
);
|
||||||
const isHeaderVisible =
|
const isHeaderVisible =
|
||||||
props.isVisibleSearch ||
|
props.isVisibleSearch ||
|
||||||
props.isVisibleFilters ||
|
props.isVisibleFilters ||
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from "react";
|
import React, { memo } from "react";
|
||||||
|
|
||||||
import { CellWrapper } from "../TableStyledWrappers";
|
import { CellWrapper } from "../TableStyledWrappers";
|
||||||
import type { BaseCellComponentProps } from "../Constants";
|
import type { BaseCellComponentProps } from "../Constants";
|
||||||
|
|
@ -18,7 +18,7 @@ export interface RenderActionProps extends BaseCellComponentProps {
|
||||||
onCommandClick: (dynamicTrigger: string, onComplete: () => void) => void;
|
onCommandClick: (dynamicTrigger: string, onComplete: () => void) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ButtonCell(props: RenderActionProps) {
|
function ButtonCellComponent(props: RenderActionProps) {
|
||||||
const {
|
const {
|
||||||
allowCellWrapping,
|
allowCellWrapping,
|
||||||
cellBackground,
|
cellBackground,
|
||||||
|
|
@ -84,3 +84,4 @@ export function ButtonCell(props: RenderActionProps) {
|
||||||
</CellWrapper>
|
</CellWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export const ButtonCell = memo(ButtonCellComponent);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from "react";
|
import React, { memo } from "react";
|
||||||
import type { BaseCellComponentProps, CellAlignment } from "../Constants";
|
import type { BaseCellComponentProps, CellAlignment } from "../Constants";
|
||||||
import { ALIGN_ITEMS, JUSTIFY_CONTENT } from "../Constants";
|
import { ALIGN_ITEMS, JUSTIFY_CONTENT } from "../Constants";
|
||||||
import { CellWrapper, TooltipContentWrapper } from "../TableStyledWrappers";
|
import { CellWrapper, TooltipContentWrapper } from "../TableStyledWrappers";
|
||||||
|
|
@ -59,7 +59,7 @@ type CheckboxCellProps = BaseCellComponentProps & {
|
||||||
disabledCheckboxMessage: string;
|
disabledCheckboxMessage: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CheckboxCell = (props: CheckboxCellProps) => {
|
const CheckboxCellComponent = (props: CheckboxCellProps) => {
|
||||||
const {
|
const {
|
||||||
accentColor,
|
accentColor,
|
||||||
borderRadius,
|
borderRadius,
|
||||||
|
|
@ -122,3 +122,5 @@ export const CheckboxCell = (props: CheckboxCellProps) => {
|
||||||
</CheckboxCellWrapper>
|
</CheckboxCellWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CheckboxCell = memo(CheckboxCellComponent);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import React from "react";
|
import React, { memo } from "react";
|
||||||
|
|
||||||
import type { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
import type { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||||
import type { ButtonColumnActions } from "widgets/TableWidgetV2/constants";
|
import type { ButtonColumnActions } from "widgets/TableWidgetV2/constants";
|
||||||
import { EditableCellActions } from "widgets/TableWidgetV2/constants";
|
import { EditableCellActions } from "widgets/TableWidgetV2/constants";
|
||||||
|
|
@ -18,7 +17,7 @@ type RenderEditActionsProps = BaseCellComponentProps & {
|
||||||
onDiscard: () => void;
|
onDiscard: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditActionCell(props: RenderEditActionsProps) {
|
function EditActionCellComponent(props: RenderEditActionsProps) {
|
||||||
const {
|
const {
|
||||||
allowCellWrapping,
|
allowCellWrapping,
|
||||||
cellBackground,
|
cellBackground,
|
||||||
|
|
@ -91,3 +90,4 @@ export function EditActionCell(props: RenderEditActionsProps) {
|
||||||
</CellWrapper>
|
</CellWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export const EditActionCell = memo(EditActionCellComponent);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
import React, { createRef, useCallback, useEffect, useState } from "react";
|
import React, {
|
||||||
|
createRef,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
memo,
|
||||||
|
} from "react";
|
||||||
import { MenuItem, Tooltip, Menu } from "@blueprintjs/core";
|
import { MenuItem, Tooltip, Menu } from "@blueprintjs/core";
|
||||||
import Check from "remixicon-react/CheckFillIcon";
|
import Check from "remixicon-react/CheckFillIcon";
|
||||||
import ArrowDownIcon from "remixicon-react/ArrowDownSLineIcon";
|
import ArrowDownIcon from "remixicon-react/ArrowDownSLineIcon";
|
||||||
|
|
@ -145,7 +151,7 @@ type HeaderProps = {
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HeaderCell = (props: HeaderProps) => {
|
const HeaderCellComponent = (props: HeaderProps) => {
|
||||||
const { column, editMode, isSortable } = props;
|
const { column, editMode, isSortable } = props;
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
|
@ -335,3 +341,4 @@ export const HeaderCell = (props: HeaderProps) => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
export const HeaderCell = memo(HeaderCellComponent);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from "react";
|
import React, { memo } from "react";
|
||||||
import { getDragHandlers } from "widgets/TableWidgetV2/widget/utilities";
|
import { getDragHandlers } from "widgets/TableWidgetV2/widget/utilities";
|
||||||
import { HeaderCell } from "../cellComponents/HeaderCell";
|
import { HeaderCell } from "../cellComponents/HeaderCell";
|
||||||
import type { ReactTableColumnProps } from "../Constants";
|
import type { ReactTableColumnProps } from "../Constants";
|
||||||
|
|
@ -143,4 +143,4 @@ const TableColumnHeader = (props: TableColumnHeaderProps) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TableColumnHeader;
|
export default memo(TableColumnHeader);
|
||||||
|
|
|
||||||
|
|
@ -117,4 +117,4 @@ function ActionItem(props: ActionItemProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ActionItem;
|
export default React.memo(ActionItem);
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ const PageNumberInputWrapper = styled(NumericInput)<{
|
||||||
|
|
||||||
const MIN_PAGE_COUNT = 1;
|
const MIN_PAGE_COUNT = 1;
|
||||||
|
|
||||||
export function PageNumberInput(props: {
|
function PageNumberInputComponent(props: {
|
||||||
pageNo: number;
|
pageNo: number;
|
||||||
pageCount: number;
|
pageCount: number;
|
||||||
updatePageNo: (pageNo: number, event?: EventType) => void;
|
updatePageNo: (pageNo: number, event?: EventType) => void;
|
||||||
|
|
@ -109,3 +109,4 @@ export function PageNumberInput(props: {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export const PageNumberInput = React.memo(PageNumberInputComponent);
|
||||||
|
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
/* eslint-disable @typescript-eslint/ban-types */
|
|
||||||
// TODO(vikcy): Fix the banned types in this file
|
|
||||||
import React from "react";
|
|
||||||
import type { IconName } from "@blueprintjs/core";
|
|
||||||
import { Icon } from "@blueprintjs/core";
|
|
||||||
import styled from "styled-components";
|
|
||||||
|
|
||||||
const PagerContainer = styled.div`
|
|
||||||
&&& {
|
|
||||||
height: 49px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
function PagerIcon(props: {
|
|
||||||
icon: IconName;
|
|
||||||
onClick: Function;
|
|
||||||
className: string;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Icon
|
|
||||||
className={props.className}
|
|
||||||
icon={props.icon}
|
|
||||||
iconSize={14}
|
|
||||||
onClick={props.onClick as any}
|
|
||||||
style={{
|
|
||||||
padding: 14,
|
|
||||||
marginTop: 5,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
interface PagerProps {
|
|
||||||
pageNo: number;
|
|
||||||
prevPageClick: Function;
|
|
||||||
nextPageClick: Function;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PageWrapper = styled.div`
|
|
||||||
&& {
|
|
||||||
width: 140px;
|
|
||||||
display: flex;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function TablePagination(props: PagerProps) {
|
|
||||||
return (
|
|
||||||
<PagerContainer className={"e-control e-pager e-lib"}>
|
|
||||||
<PageWrapper>
|
|
||||||
<PagerIcon
|
|
||||||
className={
|
|
||||||
props.pageNo <= 1
|
|
||||||
? "e-prev e-icons e-icon-prev e-prevpagedisabled e-disable"
|
|
||||||
: "e-prev e-icons e-icon-prev e-prevpage"
|
|
||||||
}
|
|
||||||
icon={"chevron-left"}
|
|
||||||
onClick={props.prevPageClick}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={"e-numericcontainer"}
|
|
||||||
style={{
|
|
||||||
marginTop: 12,
|
|
||||||
marginLeft: 6,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className={"e-link e-numericitem e-spacing e-currentitem e-active"}
|
|
||||||
>
|
|
||||||
{props.pageNo}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<PagerIcon
|
|
||||||
className={"e-next e-icons e-icon-next e-nextpage"}
|
|
||||||
icon={"chevron-right"}
|
|
||||||
onClick={props.nextPageClick}
|
|
||||||
/>
|
|
||||||
</PageWrapper>
|
|
||||||
</PagerContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -113,17 +113,25 @@ interface TableFilterProps {
|
||||||
borderRadius: string;
|
borderRadius: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultFilters = [{ ...DEFAULT_FILTER }];
|
||||||
|
const getTableFilters = (filters: ReactTableFilter[] | undefined) => {
|
||||||
|
if (!filters || filters.length === 0) {
|
||||||
|
return defaultFilters;
|
||||||
|
}
|
||||||
|
return filters;
|
||||||
|
};
|
||||||
|
|
||||||
function TableFilterPaneContent(props: TableFilterProps) {
|
function TableFilterPaneContent(props: TableFilterProps) {
|
||||||
const [filters, updateFilters] = React.useState(
|
const [filters, updateFilters] = React.useState(
|
||||||
new Array<ReactTableFilter>(),
|
getTableFilters(props.filters),
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const filters: ReactTableFilter[] = props.filters ? [...props.filters] : [];
|
const updatedFiltersState = getTableFilters(props.filters);
|
||||||
if (filters.length === 0) {
|
//if props has been updated update the filters state
|
||||||
filters.push({ ...DEFAULT_FILTER });
|
if (updatedFiltersState !== filters) {
|
||||||
|
updateFilters(updatedFiltersState);
|
||||||
}
|
}
|
||||||
updateFilters(filters);
|
|
||||||
}, [props.filters]);
|
}, [props.filters]);
|
||||||
|
|
||||||
const addFilter = () => {
|
const addFilter = () => {
|
||||||
|
|
@ -150,8 +158,8 @@ function TableFilterPaneContent(props: TableFilterProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearFilters = useCallback(() => {
|
const clearFilters = useCallback(() => {
|
||||||
props.applyFilter([{ ...DEFAULT_FILTER }]);
|
props.applyFilter(defaultFilters);
|
||||||
}, []);
|
}, [props]);
|
||||||
|
|
||||||
const columns: DropdownOption[] = props.columns
|
const columns: DropdownOption[] = props.columns
|
||||||
.map((column: ReactTableColumnProps) => {
|
.map((column: ReactTableColumnProps) => {
|
||||||
|
|
|
||||||
|
|
@ -105,5 +105,5 @@ function TableFilters(props: TableFilterProps) {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const TableFiltersMemoised = React.memo(TableFilters);
|
||||||
export default TableFilters;
|
export default TableFiltersMemoised;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export interface AddNewRowBannerType {
|
||||||
disabledAddNewRowSave: boolean;
|
disabledAddNewRowSave: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AddNewRowBanner(props: AddNewRowBannerType) {
|
function AddNewRowBannerComponent(props: AddNewRowBannerType) {
|
||||||
const [isDiscardLoading, setIsDiscardLoading] = useState(false);
|
const [isDiscardLoading, setIsDiscardLoading] = useState(false);
|
||||||
const [isSaveLoading, setIsSaveLoading] = useState(false);
|
const [isSaveLoading, setIsSaveLoading] = useState(false);
|
||||||
|
|
||||||
|
|
@ -81,3 +81,4 @@ export function AddNewRowBanner(props: AddNewRowBannerType) {
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export const AddNewRowBanner = React.memo(AddNewRowBannerComponent);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export interface BannerPropType extends AddNewRowBannerType {
|
||||||
isAddRowInProgress: boolean;
|
isAddRowInProgress: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Banner(props: BannerPropType) {
|
function BannerComponent(props: BannerPropType) {
|
||||||
return props.isAddRowInProgress ? (
|
return props.isAddRowInProgress ? (
|
||||||
<AddNewRowBanner
|
<AddNewRowBanner
|
||||||
accentColor={props.accentColor}
|
accentColor={props.accentColor}
|
||||||
|
|
@ -17,3 +17,4 @@ export function Banner(props: BannerPropType) {
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
export const Banner = React.memo(BannerComponent);
|
||||||
|
|
|
||||||
|
|
@ -162,44 +162,47 @@ function ReactTableComponent(props: ReactTableComponentProps) {
|
||||||
width,
|
width,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const sortTableColumn = (columnIndex: number, asc: boolean) => {
|
const sortTableColumn = useCallback(
|
||||||
if (allowSorting) {
|
(columnIndex: number, asc: boolean) => {
|
||||||
if (columnIndex === -1) {
|
if (allowSorting) {
|
||||||
_sortTableColumn("", asc);
|
if (columnIndex === -1) {
|
||||||
} else {
|
_sortTableColumn("", asc);
|
||||||
const column = columns[columnIndex];
|
} else {
|
||||||
const columnType = column.metaProperties?.type || ColumnTypes.TEXT;
|
const column = columns[columnIndex];
|
||||||
if (
|
const columnType = column.metaProperties?.type || ColumnTypes.TEXT;
|
||||||
columnType !== ColumnTypes.IMAGE &&
|
if (
|
||||||
columnType !== ColumnTypes.VIDEO
|
columnType !== ColumnTypes.IMAGE &&
|
||||||
) {
|
columnType !== ColumnTypes.VIDEO
|
||||||
_sortTableColumn(column.alias, asc);
|
) {
|
||||||
|
_sortTableColumn(column.alias, asc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
[_sortTableColumn, allowSorting, columns],
|
||||||
|
);
|
||||||
|
|
||||||
const selectTableRow = (row: {
|
const selectTableRow = useCallback(
|
||||||
original: Record<string, unknown>;
|
(row: { original: Record<string, unknown>; index: number }) => {
|
||||||
index: number;
|
if (allowRowSelection) {
|
||||||
}) => {
|
onRowClick(row.original, row.index);
|
||||||
if (allowRowSelection) {
|
|
||||||
onRowClick(row.original, row.index);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleAllRowSelect = (
|
|
||||||
isSelect: boolean,
|
|
||||||
pageData: Row<Record<string, unknown>>[],
|
|
||||||
) => {
|
|
||||||
if (allowRowSelection) {
|
|
||||||
if (isSelect) {
|
|
||||||
selectAllRow(pageData);
|
|
||||||
} else {
|
|
||||||
unSelectAllRow(pageData);
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
[allowRowSelection, onRowClick],
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleAllRowSelect = useCallback(
|
||||||
|
(isSelect: boolean, pageData: Row<Record<string, unknown>>[]) => {
|
||||||
|
if (allowRowSelection) {
|
||||||
|
if (isSelect) {
|
||||||
|
selectAllRow(pageData);
|
||||||
|
} else {
|
||||||
|
unSelectAllRow(pageData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[allowRowSelection, selectAllRow, unSelectAllRow],
|
||||||
|
);
|
||||||
|
|
||||||
const memoziedDisableDrag = useCallback(
|
const memoziedDisableDrag = useCallback(
|
||||||
() => disableDrag(true),
|
() => disableDrag(true),
|
||||||
|
|
@ -310,11 +313,13 @@ export default React.memo(ReactTableComponent, (prev, next) => {
|
||||||
prev.borderWidth === next.borderWidth &&
|
prev.borderWidth === next.borderWidth &&
|
||||||
prev.borderColor === next.borderColor &&
|
prev.borderColor === next.borderColor &&
|
||||||
prev.accentColor === next.accentColor &&
|
prev.accentColor === next.accentColor &&
|
||||||
|
//shallow equal possible
|
||||||
equal(prev.columnWidthMap, next.columnWidthMap) &&
|
equal(prev.columnWidthMap, next.columnWidthMap) &&
|
||||||
equal(prev.tableData, next.tableData) &&
|
//static reference
|
||||||
|
prev.tableData === next.tableData &&
|
||||||
// Using JSON stringify becuase isEqual doesnt work with functions,
|
// Using JSON stringify becuase isEqual doesnt work with functions,
|
||||||
// and we are not changing the columns manually.
|
// and we are not changing the columns manually.
|
||||||
JSON.stringify(prev.columns) === JSON.stringify(next.columns) &&
|
prev.columns === next.columns &&
|
||||||
equal(prev.editableCell, next.editableCell) &&
|
equal(prev.editableCell, next.editableCell) &&
|
||||||
prev.variant === next.variant &&
|
prev.variant === next.variant &&
|
||||||
prev.primaryColumnId === next.primaryColumnId &&
|
prev.primaryColumnId === next.primaryColumnId &&
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,164 @@
|
||||||
|
import { isBoolean, isArray, findIndex, isEqual } from "lodash";
|
||||||
|
import type { RenderMode } from "constants/WidgetConstants";
|
||||||
|
import { RenderModes } from "constants/WidgetConstants";
|
||||||
|
import { StickyType } from "../../component/Constants";
|
||||||
|
import {
|
||||||
|
COLUMN_MIN_WIDTH,
|
||||||
|
DEFAULT_COLUMN_WIDTH,
|
||||||
|
DEFAULT_COLUMN_NAME,
|
||||||
|
} from "../../constants";
|
||||||
|
import { fetchSticky } from "../utilities";
|
||||||
|
import type {
|
||||||
|
ColumnProperties,
|
||||||
|
ReactTableColumnProps,
|
||||||
|
} from "../../component/Constants";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
|
||||||
|
export type getColumns = (
|
||||||
|
renderCell: any,
|
||||||
|
columnWidthMap: { [key: string]: number } | undefined,
|
||||||
|
orderedTableColumns: any,
|
||||||
|
componentWidth: number,
|
||||||
|
primaryColumns: Record<string, ColumnProperties>,
|
||||||
|
renderMode: RenderMode,
|
||||||
|
widgetId: string,
|
||||||
|
) => ReactTableColumnProps[];
|
||||||
|
|
||||||
|
//TODO: (Vamsi) need to unit test this function
|
||||||
|
|
||||||
|
export const getColumnsPureFn: getColumns = (
|
||||||
|
renderCell,
|
||||||
|
columnWidthMap = {},
|
||||||
|
orderedTableColumns = [],
|
||||||
|
componentWidth,
|
||||||
|
primaryColumns,
|
||||||
|
renderMode,
|
||||||
|
widgetId,
|
||||||
|
) => {
|
||||||
|
let columns: ReactTableColumnProps[] = [];
|
||||||
|
const hiddenColumns: ReactTableColumnProps[] = [];
|
||||||
|
|
||||||
|
let totalColumnWidth = 0;
|
||||||
|
|
||||||
|
if (isArray(orderedTableColumns)) {
|
||||||
|
orderedTableColumns.forEach((column: any) => {
|
||||||
|
const isHidden = !column.isVisible;
|
||||||
|
|
||||||
|
const columnData = {
|
||||||
|
id: column.id,
|
||||||
|
Header:
|
||||||
|
column.hasOwnProperty("label") && typeof column.label === "string"
|
||||||
|
? column.label
|
||||||
|
: DEFAULT_COLUMN_NAME,
|
||||||
|
alias: column.alias,
|
||||||
|
accessor: (row: any) => row[column.alias],
|
||||||
|
width: columnWidthMap[column.id] || DEFAULT_COLUMN_WIDTH,
|
||||||
|
minWidth: COLUMN_MIN_WIDTH,
|
||||||
|
draggable: true,
|
||||||
|
isHidden: false,
|
||||||
|
isAscOrder: column.isAscOrder,
|
||||||
|
isDerived: column.isDerived,
|
||||||
|
sticky: fetchSticky(column.id, primaryColumns, renderMode, widgetId),
|
||||||
|
metaProperties: {
|
||||||
|
isHidden: isHidden,
|
||||||
|
type: column.columnType,
|
||||||
|
format: column.outputFormat || "",
|
||||||
|
inputFormat: column.inputFormat || "",
|
||||||
|
},
|
||||||
|
columnProperties: column,
|
||||||
|
Cell: renderCell,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAllCellVisible: boolean | boolean[] = column.isCellVisible;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If all cells are not visible or column itself is not visible,
|
||||||
|
* set isHidden and push it to hiddenColumns array else columns array
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
(isBoolean(isAllCellVisible) && !isAllCellVisible) ||
|
||||||
|
(isArray(isAllCellVisible) &&
|
||||||
|
isAllCellVisible.every((visibility) => visibility === false)) ||
|
||||||
|
isHidden
|
||||||
|
) {
|
||||||
|
columnData.isHidden = true;
|
||||||
|
hiddenColumns.push(columnData);
|
||||||
|
} else {
|
||||||
|
totalColumnWidth += columnData.width;
|
||||||
|
columns.push(columnData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastColumnIndex = columns.length - 1;
|
||||||
|
if (totalColumnWidth < componentWidth) {
|
||||||
|
/*
|
||||||
|
This "if" block is responsible for upsizing the last column width
|
||||||
|
if there is space left in the table container towards the right
|
||||||
|
*/
|
||||||
|
if (columns[lastColumnIndex]) {
|
||||||
|
const lastColumnWidth =
|
||||||
|
columns[lastColumnIndex].width || DEFAULT_COLUMN_WIDTH;
|
||||||
|
const remainingWidth = componentWidth - totalColumnWidth;
|
||||||
|
// Adding the remaining width i.e. space left towards the right, to the last column width
|
||||||
|
columns[lastColumnIndex].width = lastColumnWidth + remainingWidth;
|
||||||
|
}
|
||||||
|
} else if (totalColumnWidth > componentWidth) {
|
||||||
|
/*
|
||||||
|
This "else-if" block is responsible for downsizing the last column width
|
||||||
|
if the last column spills over resulting in horizontal scroll
|
||||||
|
*/
|
||||||
|
const extraWidth = totalColumnWidth - componentWidth;
|
||||||
|
const lastColWidth = columns[lastColumnIndex].width || DEFAULT_COLUMN_WIDTH;
|
||||||
|
/*
|
||||||
|
Below if condition explanation:
|
||||||
|
Condition 1: (lastColWidth > COLUMN_MIN_WIDTH)
|
||||||
|
We will downsize the last column only if its greater than COLUMN_MIN_WIDTH
|
||||||
|
Condition 2: (extraWidth < lastColWidth)
|
||||||
|
This condition checks whether the last column is the only column that is spilling over.
|
||||||
|
If more than one columns are spilling over we won't downsize the last column
|
||||||
|
*/
|
||||||
|
if (lastColWidth > COLUMN_MIN_WIDTH && extraWidth < lastColWidth) {
|
||||||
|
const availableWidthForLastColumn = lastColWidth - extraWidth;
|
||||||
|
/*
|
||||||
|
Below we are making sure last column width doesn't go lower than COLUMN_MIN_WIDTH again
|
||||||
|
as availableWidthForLastColumn might go lower than COLUMN_MIN_WIDTH in some cases
|
||||||
|
*/
|
||||||
|
columns[lastColumnIndex].width =
|
||||||
|
availableWidthForLastColumn < COLUMN_MIN_WIDTH
|
||||||
|
? COLUMN_MIN_WIDTH
|
||||||
|
: availableWidthForLastColumn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hiddenColumns.length && renderMode === RenderModes.CANVAS) {
|
||||||
|
// Get the index of the first column that is frozen to right
|
||||||
|
const rightFrozenColumnIdx = findIndex(
|
||||||
|
columns,
|
||||||
|
(col) => col.sticky === StickyType.RIGHT,
|
||||||
|
);
|
||||||
|
if (rightFrozenColumnIdx !== -1) {
|
||||||
|
columns.splice(rightFrozenColumnIdx, 0, ...hiddenColumns);
|
||||||
|
} else {
|
||||||
|
columns = columns.concat(hiddenColumns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns.filter((column: ReactTableColumnProps) => !!column.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
// the result of this cache function is a prop for the useTable hook, this prop needs to memoised as per their docs
|
||||||
|
// we have noticed expensive computation from the useTable if columns isnt memoised
|
||||||
|
export const getMemoiseGetColumnsWithLocalStorageFn = () => {
|
||||||
|
const memoisedGetColumns = memoizeOne(getColumnsPureFn);
|
||||||
|
|
||||||
|
return memoizeOne(
|
||||||
|
//we are not using this parameter it is used by the memoisation comparator
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
(widgetLocalStorageState) => {
|
||||||
|
memoisedGetColumns.clear();
|
||||||
|
return memoisedGetColumns as getColumns;
|
||||||
|
},
|
||||||
|
isEqual,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
import log from "loglevel";
|
||||||
|
import type { MomentInput } from "moment";
|
||||||
|
import moment from "moment";
|
||||||
|
import _, { isNumber, isNil, isArray } from "lodash";
|
||||||
|
import type { EditableCell } from "../../constants";
|
||||||
|
import { ColumnTypes, DateInputFormat } from "../../constants";
|
||||||
|
import type { ReactTableColumnProps } from "../../component/Constants";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import shallowEqual from "shallowequal";
|
||||||
|
|
||||||
|
export type tableData = Array<Record<string, unknown>>;
|
||||||
|
|
||||||
|
//TODO: (Vamsi) need to unit test this function
|
||||||
|
export const transformDataPureFn = (
|
||||||
|
tableData: Array<Record<string, unknown>>,
|
||||||
|
columns: ReactTableColumnProps[],
|
||||||
|
): tableData => {
|
||||||
|
if (isArray(tableData)) {
|
||||||
|
return tableData.map((row, rowIndex) => {
|
||||||
|
const newRow: { [key: string]: any } = {};
|
||||||
|
|
||||||
|
columns.forEach((column) => {
|
||||||
|
const { alias } = column;
|
||||||
|
let value = row[alias];
|
||||||
|
|
||||||
|
if (column.metaProperties) {
|
||||||
|
switch (column.metaProperties.type) {
|
||||||
|
case ColumnTypes.DATE:
|
||||||
|
let isValidDate = true;
|
||||||
|
const outputFormat = _.isArray(column.metaProperties.format)
|
||||||
|
? column.metaProperties.format[rowIndex]
|
||||||
|
: column.metaProperties.format;
|
||||||
|
let inputFormat;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const type = _.isArray(column.metaProperties.inputFormat)
|
||||||
|
? column.metaProperties.inputFormat[rowIndex]
|
||||||
|
: column.metaProperties.inputFormat;
|
||||||
|
|
||||||
|
if (
|
||||||
|
type !== DateInputFormat.EPOCH &&
|
||||||
|
type !== DateInputFormat.MILLISECONDS
|
||||||
|
) {
|
||||||
|
inputFormat = type;
|
||||||
|
moment(value as MomentInput, inputFormat);
|
||||||
|
} else if (!isNumber(value)) {
|
||||||
|
isValidDate = false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
isValidDate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValidDate && value) {
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
column.metaProperties.inputFormat ===
|
||||||
|
DateInputFormat.MILLISECONDS
|
||||||
|
) {
|
||||||
|
value = Number(value);
|
||||||
|
} else if (
|
||||||
|
column.metaProperties.inputFormat === DateInputFormat.EPOCH
|
||||||
|
) {
|
||||||
|
value = 1000 * Number(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
newRow[alias] = moment(
|
||||||
|
value as MomentInput,
|
||||||
|
inputFormat,
|
||||||
|
).format(outputFormat);
|
||||||
|
} catch (e) {
|
||||||
|
log.debug("Unable to parse Date:", { e });
|
||||||
|
newRow[alias] = "";
|
||||||
|
}
|
||||||
|
} else if (value) {
|
||||||
|
newRow[alias] = "Invalid Value";
|
||||||
|
} else {
|
||||||
|
newRow[alias] = "";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
let data;
|
||||||
|
|
||||||
|
if (
|
||||||
|
_.isString(value) ||
|
||||||
|
_.isNumber(value) ||
|
||||||
|
_.isBoolean(value)
|
||||||
|
) {
|
||||||
|
data = value;
|
||||||
|
} else if (isNil(value)) {
|
||||||
|
data = "";
|
||||||
|
} else {
|
||||||
|
data = JSON.stringify(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
newRow[alias] = data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return newRow;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// lazily generate the cache so that we can create several memoised instances
|
||||||
|
const getMemoizedTransformData = () => memoizeOne(transformDataPureFn);
|
||||||
|
|
||||||
|
export const injectEditableCellToTableData = (
|
||||||
|
tableData: tableData,
|
||||||
|
editableCell: EditableCell | undefined,
|
||||||
|
): tableData => {
|
||||||
|
/*
|
||||||
|
* Inject the edited cell value from the editableCell object
|
||||||
|
*/
|
||||||
|
if (!editableCell || !tableData.length) return tableData;
|
||||||
|
const { column, index: updatedRowIndex, inputValue } = editableCell;
|
||||||
|
|
||||||
|
const inRangeForUpdate =
|
||||||
|
updatedRowIndex >= 0 && updatedRowIndex < tableData.length;
|
||||||
|
if (!inRangeForUpdate) return tableData;
|
||||||
|
//if same value ignore update
|
||||||
|
if (tableData[updatedRowIndex][column] === inputValue) return tableData;
|
||||||
|
//create copies of data
|
||||||
|
const copy = [...tableData];
|
||||||
|
copy[updatedRowIndex] = { ...copy[updatedRowIndex], [column]: inputValue };
|
||||||
|
return copy;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMemoiseInjectEditableCellToTableData = () =>
|
||||||
|
memoizeOne(injectEditableCellToTableData, (prev, next) => {
|
||||||
|
const [prevTableData, prevCellEditable] = prev;
|
||||||
|
const [nextTableData, nextCellEditable] = next;
|
||||||
|
//shallow compare the cellEditable properties
|
||||||
|
if (!shallowEqual(prevCellEditable, nextCellEditable)) return false;
|
||||||
|
|
||||||
|
return shallowEqual(prevTableData, nextTableData);
|
||||||
|
});
|
||||||
|
|
||||||
|
export type transformDataWithEditableCell = (
|
||||||
|
editableCell: EditableCell | undefined,
|
||||||
|
tableData: Array<Record<string, unknown>>,
|
||||||
|
columns: ReactTableColumnProps[],
|
||||||
|
) => tableData;
|
||||||
|
|
||||||
|
// the result of this cache function is a prop for the useTable hook, this prop needs to memoised as per their docs
|
||||||
|
// we have noticed expensive computation from the useTable if tableData isnt memoised
|
||||||
|
export const getMemoiseTransformDataWithEditableCell =
|
||||||
|
(): transformDataWithEditableCell => {
|
||||||
|
const memoizedTransformData = getMemoizedTransformData();
|
||||||
|
const memoiseInjectEditableCellToTableData =
|
||||||
|
getMemoiseInjectEditableCellToTableData();
|
||||||
|
return memoizeOne((editableCell, tableData, columns) => {
|
||||||
|
const transformedData = memoizedTransformData(tableData, columns);
|
||||||
|
return memoiseInjectEditableCellToTableData(
|
||||||
|
transformedData,
|
||||||
|
editableCell,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -14943,9 +14943,10 @@ memfs@^3.2.2:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz"
|
resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz"
|
||||||
|
|
||||||
memoize-one@^5.2.1:
|
memoize-one@^6.0.0:
|
||||||
version "5.2.1"
|
version "6.0.0"
|
||||||
resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz"
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
|
||||||
|
integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
|
||||||
|
|
||||||
memoizerific@^1.11.3:
|
memoizerific@^1.11.3:
|
||||||
version "1.11.3"
|
version "1.11.3"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user