PromucFlow_constructor/app/client/src/WidgetProvider/factory/index.tsx
Ashit Rath 88d3599bc1
chore: Split canvas widget reducers (#39327)
## Description
Split canvasWidgetsReducer and canvasWidgetsStructureReducer for UI
modules

Fixes https://github.com/appsmithorg/appsmith/issues/39326

## Automation

/ok-to-test tags="@tag.All"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/13385385883>
> Commit: ec13bb0625735d4a0c1b918fd785b3a5ea858245
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=13385385883&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Tue, 18 Feb 2025 08:41:34 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Refactor**
- Streamlined internal dependency management and reorganized module
paths for improved maintainability.
- Updated import paths for `CanvasWidgetsReduxState`,
`FlattenedWidgetProps`, and related types to reflect a new
organizational structure.
- These behind-the-scenes changes do not affect any user-visible
functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-02-18 16:12:05 +05:30

696 lines
19 KiB
TypeScript

import type {
PropertyPaneConfig,
PropertyPaneSectionConfig,
} from "constants/PropertyControlConstants";
import type { WidgetProps } from "widgets/BaseWidget";
import type { RenderMode } from "constants/WidgetConstants";
import * as log from "loglevel";
import type {
AnvilConfig,
AutocompletionDefinitions,
AutoLayoutConfig,
CanvasWidgetStructure,
FlattenedWidgetProps,
WidgetConfigProps,
WidgetDefaultProps,
WidgetMethods,
} from "WidgetProvider/constants";
import {
addPropertyConfigIds,
addSearchConfigToPanelConfig,
convertFunctionsToString,
enhancePropertyPaneConfig,
generatePropertyPaneSearchConfig,
getDefaultOnCanvasUIConfig,
PropertyPaneConfigTypes,
} from "./helpers";
import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants";
import type BaseWidget from "widgets/BaseWidget";
import { flow } from "lodash";
import { generateReactKey } from "utils/generators";
import {
WidgetFeaturePropertyEnhancements,
WidgetFeatureProps,
} from "../../utils/WidgetFeatures";
import type { RegisteredWidgetFeatures } from "../../utils/WidgetFeatures";
import type { SetterConfig } from "entities/AppTheming";
import { freeze, memoize } from "./decorators";
import { create } from "mutative";
import type { CanvasWidgetsReduxState } from "ee/reducers/entityReducers/canvasWidgetsReducer";
import type {
CopiedWidgetData,
PasteDestinationInfo,
} from "layoutSystems/anvil/utils/paste/types";
import { call } from "redux-saga/effects";
import type { DerivedPropertiesMap } from "./types";
// exporting it as well so that existing imports are not affected
// TODO: remove this once all imports are updated
export type { DerivedPropertiesMap };
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type WidgetDerivedPropertyType = any;
export type WidgetType = (typeof WidgetFactory.widgetTypes)[number];
class WidgetFactory {
static widgetTypes: Record<string, string> = {};
static widgetConfigMap: Map<
WidgetType,
Partial<WidgetProps> & WidgetConfigProps & { type: string }
> = new Map();
static widgetDefaultPropertiesMap: Map<string, Record<string, unknown>> =
new Map();
static widgetsMap: Map<WidgetType, typeof BaseWidget> = new Map();
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static widgetBuilderMap: Map<WidgetType, any> = new Map();
static initialize(
widgets: [
typeof BaseWidget,
(widgetProps: CanvasWidgetStructure) => React.ReactNode,
][],
) {
const start = performance.now();
for (const [widget, builder] of widgets) {
WidgetFactory.widgetsMap.set(widget.type, widget);
WidgetFactory.widgetTypes[widget.type] = widget.type;
WidgetFactory.widgetBuilderMap.set(widget.type, builder);
WidgetFactory.configureWidget(widget);
}
log.debug("Widget registration took: ", performance.now() - start, "ms");
}
private static configureWidget(widget: typeof BaseWidget) {
const defaultConfig: WidgetDefaultProps = widget.getDefaults();
const config = widget.getConfig();
/**
* As this will make all layout system widgets have these properties.
* We're going to prioritise #21825.
* This will prevent the DSLs which are persisted from being polluted and overly large.
*
* The following makes sure that the on canvas ui configurations are picked up from widgets
* and added to the WidgetFactory, such that these are accessible when needed in the applcation.
*/
const onCanvasUI =
config.onCanvasUI || getDefaultOnCanvasUIConfig(defaultConfig);
const { IconCmp } = widget.getMethods();
const features = widget.getFeatures();
let enhancedFeatures: Record<string, unknown> = {};
if (features) {
Object.keys(features).forEach((registeredFeature: string) => {
enhancedFeatures = Object.assign(
{},
WidgetFeatureProps[registeredFeature as RegisteredWidgetFeatures],
WidgetFeaturePropertyEnhancements[
registeredFeature as RegisteredWidgetFeatures
](widget),
);
});
}
const _config = {
type: widget.type,
...widget.getDefaults(),
...enhancedFeatures,
searchTags: config.searchTags,
tags: config.tags,
hideCard: !!config.hideCard || !(config.iconSVG || IconCmp),
isDeprecated: !!config.isDeprecated,
replacement: config.replacement,
displayName: config.name,
displayOrder: config.displayOrder,
key: generateReactKey(),
iconSVG: config.iconSVG,
thumbnailSVG: config.thumbnailSVG,
isCanvas: config.isCanvas,
needsHeightForContent: config.needsHeightForContent,
isSearchWildcard: config.isSearchWildcard,
needsErrorInfo: !!config.needsErrorInfo,
onCanvasUI,
};
// When adding widgets to canvas in Anvil, we don't need all of configured properties
// (See _config object)
// and that should ideally be the case for Fixed mode widgets as well
// So, creating this map to use in WidgetAdditionSagas for both Fixed
// and Anvil.
// Before this we were using "ALL" configured properties when creating
// the newly added widget. This lead to many extra properties being added
// to the DSL
WidgetFactory.widgetDefaultPropertiesMap.set(
widget.type,
Object.freeze({ ...defaultConfig }),
);
WidgetFactory.widgetConfigMap.set(widget.type, Object.freeze(_config));
}
static get(type: WidgetType) {
const widget = WidgetFactory.widgetsMap.get(type);
if (widget) {
return widget;
} else {
log.error(`Widget is not defined with type: ${type}`);
return;
}
}
static getConfig(type: WidgetType) {
const config = WidgetFactory.widgetConfigMap.get(type);
if (config) {
return config;
} else {
log.error(`Widget config is not registered for type: ${type}`);
return;
}
}
static getConfigs = () => {
return Object.fromEntries(WidgetFactory.widgetConfigMap);
};
static createWidget(
widgetData: CanvasWidgetStructure,
renderMode: RenderMode,
): React.ReactNode {
const { type } = widgetData;
const builder = WidgetFactory.widgetBuilderMap.get(type);
if (builder) {
const widgetProps = {
key: widgetData.widgetId,
isVisible: true,
...widgetData,
renderMode,
};
return builder(widgetProps);
} else {
const ex: WidgetCreationException = {
message:
"Widget Builder not registered for widget type" + widgetData.type,
};
log.error(ex);
return null;
}
}
@memoize
@freeze
static getWidgetTypes(): WidgetType[] {
return Array.from(WidgetFactory.widgetsMap.keys());
}
@memoize
@freeze
static getWidgetDerivedPropertiesMap(
widgetType: WidgetType,
): DerivedPropertiesMap {
const widget = WidgetFactory.widgetsMap.get(widgetType);
const derivedProperties = widget?.getDerivedPropertiesMap();
if (derivedProperties) {
return derivedProperties;
} else {
log.error(
`Derived properties are not defined for widget type: ${widgetType}`,
);
return {};
}
}
@memoize
@freeze
static getWidgetDefaultPropertiesMap(
widgetType: WidgetType,
): Record<string, string> {
const widget = WidgetFactory.widgetsMap.get(widgetType);
const defaultProperties = widget?.getDefaultPropertiesMap();
if (defaultProperties) {
return defaultProperties;
} else {
log.error(
`Default properties are not defined for widget type: ${widgetType}`,
);
return {};
}
}
@memoize
@freeze
static getWidgetDependencyMap(
widgetType: WidgetType,
): Record<string, string[]> {
const widget = WidgetFactory.widgetsMap.get(widgetType);
const dependencyMap = widget?.getDependencyMap();
if (dependencyMap) {
return dependencyMap;
} else {
log.error(`Dependency map is defined for widget type: ${widgetType}`);
return {};
}
}
@memoize
@freeze
static getWidgetMetaPropertiesMap(
widgetType: WidgetType,
): Record<string, unknown> {
const widget = WidgetFactory.widgetsMap.get(widgetType);
const metaProperties = widget?.getMetaPropertiesMap();
if (metaProperties) {
return metaProperties;
} else {
log.error(
`Meta properties are not defined for widget type: ${widgetType}`,
);
return {};
}
}
@memoize
@freeze
static getWidgetPropertyPaneCombinedConfig(
type: WidgetType,
widgetProperties: WidgetProps,
): readonly PropertyPaneConfig[] {
const contentConfig = WidgetFactory.getWidgetPropertyPaneContentConfig(
type,
widgetProperties,
);
const styleConfig = WidgetFactory.getWidgetPropertyPaneStyleConfig(type);
return [...contentConfig, ...styleConfig];
}
@memoize
@freeze
private static getWidgetPropertyPaneConfigWithMemo(type: WidgetType) {
const widget = WidgetFactory.widgetsMap.get(type);
const propertyPaneConfig = widget?.getPropertyPaneConfig();
const features = widget?.getFeatures();
if (Array.isArray(propertyPaneConfig) && propertyPaneConfig.length > 0) {
const enhance = flow([
enhancePropertyPaneConfig,
convertFunctionsToString,
addPropertyConfigIds,
]);
const enhancedPropertyPaneConfig = enhance(propertyPaneConfig, features);
return enhancedPropertyPaneConfig;
}
}
@memoize
static getWidgetPropertyPaneConfig(
type: WidgetType,
widgetProperties: WidgetProps,
): readonly PropertyPaneConfig[] {
const propertyPaneConfig =
WidgetFactory.getWidgetPropertyPaneConfigWithMemo(type);
if (Array.isArray(propertyPaneConfig) && propertyPaneConfig.length > 0) {
return propertyPaneConfig;
} else {
const config = WidgetFactory.getWidgetPropertyPaneCombinedConfig(
type,
widgetProperties,
);
if (config === undefined) {
log.error("Widget property pane config not defined", type);
return [];
} else {
return config;
}
}
}
@memoize
private static getWidgetPropertyPaneContentConfigWithDynamicPropertyGenerator(
type: WidgetType,
) {
const widget = WidgetFactory.widgetsMap.get(type);
const propertyPaneContentConfig = widget?.getPropertyPaneContentConfig();
const features = widget?.getFeatures();
if (propertyPaneContentConfig) {
const enhance = flow([
enhancePropertyPaneConfig,
convertFunctionsToString,
addPropertyConfigIds,
addSearchConfigToPanelConfig,
]);
const enhancedPropertyPaneContentConfig = enhance(
propertyPaneContentConfig,
features,
PropertyPaneConfigTypes.CONTENT,
type,
);
return enhancedPropertyPaneContentConfig;
} else {
return [];
}
}
@memoize
@freeze
static getWidgetPropertyPaneContentConfig(
type: WidgetType,
widgetProperties: WidgetProps,
): readonly PropertyPaneConfig[] {
const propertyPaneContentConfigWithDynamicPropertyGenerator: PropertyPaneSectionConfig[] =
WidgetFactory.getWidgetPropertyPaneContentConfigWithDynamicPropertyGenerator(
type,
);
if (
propertyPaneContentConfigWithDynamicPropertyGenerator.some(
(d) => d.hasDynamicProperties,
)
) {
return propertyPaneContentConfigWithDynamicPropertyGenerator.map(
(section: PropertyPaneSectionConfig) => {
if (section.hasDynamicProperties) {
const dynamicProperties =
section.generateDynamicProperties?.(widgetProperties);
if (dynamicProperties && dynamicProperties.length) {
addPropertyConfigIds(dynamicProperties, false);
section = create(section, (draft) => {
draft.children = [...dynamicProperties, ...section.children];
});
}
}
return section;
},
);
} else {
return propertyPaneContentConfigWithDynamicPropertyGenerator;
}
}
@memoize
@freeze
static getWidgetPropertyPaneStyleConfig(
type: WidgetType,
): readonly PropertyPaneConfig[] {
const widget = WidgetFactory.widgetsMap.get(type);
const propertyPaneStyleConfig = widget?.getPropertyPaneStyleConfig();
const features = widget?.getFeatures();
if (propertyPaneStyleConfig) {
const enhance = flow([
enhancePropertyPaneConfig,
convertFunctionsToString,
addPropertyConfigIds,
addSearchConfigToPanelConfig,
]);
const enhancedPropertyPaneConfig = enhance(
propertyPaneStyleConfig,
features,
PropertyPaneConfigTypes.STYLE,
);
return enhancedPropertyPaneConfig;
} else {
return [];
}
}
@memoize
@freeze
static getWidgetPropertyPaneSearchConfig(
type: WidgetType,
widgetProperties: WidgetProps,
): readonly PropertyPaneConfig[] {
const config = generatePropertyPaneSearchConfig(
WidgetFactory.getWidgetPropertyPaneContentConfig(type, widgetProperties),
WidgetFactory.getWidgetPropertyPaneStyleConfig(type),
);
if (config) {
return config;
} else {
return [];
}
}
@memoize
@freeze
static getWidgetAutoLayoutConfig(type: WidgetType): AutoLayoutConfig {
// we don't need AutoLayoutConfig config for WDS widgets
if (type?.includes("WDS")) return {};
const widget = WidgetFactory.widgetsMap.get(type);
const baseAutoLayoutConfig = widget?.getAutoLayoutConfig();
if (baseAutoLayoutConfig) {
return {
...baseAutoLayoutConfig,
widgetSize:
baseAutoLayoutConfig.widgetSize?.map((sizeConfig) => ({
...sizeConfig,
configuration: (props: WidgetProps) => {
if (!props)
return {
minWidth:
WidgetFactory.widgetConfigMap.get(type)?.minWidth ||
FILL_WIDGET_MIN_WIDTH,
minHeight:
WidgetFactory.widgetConfigMap.get(type)?.minHeight || 80,
};
return sizeConfig.configuration(props);
},
})) || [],
autoDimension: baseAutoLayoutConfig.autoDimension ?? {},
disabledPropsDefaults: baseAutoLayoutConfig.disabledPropsDefaults ?? {},
};
} else {
log.error(`Auto layout config is not defined for widget type: ${type}`);
return {
autoDimension: {},
widgetSize: [],
disableResizeHandles: {},
disabledPropsDefaults: {},
};
}
}
@memoize
@freeze
static getWidgetAnvilConfig(type: WidgetType): AnvilConfig {
const widget = WidgetFactory.widgetsMap.get(type);
const baseAnvilConfig: AnvilConfig | null | undefined =
widget?.getAnvilConfig();
if (!baseAnvilConfig) {
log.error(`Anvil config is not defined for widget type: ${type}`);
return {
isLargeWidget: false,
widgetSize: {},
};
}
return baseAnvilConfig;
}
@memoize
@freeze
static getWidgetTypeConfigMap(): WidgetTypeConfigMap {
const typeConfigMap: WidgetTypeConfigMap = {};
WidgetFactory.getWidgetTypes().forEach((type) => {
typeConfigMap[type] = {
defaultProperties: WidgetFactory.getWidgetDefaultPropertiesMap(type),
derivedProperties: WidgetFactory.getWidgetDerivedPropertiesMap(type),
metaProperties: WidgetFactory.getWidgetMetaPropertiesMap(type),
};
});
return typeConfigMap;
}
static getAutocompleteDefinitions(
type: WidgetType,
): AutocompletionDefinitions {
const widget = WidgetFactory.widgetsMap.get(type);
const autocompleteDefinition = widget?.getAutocompleteDefinitions();
if (autocompleteDefinition) {
return autocompleteDefinition;
} else {
log.error(
`Auto complete definitions are not defined for widget type: ${type}`,
);
return {};
}
}
@memoize
@freeze
static getWidgetSetterConfig(type: WidgetType): Partial<SetterConfig> {
const widget = WidgetFactory.widgetsMap.get(type);
const setterConfig = widget?.getSetterConfig() || {};
return setterConfig;
}
@memoize
@freeze
static getLoadingProperties(type: WidgetType): Array<RegExp> | undefined {
const widget = WidgetFactory.widgetsMap.get(type);
return widget?.getLoadingProperties();
}
@memoize
@freeze
static getWidgetStylesheetConfigMap(widgetType: WidgetType) {
const widget = WidgetFactory.widgetsMap.get(widgetType);
const stylesheet = widget?.getStylesheetConfig();
if (stylesheet) {
return stylesheet;
} else {
log.error(
`stylesheet config is not defined for widget type: ${widgetType}`,
);
return undefined;
}
}
@memoize
static getWidgetMethods(type: WidgetType): WidgetMethods {
const widget = WidgetFactory.widgetsMap.get(type);
const methods = widget?.getMethods();
if (methods) {
return methods;
} else {
return {};
}
}
@memoize
static performPasteOperationChecks(
allWidgets: CanvasWidgetsReduxState,
oldWidget: FlattenedWidgetProps,
newWidget: FlattenedWidgetProps,
widgetIdMap: Record<string, string>,
): FlattenedWidgetProps {
const widget = WidgetFactory.widgetsMap.get(newWidget.type);
if (!widget) return newWidget;
const widgetProps: FlattenedWidgetProps | null =
widget?.pasteOperationChecks(
allWidgets,
oldWidget,
newWidget,
widgetIdMap,
);
return widgetProps !== null ? widgetProps : newWidget;
}
@memoize
static *performPasteOperation(
allWidgets: CanvasWidgetsReduxState, // All widgets
copiedWidgets: CopiedWidgetData[], // Original copied widgets
destinationInfo: PasteDestinationInfo, // Destination info of copied widgets
widgetIdMap: Record<string, string>, // Map of oldWidgetId -> newWidgetId
reverseWidgetIdMap: Record<string, string>, // Map of newWidgetId -> oldWidgetId
) {
const { parentOrder } = destinationInfo;
const parent: FlattenedWidgetProps =
allWidgets[parentOrder[parentOrder.length - 1]];
const widget = WidgetFactory.widgetsMap.get(parent.type);
if (!widget) return allWidgets;
const res: CanvasWidgetsReduxState = yield call(
widget?.performPasteOperation,
allWidgets,
copiedWidgets,
destinationInfo,
widgetIdMap,
reverseWidgetIdMap,
);
return res;
}
}
export type WidgetTypeConfigMap = Record<
string,
{
defaultProperties: Record<string, string>;
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
metaProperties: Record<string, any>;
derivedProperties: WidgetDerivedPropertyType;
}
>;
export interface WidgetCreationException {
message: string;
}
export default WidgetFactory;