PromucFlow_constructor/app/client/src/selectors/widgetSelectors.ts
Abhinav Jha e67f6a8f89
feat: WDS - Anvil compatible Modal Widget (#30351)
## Description
This PR primarily adds the modal widget to WDS.
The following changes were made:
1. WidgetNameCanvas now listens to modal body scrolls to position widget
name components correctly
2. Modal Widget is rendered as a detached widget that is outside of the
layout flow of the main canvas
3. Main container resizer now has a higher z-index to show even if the
modal is open in the preview mode
4. Widget selection flow in Anvil layout system has been modified to be
handled in a central location (`AnvilMainCanvas`)
5. Modal widget's type in modal sagas are selected via a selector that
checks for the feature flag.
6. Modal widget has its own preset that (at present) is similar to the
Main container's layout preset


#### PR fixes following issue(s)
Fixes #28588 
Fixes #28328 
Fixes #27459 

#### Media


https://github.com/appsmithorg/appsmith/assets/103687/bf350be4-2202-49f3-a860-3e38681ab32e


#### Type of change

- New feature (non-breaking change which adds functionality)
- This change requires a documentation update

## Testing
>
#### How Has This Been Tested?
- [x] Manual
- [ ] JUnit
- [ ] Jest
- [ ] Cypress

## Checklist:
#### Dev activity
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-)
have been covered
- [ ] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [ ] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [ ] Cypress test cases have been added and approved by SDET/manual QA
- [ ] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed


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

- **New Features**
- Enhanced Modal components with additional styling and customization
options.
  - Introduced a new event type for modal submission actions.
- Added a `--on-canvas-ui-z-index` CSS variable for improved layering
control.
  - Implemented a new method for widget focus management in the editor.

- **Improvements**
  - Modal components now use context hooks for close actions.
  - Improved the handling of detached widgets in various layout systems.
  - Simplified the drag-and-drop state management for widgets.
  - Upgraded the visual presentation of the widget drop area.
  - Enhanced widget selection with new custom event dispatching.
- Updated the modal widget configuration with default settings and
property pane structure.

- **Bug Fixes**
- Fixed an issue with modal scrolling to behave consistently with the
main container.
- Addressed a problem where the `id` was not found during layout element
position updates.

- **Style**
- Adjusted modal overlay positioning and content width with new CSS
standards.
- Updated zIndex references to use CSS variables for consistent styling.

- **Refactor**
- Reorganized the `Widgets` array into categorized groups for better
clarity.
- Simplified the `modalPreset` function's parameters and layout
declaration.

- **Documentation**
  - Added default values for feature flags in the documentation.

- **Chores**
  - Altered feature flags to enable new functionalities by default.

- **Tests**
  - No visible changes to end-users in this category.

- **Revert**
  - No visible changes to end-users in this category.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Preet Sidhu <preetsidhu.bits@gmail.com>
Co-authored-by: Valera Melnikov <valera@appsmith.com>
2024-01-26 09:30:57 +05:30

256 lines
7.6 KiB
TypeScript

