## 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>
256 lines
7.6 KiB
TypeScript
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,
|
|
);
|