import { createSelector } from "reselect";
import type { AppState } from "@appsmith/reducers";
import type {
CanvasWidgetsReduxState,
FlattenedWidgetProps,
} from "reducers/entityReducers/canvasWidgetsReducer";
import { getExistingWidgetNames } from "sagas/selectors";
import { getNextEntityName } from "utils/AppsmithUtils";
import WidgetFactory from "WidgetProvider/factory";
import {
getFocusedWidget,
getLastSelectedWidget,
getSelectedWidgets,
} from "./ui";
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
import { get } from "lodash";
import { getAppMode } from "@appsmith/selectors/applicationSelectors";
import { APP_MODE } from "entities/App";
import { getIsTableFilterPaneVisible } from "selectors/tableFilterSelectors";
import { getIsAutoHeightWithLimitsChanging } from "utils/hooks/autoHeightUIHooks";
import { getIsPropertyPaneVisible } from "./propertyPaneSelectors";
import { combinedPreviewModeSelector } from "./editorSelectors";
import { selectFeatureFlags } from "@appsmith/selectors/featureFlagsSelectors";
export const getIsDraggingOrResizing = (state: AppState) =>
state.ui.widgetDragResize.isResizing || state.ui.widgetDragResize.isDragging;
export const getIsResizing = (state: AppState) =>
state.ui.widgetDragResize.isResizing;
const getCanvasWidgets = (state: AppState) => state.entities.canvasWidgets;
// A selector that gets the modal widget type based on the feature flag
// This will need to be updated once Anvil and WDS are generally available
export const getModalWidgetType = createSelector(
selectFeatureFlags,
(flags) => {
let modalWidgetType = "MODAL_WIDGET";
if (flags.ab_wds_enabled) {
modalWidgetType = "WDS_MODAL_WIDGET";
}
return modalWidgetType;
},
);
export const getModalWidgets = createSelector(
getCanvasWidgets,
getModalWidgetType,
(widgets, modalWidgetType) => {
const modalWidgets = Object.values(widgets).filter(
(widget: FlattenedWidgetProps) => widget.type === modalWidgetType,
);
if (modalWidgets.length === 0) return undefined;
return modalWidgets;
},
);
export const getModalDropdownList = createSelector(
getModalWidgets,
(modalWidgets) => {
if (!modalWidgets) return undefined;
return modalWidgets.map((widget: FlattenedWidgetProps) => ({
id: widget.widgetId,
label: widget.widgetName,
value: `${widget.widgetName}`,
}));
},
);
export const getNextModalName = createSelector(
getExistingWidgetNames,
getModalWidgetType,
(names, modalWidgetType) => {
const prefix =
WidgetFactory.widgetConfigMap.get(modalWidgetType)?.widgetName || "";
return getNextEntityName(prefix, names);
},
);
/**
* Selector to get the parent widget of a particaular widget with id as a prop
*/
export const getParentWidget = createSelector(
getCanvasWidgets,
(state: AppState, widgetId: string) => widgetId,
(canvasWidgets, widgetId: string): FlattenedWidgetProps | undefined => {
if (canvasWidgets.hasOwnProperty(widgetId)) {
const widget = canvasWidgets[widgetId];
if (widget.parentId && canvasWidgets.hasOwnProperty(widget.parentId)) {
const parent = canvasWidgets[widget.parentId];
return parent;
}
}
return;
},
);
export const getFocusedParentToOpen = createSelector(
getCanvasWidgets,
(state: AppState) => state.ui.widgetDragResize.focusedWidget,
(canvasWidgets, focusedWidgetId) => {
return getParentToOpenIfAny(focusedWidgetId, canvasWidgets);
},
);
export const getParentToOpenSelector = (widgetId: string) => {
return createSelector(getCanvasWidgets, (canvasWidgets) => {
return getParentToOpenIfAny(widgetId, canvasWidgets);
});
};
// Check if widget is in the list of selected widgets
export const isWidgetSelected = (widgetId: string) => {
return createSelector(getSelectedWidgets, (widgets): boolean =>
widgets.includes(widgetId),
);
};
export const isCurrentWidgetFocused = (widgetId: string) => {
return createSelector(
getFocusedWidget,
(widget): boolean => widget === widgetId,
);
};
// Check if current widget is the last selected widget
export const isCurrentWidgetLastSelected = (widgetId: string) => {
return createSelector(
getLastSelectedWidget,
(widget): boolean => widget === widgetId,
);
};
// Check if current widget is one of multiple selected widgets
export const isMultiSelectedWidget = (widgetId: string) => {
return createSelector(
getSelectedWidgets,
(widgets): boolean => widgets.length > 1 && widgets.includes(widgetId),
);
};
export function getParentToOpenIfAny(
widgetId: string | undefined,
widgets: CanvasWidgetsReduxState,
) {
if (widgetId) {
let widget = get(widgets, widgetId, undefined);
// While this widget has a openParentPropertyPane equal to true
while (widget?.openParentPropertyPane) {
// Get parent widget props
const parent = get(widgets, `${widget.parentId}`, undefined);
// If parent has openParentPropertyPane = false, return the current parent
if (!parent?.openParentPropertyPane) {
return parent;
}
if (parent?.parentId && parent.parentId !== MAIN_CONTAINER_WIDGET_ID) {
widget = get(widgets, `${widget.parentId}`, undefined);
continue;
}
}
}
return;
}
export const shouldWidgetIgnoreClicksSelector = (widgetId: string) => {
return createSelector(
getFocusedWidget,
getIsTableFilterPaneVisible,
(state: AppState) => state.ui.widgetDragResize.isResizing,
(state: AppState) => state.ui.widgetDragResize.isDragging,
(state: AppState) => state.ui.canvasSelection.isDraggingForSelection,
getAppMode,
combinedPreviewModeSelector,
getIsAutoHeightWithLimitsChanging,
(
focusedWidgetId,
isTableFilterPaneVisible,
isResizing,
isDragging,
isDraggingForSelection,
appMode,
isPreviewMode,
isAutoHeightWithLimitsChanging,
) => {
const isFocused = focusedWidgetId === widgetId;
return (
isDraggingForSelection ||
isResizing ||
isDragging ||
isPreviewMode ||
appMode !== APP_MODE.EDIT ||
!isFocused ||
isTableFilterPaneVisible ||
isAutoHeightWithLimitsChanging
);
},
);
};
export const getSelectedWidgetAncestry = (state: AppState) =>
state.ui.widgetDragResize.selectedWidgetAncestry;
export const getEntityExplorerWidgetAncestry = (state: AppState) =>
state.ui.widgetDragResize.entityExplorerAncestry;
export const getEntityExplorerWidgetsToExpand = createSelector(
getEntityExplorerWidgetAncestry,
(selectedWidgetAncestry: string[]) => {
return selectedWidgetAncestry.slice(1);
},
);
export const showWidgetAsSelected = (widgetId: string) => {
return createSelector(
getLastSelectedWidget,
getSelectedWidgets,
(lastSelectedWidgetId, selectedWidgets) => {
return (
lastSelectedWidgetId === widgetId ||
(selectedWidgets.length > 1 && selectedWidgets.includes(widgetId))
);
},
);
};
export const getFirstSelectedWidgetInList = createSelector(
getSelectedWidgets,
(selectedWidgets) => {
return selectedWidgets?.length ? selectedWidgets[0] : undefined;
},
);
export const isCurrentWidgetActiveInPropertyPane = (widgetId: string) => {
return createSelector(
getIsPropertyPaneVisible,
getFirstSelectedWidgetInList,
(isPaneVisible, firstSelectedWidgetId) => {
return isPaneVisible && firstSelectedWidgetId === widgetId;
},
);
};
export const isResizingOrDragging = createSelector(
(state: AppState) => state.ui.widgetDragResize.isResizing,
(state: AppState) => state.ui.widgetDragResize.isDragging,
(isResizing, isDragging) => !!isResizing || !!isDragging,
);