perf: Widget re-rendering refactor (#14485)
* initial commit * props hoc * changes * removed ignores and withWidgetProps * added extra props to canvasStructure * widget props changes * list widget changes * reintroduced widget props hook and other refactors * remove warnings * added deepequal for childWidgets selector * fix global hotkeys and tabs widget jest test * fix main container test fix * fixed view mode width * fix form widget values * minor fix * fix skeleton * form widget validity fix * jest test fix * fixed tests: GlobalHotkeys, Tabs, CanvasSelectectionArena and fixed main container rendering * minor fix * minor comments * reverted commented code * simplified structure, selective redux state updates and other inconsistencies * fix junit test cases * stop form widget from force rendering children * fix test case * random commit to re run tests * update isFormValid prop only if it exists * detangling circular dependency * fixing cypress tests * cleaned up code * clean up man cnavas props and fix jest cases * fix rendering order of child widgets for canvas * fix dropdown reset spec * adding comments * cleaning up unwanted code * fix multiselect widget on deploy * adressing review comments * addressing minor review comment changes * destructuring modal widget child and fix test case * fix communityIssues cypress spec * rewrite isVisible logic to match previous behaviour * merging widget props with component props before checking isVisible * adressing review comments for modal widget's isVisible Co-authored-by: rahulramesha <rahul@appsmith.com>
This commit is contained in:
parent
9e3d95d216
commit
893fd34cdd
|
|
@ -278,8 +278,8 @@
|
|||
"parentRowSpace": 38,
|
||||
"leftColumn": 10,
|
||||
"rightColumn": 14,
|
||||
"topRow": 11,
|
||||
"bottomRow": 12,
|
||||
"topRow": 17,
|
||||
"bottomRow": 18,
|
||||
"parentId": "e3tq9qwta6",
|
||||
"widgetId": "ca22py6vlv"
|
||||
},
|
||||
|
|
@ -346,18 +346,18 @@
|
|||
"parentColumnSpace": 6.6856445312499995,
|
||||
"leftColumn": 2,
|
||||
"options": [
|
||||
{
|
||||
"label": "Blue",
|
||||
"value": "BLUE"
|
||||
},
|
||||
{
|
||||
"label": "Green",
|
||||
"value": "GREEN"
|
||||
},
|
||||
{
|
||||
"label": "Red",
|
||||
"value": "RED"
|
||||
}
|
||||
{
|
||||
"label": "Blue",
|
||||
"value": "BLUE"
|
||||
},
|
||||
{
|
||||
"label": "Green",
|
||||
"value": "GREEN"
|
||||
},
|
||||
{
|
||||
"label": "Red",
|
||||
"value": "RED"
|
||||
}
|
||||
],
|
||||
"isDisabled": false,
|
||||
"key": "vrhzyvir7s",
|
||||
|
|
@ -382,4 +382,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -311,10 +311,10 @@ describe("AForce - Community Issues page validations", function() {
|
|||
table.SelectTableRow(0);
|
||||
agHelper.AssertElementVisible(locator._widgetInDeployed("tabswidget"));
|
||||
agHelper
|
||||
.GetNClick(locator._inputWidgetv1InDeployed)
|
||||
.GetNClick(locator._inputWidgetv1InDeployed, 0, true, 0)
|
||||
.type("-updating title");
|
||||
agHelper
|
||||
.GetNClick(locator._textAreainputWidgetv1InDeployed)
|
||||
.GetNClick(locator._textAreainputWidgetv1InDeployed, 0, true, 0)
|
||||
.type("-updating desc");
|
||||
agHelper
|
||||
.GetNClick(locator._inputWidgetv1InDeployed, 1)
|
||||
|
|
@ -377,7 +377,7 @@ describe("AForce - Community Issues page validations", function() {
|
|||
agHelper.Sleep();
|
||||
cy.get(table._trashIcon)
|
||||
.closest("div")
|
||||
.click();
|
||||
.click({ force: true });
|
||||
agHelper.Sleep(3000); //allowing time to delete!
|
||||
agHelper.AssertElementAbsence(locator._widgetInDeployed("tabswidget"));
|
||||
table.WaitForTableEmpty();
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ describe("Undo/Redo functionality", function() {
|
|||
cy.get(commonlocators.toastmsg)
|
||||
.eq(1)
|
||||
.contains("UNDO");
|
||||
cy.deleteWidget(widgetsPage.textWidget);
|
||||
});
|
||||
|
||||
it("checks undo/redo for color picker", function() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const explorer = require("../../../../../locators/explorerlocators.json");
|
||||
const { modifierKey } = require("../../../../../support/Constants");
|
||||
|
||||
const firstButton = ".t--buttongroup-widget > div > button > div";
|
||||
const menuButton =
|
||||
|
|
@ -36,7 +37,7 @@ describe("Button Group Widget Functionality", function() {
|
|||
cy.get(firstButton).contains("Add");
|
||||
|
||||
// Undo
|
||||
cy.get("body").type("{ctrl+z}");
|
||||
cy.get("body").type(`{${modifierKey}+z}`);
|
||||
|
||||
// Check if the button is back
|
||||
cy.get(".t--buttongroup-widget")
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ describe("Binding the list widget with text widget", function() {
|
|||
.click({ force: true })
|
||||
.type("#$%1234", { delay: 300 })
|
||||
.type("{enter}");
|
||||
cy.wait(500);
|
||||
cy.get(".t--widget-name").contains("___1234");
|
||||
cy.verifyUpdatedWidgetName("12345");
|
||||
cy.get(".t--delete-widget").click({ force: true });
|
||||
|
|
|
|||
1
app/client/cypress/support/Constants.js
Normal file
1
app/client/cypress/support/Constants.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const modifierKey = Cypress.platform === "darwin" ? "meta" : "ctrl";
|
||||
|
|
@ -245,6 +245,7 @@ Cypress.Commands.add("verifyUpdatedWidgetName", (text) => {
|
|||
.click({ force: true })
|
||||
.type(text, { delay: 300 })
|
||||
.type("{enter}");
|
||||
cy.wait(500);
|
||||
cy.get(".t--widget-name").contains(text);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,12 @@ export interface CreatePageActionPayload {
|
|||
blockNavigation?: boolean;
|
||||
}
|
||||
|
||||
export type updateLayoutOptions = {
|
||||
isRetry?: boolean;
|
||||
shouldReplay?: boolean;
|
||||
updatedWidgetIds?: string[];
|
||||
};
|
||||
|
||||
export const fetchPage = (
|
||||
pageId: string,
|
||||
isFirstLoad = false,
|
||||
|
|
@ -130,12 +136,12 @@ export const deletePageSuccess = () => {
|
|||
|
||||
export const updateAndSaveLayout = (
|
||||
widgets: CanvasWidgetsReduxState,
|
||||
isRetry?: boolean,
|
||||
shouldReplay?: boolean,
|
||||
options: updateLayoutOptions = {},
|
||||
) => {
|
||||
const { isRetry, shouldReplay, updatedWidgetIds } = options;
|
||||
return {
|
||||
type: ReduxActionTypes.UPDATE_LAYOUT,
|
||||
payload: { widgets, isRetry, shouldReplay },
|
||||
payload: { widgets, isRetry, shouldReplay, updatedWidgetIds },
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -902,6 +902,7 @@ export interface UpdateCanvasPayload {
|
|||
currentPageName: string;
|
||||
currentApplicationId: string;
|
||||
pageActions: PageAction[][];
|
||||
updatedWidgetIds?: string[];
|
||||
}
|
||||
|
||||
export interface ShowPropertyPanePayload {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { usePositionedContainerZIndex } from "utils/hooks/usePositionedContainer
|
|||
import { useSelector } from "react-redux";
|
||||
import { snipingModeSelector } from "selectors/editorSelectors";
|
||||
import WidgetFactory from "utils/WidgetFactory";
|
||||
import { isEqual, memoize } from "lodash";
|
||||
import { memoize } from "lodash";
|
||||
import { getReflowSelector } from "selectors/widgetReflowSelectors";
|
||||
import { AppState } from "reducers";
|
||||
import { POSITIONED_WIDGET } from "constants/componentClassNameConstants";
|
||||
|
|
@ -59,7 +59,7 @@ export function PositionedContainer(props: PositionedContainerProps) {
|
|||
|
||||
const reflowSelector = getReflowSelector(props.widgetId);
|
||||
|
||||
const reflowedPosition = useSelector(reflowSelector, isEqual);
|
||||
const reflowedPosition = useSelector(reflowSelector);
|
||||
const dragDetails = useSelector(
|
||||
(state: AppState) => state.ui.widgetDragResize.dragDetails,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -37,12 +37,11 @@ import {
|
|||
snipingModeSelector,
|
||||
} from "selectors/editorSelectors";
|
||||
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
|
||||
import { getCanvasWidgets } from "selectors/entitiesSelector";
|
||||
import { focusWidget } from "actions/widgetActions";
|
||||
import { getParentToOpenIfAny } from "utils/hooks/useClickToSelectWidget";
|
||||
import { GridDefaults } from "constants/WidgetConstants";
|
||||
import { DropTargetContext } from "./DropTargetComponent";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useCanvasDragging";
|
||||
import { getParentToOpenSelector } from "selectors/widgetSelectors";
|
||||
|
||||
export type ResizableComponentProps = WidgetProps & {
|
||||
paddingOffset: number;
|
||||
|
|
@ -53,7 +52,6 @@ export const ResizableComponent = memo(function ResizableComponent(
|
|||
) {
|
||||
// Fetch information from the context
|
||||
const { updateWidget } = useContext(EditorContext);
|
||||
const canvasWidgets = useSelector(getCanvasWidgets);
|
||||
|
||||
const isSnipingMode = useSelector(snipingModeSelector);
|
||||
const isPreviewMode = useSelector(previewModeSelector);
|
||||
|
|
@ -78,9 +76,8 @@ export const ResizableComponent = memo(function ResizableComponent(
|
|||
const isResizing = useSelector(
|
||||
(state: AppState) => state.ui.widgetDragResize.isResizing,
|
||||
);
|
||||
const parentWidgetToSelect = getParentToOpenIfAny(
|
||||
props.widgetId,
|
||||
canvasWidgets,
|
||||
const parentWidgetToSelect = useSelector(
|
||||
getParentToOpenSelector(props.widgetId),
|
||||
);
|
||||
|
||||
const isWidgetFocused =
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import localStorage from "utils/localStorage";
|
||||
import { GridDefaults } from "./WidgetConstants";
|
||||
|
||||
export const CANVAS_DEFAULT_HEIGHT_PX = 1292;
|
||||
export const CANVAS_DEFAULT_MIN_HEIGHT_PX = 380;
|
||||
export const CANVAS_DEFAULT_GRID_HEIGHT_PX = 1;
|
||||
export const CANVAS_DEFAULT_GRID_WIDTH_PX = 1;
|
||||
export const CANVAS_DEFAULT_MIN_ROWS = Math.ceil(
|
||||
CANVAS_DEFAULT_MIN_HEIGHT_PX / GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
||||
);
|
||||
export const CANVAS_BACKGROUND_COLOR = "#FFFFFF";
|
||||
export const DEFAULT_ENTITY_EXPLORER_WIDTH = 256;
|
||||
export const DEFAULT_PROPERTY_PANE_WIDTH = 256;
|
||||
|
|
|
|||
|
|
@ -138,6 +138,15 @@ export const WIDGET_STATIC_PROPS = {
|
|||
noContainerOffset: false,
|
||||
};
|
||||
|
||||
export const WIDGET_DSL_STRUCTURE_PROPS = {
|
||||
children: true,
|
||||
type: true,
|
||||
widgetId: true,
|
||||
parentId: true,
|
||||
topRow: true,
|
||||
bottomRow: true,
|
||||
};
|
||||
|
||||
export type TextSize = keyof typeof TextSizes;
|
||||
|
||||
export const DEFAULT_FONT_SIZE = THEMEING_TEXT_SIZES.base;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import styled from "styled-components";
|
|||
import WidgetFactory from "utils/WidgetFactory";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { useDynamicAppLayout } from "utils/hooks/useDynamicAppLayout";
|
||||
import { DSLWidget } from "widgets/constants";
|
||||
import { CanvasWidgetStructure } from "widgets/constants";
|
||||
import { RenderModes } from "constants/WidgetConstants";
|
||||
|
||||
const PageView = styled.div<{ width: number }>`
|
||||
|
|
@ -14,10 +14,11 @@ const PageView = styled.div<{ width: number }>`
|
|||
`;
|
||||
|
||||
type AppPageProps = {
|
||||
dsl: DSLWidget;
|
||||
pageName?: string;
|
||||
pageId?: string;
|
||||
appName?: string;
|
||||
canvasWidth: number;
|
||||
pageId?: string;
|
||||
pageName?: string;
|
||||
widgetsStructure: CanvasWidgetStructure;
|
||||
};
|
||||
|
||||
export function AppPage(props: AppPageProps) {
|
||||
|
|
@ -33,9 +34,9 @@ export function AppPage(props: AppPageProps) {
|
|||
}, [props.pageId, props.pageName]);
|
||||
|
||||
return (
|
||||
<PageView className="t--app-viewer-page" width={props.dsl.rightColumn}>
|
||||
{props.dsl.widgetId &&
|
||||
WidgetFactory.createWidget(props.dsl, RenderModes.PAGE)}
|
||||
<PageView className="t--app-viewer-page" width={props.canvasWidth}>
|
||||
{props.widgetsStructure.widgetId &&
|
||||
WidgetFactory.createWidget(props.widgetsStructure, RenderModes.PAGE)}
|
||||
</PageView>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ import { theme } from "constants/DefaultTheme";
|
|||
import { Icon, NonIdealState, Spinner } from "@blueprintjs/core";
|
||||
import Centered from "components/designSystems/appsmith/CenteredWrapper";
|
||||
import AppPage from "./AppPage";
|
||||
import {
|
||||
getCanvasWidgetDsl,
|
||||
getCurrentPageName,
|
||||
} from "selectors/editorSelectors";
|
||||
import { getCanvasWidth, getCurrentPageName } from "selectors/editorSelectors";
|
||||
import RequestConfirmationModal from "pages/Editor/RequestConfirmationModal";
|
||||
import { getCurrentApplication } from "selectors/applicationSelectors";
|
||||
import {
|
||||
|
|
@ -19,6 +16,8 @@ import {
|
|||
PERMISSION_TYPE,
|
||||
} from "../Applications/permissionHelpers";
|
||||
import { builderURL } from "RouteBuilder";
|
||||
import { getCanvasWidgetsStructure } from "selectors/entitiesSelector";
|
||||
import { isEqual } from "lodash";
|
||||
|
||||
const Section = styled.section`
|
||||
height: 100%;
|
||||
|
|
@ -33,7 +32,8 @@ type AppViewerPageContainerProps = RouteComponentProps<AppViewerRouteParams>;
|
|||
|
||||
function AppViewerPageContainer(props: AppViewerPageContainerProps) {
|
||||
const currentPageName = useSelector(getCurrentPageName);
|
||||
const widgets = useSelector(getCanvasWidgetDsl);
|
||||
const widgetsStructure = useSelector(getCanvasWidgetsStructure, isEqual);
|
||||
const canvasWidth = useSelector(getCanvasWidth);
|
||||
const isFetchingPage = useSelector(getIsFetchingPage);
|
||||
const currentApplication = useSelector(getCurrentApplication);
|
||||
const { match } = props;
|
||||
|
|
@ -86,15 +86,17 @@ function AppViewerPageContainer(props: AppViewerPageContainerProps) {
|
|||
|
||||
if (isFetchingPage) return pageLoading;
|
||||
|
||||
if (!(widgets.children && widgets.children.length > 0)) return pageNotFound;
|
||||
if (!(widgetsStructure.children && widgetsStructure.children.length > 0))
|
||||
return pageNotFound;
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<AppPage
|
||||
appName={currentApplication?.name}
|
||||
dsl={widgets}
|
||||
canvasWidth={canvasWidth}
|
||||
pageId={match.params.pageId}
|
||||
pageName={currentPageName}
|
||||
widgetsStructure={widgetsStructure}
|
||||
/>
|
||||
<RequestConfirmationModal />
|
||||
</Section>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import log from "loglevel";
|
|||
import * as Sentry from "@sentry/react";
|
||||
import styled from "styled-components";
|
||||
import store, { useSelector } from "store";
|
||||
import { DSLWidget } from "widgets/constants";
|
||||
import { CanvasWidgetStructure } from "widgets/constants";
|
||||
import WidgetFactory from "utils/WidgetFactory";
|
||||
import React, { memo, useCallback, useEffect } from "react";
|
||||
|
||||
|
|
@ -22,8 +22,9 @@ import { getPageLevelSocketRoomId } from "sagas/WebsocketSagas/utils";
|
|||
import { previewModeSelector } from "selectors/editorSelectors";
|
||||
|
||||
interface CanvasProps {
|
||||
dsl: DSLWidget;
|
||||
widgetsStructure: CanvasWidgetStructure;
|
||||
pageId: string;
|
||||
canvasWidth: number;
|
||||
}
|
||||
|
||||
type PointerEventDataType = {
|
||||
|
|
@ -72,7 +73,7 @@ const useShareMousePointerEvent = () => {
|
|||
|
||||
// TODO(abhinav): get the render mode from context
|
||||
const Canvas = memo((props: CanvasProps) => {
|
||||
const { pageId } = props;
|
||||
const { canvasWidth, pageId } = props;
|
||||
const isPreviewMode = useSelector(previewModeSelector);
|
||||
const selectedTheme = useSelector(getSelectedAppTheme);
|
||||
|
||||
|
|
@ -118,11 +119,14 @@ const Canvas = memo((props: CanvasProps) => {
|
|||
!!data && delayedShareMousePointer(data);
|
||||
}}
|
||||
style={{
|
||||
width: props.dsl.rightColumn,
|
||||
width: canvasWidth,
|
||||
}}
|
||||
>
|
||||
{props.dsl.widgetId &&
|
||||
WidgetFactory.createWidget(props.dsl, RenderModes.CANVAS)}
|
||||
{props.widgetsStructure.widgetId &&
|
||||
WidgetFactory.createWidget(
|
||||
props.widgetsStructure,
|
||||
RenderModes.CANVAS,
|
||||
)}
|
||||
{isMultiplayerEnabledForUser && (
|
||||
<CanvasMultiPointerArena pageId={pageId} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -9,14 +9,19 @@ import { act, render, fireEvent, waitFor } from "test/testUtils";
|
|||
import GlobalHotKeys from "./GlobalHotKeys";
|
||||
import MainContainer from "../MainContainer";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import * as widgetRenderUtils from "utils/widgetRenderUtils";
|
||||
import * as utilities from "selectors/editorSelectors";
|
||||
import * as dataTreeSelectors from "selectors/dataTreeSelectors";
|
||||
import store from "store";
|
||||
import { sagasToRunForTests } from "test/sagas";
|
||||
import { all } from "@redux-saga/core/effects";
|
||||
import {
|
||||
dispatchTestKeyboardEventWithCode,
|
||||
MockApplication,
|
||||
mockCreateCanvasWidget,
|
||||
mockGetCanvasWidgetDsl,
|
||||
mockGetChildWidgets,
|
||||
mockGetWidgetEvalValues,
|
||||
MockPageDSL,
|
||||
useMockDsl,
|
||||
} from "test/testCommon";
|
||||
|
|
@ -40,6 +45,11 @@ jest.mock("constants/routes", () => {
|
|||
describe("Canvas Hot Keys", () => {
|
||||
const mockGetIsFetchingPage = jest.spyOn(utilities, "getIsFetchingPage");
|
||||
const spyGetCanvasWidgetDsl = jest.spyOn(utilities, "getCanvasWidgetDsl");
|
||||
const spyGetChildWidgets = jest.spyOn(utilities, "getChildWidgets");
|
||||
const spyCreateCanvasWidget = jest.spyOn(
|
||||
widgetRenderUtils,
|
||||
"createCanvasWidget",
|
||||
);
|
||||
|
||||
function UpdatedMainContainer({ dsl }: any) {
|
||||
useMockDsl(dsl);
|
||||
|
|
@ -68,6 +78,16 @@ describe("Canvas Hot Keys", () => {
|
|||
});
|
||||
|
||||
describe("Select all hotkey", () => {
|
||||
jest
|
||||
.spyOn(widgetRenderUtils, "createCanvasWidget")
|
||||
.mockImplementation(mockCreateCanvasWidget);
|
||||
jest
|
||||
.spyOn(dataTreeSelectors, "getWidgetEvalValues")
|
||||
.mockImplementation(mockGetWidgetEvalValues);
|
||||
jest
|
||||
.spyOn(utilities, "computeMainContainerWidget")
|
||||
.mockImplementation((widget) => widget as any);
|
||||
|
||||
it("Cmd + A - select all widgets on canvas", async () => {
|
||||
const children: any = buildChildren([
|
||||
{ type: "TABS_WIDGET", parentId: MAIN_CONTAINER_WIDGET_ID },
|
||||
|
|
@ -240,12 +260,14 @@ describe("Canvas Hot Keys", () => {
|
|||
expect(selectedWidgets.length).toBe(children.length);
|
||||
});
|
||||
it("Cmd + A - select all widgets inside a form", async () => {
|
||||
spyGetChildWidgets.mockImplementation(mockGetChildWidgets);
|
||||
const children: any = buildChildren([
|
||||
{ type: "FORM_WIDGET", parentId: MAIN_CONTAINER_WIDGET_ID },
|
||||
]);
|
||||
const dsl: any = widgetCanvasFactory.build({
|
||||
children,
|
||||
});
|
||||
|
||||
spyGetCanvasWidgetDsl.mockImplementation(mockGetCanvasWidgetDsl);
|
||||
mockGetIsFetchingPage.mockImplementation(() => false);
|
||||
|
||||
|
|
@ -337,6 +359,8 @@ describe("Canvas Hot Keys", () => {
|
|||
});
|
||||
spyGetCanvasWidgetDsl.mockImplementation(mockGetCanvasWidgetDsl);
|
||||
mockGetIsFetchingPage.mockImplementation(() => false);
|
||||
spyGetChildWidgets.mockImplementation(mockGetChildWidgets);
|
||||
spyCreateCanvasWidget.mockImplementation(mockCreateCanvasWidget);
|
||||
|
||||
const component = render(
|
||||
<MemoryRouter
|
||||
|
|
|
|||
|
|
@ -6,13 +6,17 @@ import {
|
|||
import { act, render, fireEvent } from "test/testUtils";
|
||||
import GlobalHotKeys from "./GlobalHotKeys";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import * as widgetRenderUtils from "utils/widgetRenderUtils";
|
||||
import * as utilities from "selectors/editorSelectors";
|
||||
import * as dataTreeSelectors from "selectors/dataTreeSelectors";
|
||||
import store from "store";
|
||||
import { sagasToRunForTests } from "test/sagas";
|
||||
import { all } from "@redux-saga/core/effects";
|
||||
import {
|
||||
MockApplication,
|
||||
mockCreateCanvasWidget,
|
||||
mockGetCanvasWidgetDsl,
|
||||
mockGetWidgetEvalValues,
|
||||
syntheticTestMouseEvent,
|
||||
} from "test/testCommon";
|
||||
import lodash from "lodash";
|
||||
|
|
@ -89,6 +93,16 @@ const renderNestedComponent = () => {
|
|||
describe("Drag and Drop widgets into Main container", () => {
|
||||
const mockGetIsFetchingPage = jest.spyOn(utilities, "getIsFetchingPage");
|
||||
const spyGetCanvasWidgetDsl = jest.spyOn(utilities, "getCanvasWidgetDsl");
|
||||
|
||||
jest
|
||||
.spyOn(widgetRenderUtils, "createCanvasWidget")
|
||||
.mockImplementation(mockCreateCanvasWidget);
|
||||
jest
|
||||
.spyOn(dataTreeSelectors, "getWidgetEvalValues")
|
||||
.mockImplementation(mockGetWidgetEvalValues);
|
||||
jest
|
||||
.spyOn(utilities, "computeMainContainerWidget")
|
||||
.mockImplementation((widget) => widget as any);
|
||||
jest
|
||||
.spyOn(useDynamicAppLayoutHook, "useDynamicAppLayout")
|
||||
.mockImplementation(() => true);
|
||||
|
|
@ -451,6 +465,7 @@ describe("Drag and Drop widgets into Main container", () => {
|
|||
children,
|
||||
});
|
||||
dsl.bottomRow = 250;
|
||||
|
||||
spyGetCanvasWidgetDsl.mockImplementation(mockGetCanvasWidgetDsl);
|
||||
mockGetIsFetchingPage.mockImplementation(() => false);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import { useSelector } from "react-redux";
|
|||
import {
|
||||
getCurrentPageId,
|
||||
getIsFetchingPage,
|
||||
getCanvasWidgetDsl,
|
||||
getViewModePageList,
|
||||
previewModeSelector,
|
||||
getCanvasWidth,
|
||||
} from "selectors/editorSelectors";
|
||||
import styled from "styled-components";
|
||||
import { getCanvasClassName } from "utils/generators";
|
||||
|
|
@ -25,6 +25,8 @@ import useGoogleFont from "utils/hooks/useGoogleFont";
|
|||
import { IconSize } from "components/ads/Icon";
|
||||
import { useDynamicAppLayout } from "utils/hooks/useDynamicAppLayout";
|
||||
import { getCurrentThemeDetails } from "selectors/themeSelectors";
|
||||
import { getCanvasWidgetsStructure } from "selectors/entitiesSelector";
|
||||
import { isEqual } from "lodash";
|
||||
import { WidgetGlobaStyles } from "globalStyles/WidgetGlobalStyles";
|
||||
|
||||
const Container = styled.section<{
|
||||
|
|
@ -49,7 +51,8 @@ function CanvasContainer() {
|
|||
const dispatch = useDispatch();
|
||||
const currentPageId = useSelector(getCurrentPageId);
|
||||
const isFetchingPage = useSelector(getIsFetchingPage);
|
||||
const widgets = useSelector(getCanvasWidgetDsl);
|
||||
const canvasWidth = useSelector(getCanvasWidth);
|
||||
const widgetsStructure = useSelector(getCanvasWidgetsStructure, isEqual);
|
||||
const pages = useSelector(getViewModePageList);
|
||||
const theme = useSelector(getCurrentThemeDetails);
|
||||
const isPreviewMode = useSelector(previewModeSelector);
|
||||
|
|
@ -80,8 +83,14 @@ function CanvasContainer() {
|
|||
node = pageLoading;
|
||||
}
|
||||
|
||||
if (!isPageInitializing && widgets) {
|
||||
node = <Canvas dsl={widgets} pageId={params.pageId} />;
|
||||
if (!isPageInitializing && widgetsStructure) {
|
||||
node = (
|
||||
<Canvas
|
||||
canvasWidth={canvasWidth}
|
||||
pageId={params.pageId}
|
||||
widgetsStructure={widgetsStructure}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// calculating exact height to not allow scroll at this component,
|
||||
// calculating total height minus margin on top, top bar and bottom bar
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import {
|
|||
} from "test/factories/WidgetFactoryUtils";
|
||||
import {
|
||||
MockApplication,
|
||||
mockCreateCanvasWidget,
|
||||
mockGetCanvasWidgetDsl,
|
||||
mockGetWidgetEvalValues,
|
||||
MockPageDSL,
|
||||
syntheticTestMouseEvent,
|
||||
} from "test/testCommon";
|
||||
|
|
@ -17,10 +19,22 @@ import { sagasToRunForTests } from "test/sagas";
|
|||
import GlobalHotKeys from "pages/Editor/GlobalHotKeys";
|
||||
import { UpdatedMainContainer } from "test/testMockedWidgets";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import * as widgetRenderUtils from "utils/widgetRenderUtils";
|
||||
import * as utilities from "selectors/editorSelectors";
|
||||
import Canvas from "pages/Editor/Canvas";
|
||||
import * as dataTreeSelectors from "selectors/dataTreeSelectors";
|
||||
|
||||
describe("Canvas selection test cases", () => {
|
||||
jest
|
||||
.spyOn(dataTreeSelectors, "getWidgetEvalValues")
|
||||
.mockImplementation(mockGetWidgetEvalValues);
|
||||
jest
|
||||
.spyOn(utilities, "computeMainContainerWidget")
|
||||
.mockImplementation((widget) => widget as any);
|
||||
jest
|
||||
.spyOn(widgetRenderUtils, "createCanvasWidget")
|
||||
.mockImplementation(mockCreateCanvasWidget);
|
||||
|
||||
it("Should select using canvas draw", () => {
|
||||
const children: any = buildChildren([
|
||||
{
|
||||
|
|
@ -263,7 +277,11 @@ describe("Canvas selection test cases", () => {
|
|||
|
||||
const component = render(
|
||||
<MockPageDSL dsl={dsl}>
|
||||
<Canvas dsl={dsl} pageId="" />
|
||||
<Canvas
|
||||
canvasWidth={dsl.rightColumn}
|
||||
pageId="page_id"
|
||||
widgetsStructure={dsl}
|
||||
/>
|
||||
</MockPageDSL>,
|
||||
);
|
||||
const selectionCanvas: any = component.queryByTestId(`canvas-${canvasId}`);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
import { useSelector } from "store";
|
||||
import { AppState } from "reducers";
|
||||
import { getSelectedWidgets } from "selectors/ui";
|
||||
import { getOccupiedSpaces } from "selectors/editorSelectors";
|
||||
import { getOccupiedSpacesWhileMoving } from "selectors/editorSelectors";
|
||||
import { getTableFilterState } from "selectors/tableFilterSelectors";
|
||||
import { OccupiedSpace } from "constants/CanvasEditorConstants";
|
||||
import { getDragDetails, getWidgetByID, getWidgets } from "sagas/selectors";
|
||||
|
|
@ -117,7 +117,8 @@ export const useBlocksToBeDraggedOnCanvas = ({
|
|||
(state: AppState) => state.ui.widgetDragResize.isResizing,
|
||||
);
|
||||
const selectedWidgets = useSelector(getSelectedWidgets);
|
||||
const occupiedSpaces = useSelector(getOccupiedSpaces, isEqual) || {};
|
||||
const occupiedSpaces =
|
||||
useSelector(getOccupiedSpacesWhileMoving, isEqual) || {};
|
||||
const isNewWidget = !!newWidget && !dragParent;
|
||||
const childrenOccupiedSpaces: OccupiedSpace[] =
|
||||
(dragParent && occupiedSpaces[dragParent]) || [];
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import {
|
|||
ReduxAction,
|
||||
} from "@appsmith/constants/ReduxActionConstants";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import { Diff, diff } from "deep-diff";
|
||||
import { uniq } from "lodash";
|
||||
|
||||
const initialState: CanvasWidgetsReduxState = {};
|
||||
|
||||
|
|
@ -14,6 +16,26 @@ export type FlattenedWidgetProps<orType = never> =
|
|||
})
|
||||
| orType;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param updateLayoutDiff
|
||||
* @returns list of widgets that were updated
|
||||
*/
|
||||
function getUpdatedWidgetLists(
|
||||
updateLayoutDiff: Diff<
|
||||
CanvasWidgetsReduxState,
|
||||
{
|
||||
[widgetId: string]: WidgetProps;
|
||||
}
|
||||
>[],
|
||||
) {
|
||||
return uniq(
|
||||
updateLayoutDiff
|
||||
.map((diff: Diff<CanvasWidgetsReduxState>) => diff.path?.[0])
|
||||
.filter((widgetId) => !!widgetId),
|
||||
);
|
||||
}
|
||||
|
||||
const canvasWidgetsReducer = createImmerReducer(initialState, {
|
||||
[ReduxActionTypes.INIT_CANVAS_LAYOUT]: (
|
||||
state: CanvasWidgetsReduxState,
|
||||
|
|
@ -25,10 +47,29 @@ const canvasWidgetsReducer = createImmerReducer(initialState, {
|
|||
state: CanvasWidgetsReduxState,
|
||||
action: ReduxAction<UpdateCanvasPayload>,
|
||||
) => {
|
||||
return action.payload.widgets;
|
||||
let listOfUpdatedWidgets;
|
||||
// if payload has knowledge of which widgets were changed, use that
|
||||
if (action.payload.updatedWidgetIds) {
|
||||
listOfUpdatedWidgets = action.payload.updatedWidgetIds;
|
||||
} // else diff out the widgets that need to be updated
|
||||
else {
|
||||
const updatedLayoutDiffs = diff(state, action.payload.widgets);
|
||||
if (!updatedLayoutDiffs) return state;
|
||||
|
||||
listOfUpdatedWidgets = getUpdatedWidgetLists(updatedLayoutDiffs);
|
||||
}
|
||||
|
||||
//update only the widgets that need to be updated.
|
||||
for (const widgetId of listOfUpdatedWidgets) {
|
||||
const updatedWidget = action.payload.widgets[widgetId];
|
||||
if (updatedWidget) {
|
||||
state[widgetId] = updatedWidget;
|
||||
} else {
|
||||
delete state[widgetId];
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export interface CanvasWidgetsReduxState {
|
||||
[widgetId: string]: FlattenedWidgetProps;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
import { createImmerReducer } from "utils/ReducerUtils";
|
||||
import {
|
||||
ReduxActionTypes,
|
||||
UpdateCanvasPayload,
|
||||
ReduxAction,
|
||||
} from "@appsmith/constants/ReduxActionConstants";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import { CanvasWidgetStructure } from "widgets/constants";
|
||||
import { pick } from "lodash";
|
||||
import {
|
||||
MAIN_CONTAINER_WIDGET_ID,
|
||||
WidgetType,
|
||||
WIDGET_DSL_STRUCTURE_PROPS,
|
||||
} from "constants/WidgetConstants";
|
||||
import { CANVAS_DEFAULT_MIN_ROWS } from "constants/AppConstants";
|
||||
|
||||
export type FlattenedWidgetProps<orType = never> =
|
||||
| (WidgetProps & {
|
||||
children?: string[];
|
||||
})
|
||||
| orType;
|
||||
|
||||
export type CanvasWidgetsStructureReduxState = {
|
||||
children?: CanvasWidgetsStructureReduxState[];
|
||||
type: WidgetType;
|
||||
widgetId: string;
|
||||
parentId?: string;
|
||||
bottomRow: number;
|
||||
topRow: number;
|
||||
};
|
||||
|
||||
const initialState: CanvasWidgetsStructureReduxState = {
|
||||
type: "CANVAS_WIDGET",
|
||||
widgetId: MAIN_CONTAINER_WIDGET_ID,
|
||||
topRow: 0,
|
||||
bottomRow: CANVAS_DEFAULT_MIN_ROWS,
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate dsl type skeletal structure from canvas widgets
|
||||
* @param rootWidgetId
|
||||
* @param widgets
|
||||
* @returns
|
||||
*/
|
||||
function denormalize(
|
||||
rootWidgetId: string,
|
||||
widgets: Record<string, FlattenedWidgetProps>,
|
||||
): CanvasWidgetStructure {
|
||||
const rootWidget = widgets[rootWidgetId];
|
||||
|
||||
const children = (rootWidget.children || []).map((childId) =>
|
||||
denormalize(childId, widgets),
|
||||
);
|
||||
|
||||
const staticProps = Object.keys(WIDGET_DSL_STRUCTURE_PROPS);
|
||||
|
||||
const structure = pick(rootWidget, staticProps) as CanvasWidgetStructure;
|
||||
|
||||
structure.children = children;
|
||||
|
||||
return structure;
|
||||
}
|
||||
|
||||
const canvasWidgetsStructureReducer = createImmerReducer(initialState, {
|
||||
[ReduxActionTypes.INIT_CANVAS_LAYOUT]: (
|
||||
state: CanvasWidgetsStructureReduxState,
|
||||
action: ReduxAction<UpdateCanvasPayload>,
|
||||
) => {
|
||||
return denormalize("0", action.payload.widgets);
|
||||
},
|
||||
[ReduxActionTypes.UPDATE_LAYOUT]: (
|
||||
state: CanvasWidgetsStructureReduxState,
|
||||
action: ReduxAction<UpdateCanvasPayload>,
|
||||
) => {
|
||||
return denormalize("0", action.payload.widgets);
|
||||
},
|
||||
});
|
||||
|
||||
export default canvasWidgetsStructureReducer;
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
import { combineReducers } from "redux";
|
||||
import canvasWidgetsReducer from "./canvasWidgetsReducer";
|
||||
import widgetConfigReducer from "./widgetConfigReducer";
|
||||
import actionsReducer from "./actionsReducer";
|
||||
import datasourceReducer from "./datasourceReducer";
|
||||
import pageListReducer from "./pageListReducer";
|
||||
import jsExecutionsReducer from "./jsExecutionsReducer";
|
||||
import pluginsReducer from "reducers/entityReducers/pluginsReducer";
|
||||
import metaReducer from "./metaReducer";
|
||||
import appReducer from "./appReducer";
|
||||
import canvasWidgetsReducer from "./canvasWidgetsReducer";
|
||||
import canvasWidgetsStructureReducer from "./canvasWidgetsStructureReducer";
|
||||
import datasourceReducer from "./datasourceReducer";
|
||||
import jsActionsReducer from "./jsActionsReducer";
|
||||
import jsExecutionsReducer from "./jsExecutionsReducer";
|
||||
import metaReducer from "./metaReducer";
|
||||
import pageListReducer from "./pageListReducer";
|
||||
import pluginsReducer from "reducers/entityReducers/pluginsReducer";
|
||||
import widgetConfigReducer from "./widgetConfigReducer";
|
||||
|
||||
const entityReducer = combineReducers({
|
||||
canvasWidgets: canvasWidgetsReducer,
|
||||
canvasWidgetsStructure: canvasWidgetsStructureReducer,
|
||||
widgetConfig: widgetConfigReducer,
|
||||
actions: actionsReducer,
|
||||
datasources: datasourceReducer,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ import SettingsReducer, {
|
|||
SettingsReduxState,
|
||||
} from "@appsmith/reducers/settingsReducer";
|
||||
import { TriggerValuesEvaluationState } from "./evaluationReducers/triggerReducer";
|
||||
import { CanvasWidgetStructure } from "widgets/constants";
|
||||
|
||||
const appReducer = combineReducers({
|
||||
entities: entityReducer,
|
||||
|
|
@ -115,6 +116,7 @@ export interface AppState {
|
|||
mainCanvas: MainCanvasReduxState;
|
||||
};
|
||||
entities: {
|
||||
canvasWidgetsStructure: CanvasWidgetStructure;
|
||||
canvasWidgets: CanvasWidgetsReduxState;
|
||||
actions: ActionDataState;
|
||||
widgetConfig: WidgetConfigReducerState;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { areArraysEqual } from "utils/AppsmithUtils";
|
||||
import { createImmerReducer } from "utils/ReducerUtils";
|
||||
import {
|
||||
ReduxAction,
|
||||
|
|
@ -86,10 +87,12 @@ export const widgetDraggingReducer = createImmerReducer(initialState, {
|
|||
}
|
||||
} else {
|
||||
state.lastSelectedWidget = action.payload.widgetId;
|
||||
if (action.payload.widgetId) {
|
||||
state.selectedWidgets = [action.payload.widgetId];
|
||||
} else {
|
||||
if (!action.payload.widgetId) {
|
||||
state.selectedWidgets = [];
|
||||
} else if (
|
||||
!areArraysEqual(state.selectedWidgets, [action.payload.widgetId])
|
||||
) {
|
||||
state.selectedWidgets = [action.payload.widgetId];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -109,7 +112,7 @@ export const widgetDraggingReducer = createImmerReducer(initialState, {
|
|||
action: ReduxAction<{ widgetIds?: string[] }>,
|
||||
) => {
|
||||
const { widgetIds } = action.payload;
|
||||
if (widgetIds) {
|
||||
if (widgetIds && !areArraysEqual(widgetIds, state.selectedWidgets)) {
|
||||
state.selectedWidgets = widgetIds || [];
|
||||
if (widgetIds.length > 1) {
|
||||
state.lastSelectedWidget = "";
|
||||
|
|
@ -123,7 +126,7 @@ export const widgetDraggingReducer = createImmerReducer(initialState, {
|
|||
action: ReduxAction<{ widgetIds?: string[] }>,
|
||||
) => {
|
||||
const { widgetIds } = action.payload;
|
||||
if (widgetIds) {
|
||||
if (widgetIds && !areArraysEqual(widgetIds, state.selectedWidgets)) {
|
||||
state.selectedWidgets = [...state.selectedWidgets, ...widgetIds];
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { ReactNode, useState, useEffect, useRef, useMemo } from "react";
|
||||
import React, { ReactNode, useState, useEffect, useRef } from "react";
|
||||
import styled, { StyledComponent } from "styled-components";
|
||||
import { WIDGET_PADDING } from "constants/WidgetConstants";
|
||||
import { useDrag } from "react-use-gesture";
|
||||
|
|
@ -17,7 +17,7 @@ import {
|
|||
ReflowedSpace,
|
||||
} from "reflow/reflowTypes";
|
||||
import { getNearestParentCanvas } from "utils/generators";
|
||||
import { getOccupiedSpaces } from "selectors/editorSelectors";
|
||||
import { getContainerOccupiedSpacesSelectorWhileResizing } from "selectors/editorSelectors";
|
||||
import { isDropZoneOccupied } from "utils/WidgetPropsUtils";
|
||||
|
||||
const ResizeWrapper = styled(animated.div)<{ prevents: boolean }>`
|
||||
|
|
@ -161,12 +161,9 @@ export function ReflowResizable(props: ResizableProps) {
|
|||
const resizableRef = useRef<HTMLDivElement>(null);
|
||||
const [isResizing, setResizing] = useState(false);
|
||||
|
||||
const occupiedSpaces = useSelector(getOccupiedSpaces);
|
||||
const occupiedSpacesBySiblingWidgets = useMemo(() => {
|
||||
return occupiedSpaces && props.parentId && occupiedSpaces[props.parentId]
|
||||
? occupiedSpaces[props.parentId]
|
||||
: undefined;
|
||||
}, [occupiedSpaces, props.parentId]);
|
||||
const occupiedSpacesBySiblingWidgets = useSelector(
|
||||
getContainerOccupiedSpacesSelectorWhileResizing(props.parentId),
|
||||
);
|
||||
const checkForCollision = (widgetNewSize: {
|
||||
left: number;
|
||||
top: number;
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ export function* resizeModalSaga(resizeAction: ReduxAction<ModalWidgetResize>) {
|
|||
}
|
||||
|
||||
log.debug("resize computations took", performance.now() - start, "ms");
|
||||
//TODO Identify the updated widgets and pass the values
|
||||
yield put(updateAndSaveLayout(widgets));
|
||||
} catch (error) {
|
||||
yield put({
|
||||
|
|
|
|||
|
|
@ -500,7 +500,9 @@ function* savePageSaga(action: ReduxAction<{ isRetry?: boolean }>) {
|
|||
correctWidget: JSON.stringify(normalizedWidgets),
|
||||
});
|
||||
yield put(
|
||||
updateAndSaveLayout(normalizedWidgets.entities.canvasWidgets, true),
|
||||
updateAndSaveLayout(normalizedWidgets.entities.canvasWidgets, {
|
||||
isRetry: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -810,6 +812,7 @@ export function* updateWidgetNameSaga(
|
|||
// @ts-expect-error parentId can be undefined
|
||||
widgets[parentId] = parent;
|
||||
// Update and save the new widgets
|
||||
//TODO Identify the updated widgets and pass the values
|
||||
yield put(updateAndSaveLayout(widgets));
|
||||
// Send a update saying that we've successfully updated the name
|
||||
yield put(updateWidgetNameSuccess());
|
||||
|
|
|
|||
|
|
@ -214,7 +214,13 @@ export function* undoRedoSaga(action: ReduxAction<UndoRedoPayload>) {
|
|||
const isPropertyUpdate = replay.widgets && replay.propertyUpdates;
|
||||
AnalyticsUtil.logEvent(event, { paths, timeTaken });
|
||||
if (isPropertyUpdate) yield call(openPropertyPaneSaga, replay);
|
||||
yield put(updateAndSaveLayout(replayEntity.widgets, false, false));
|
||||
//TODO Identify the updated widgets and pass the values
|
||||
yield put(
|
||||
updateAndSaveLayout(replayEntity.widgets, {
|
||||
isRetry: false,
|
||||
shouldReplay: false,
|
||||
}),
|
||||
);
|
||||
if (!isPropertyUpdate) yield call(postUndoRedoSaga, replay);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import {
|
|||
isPathADynamicTrigger,
|
||||
} from "utils/DynamicBindingUtils";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import _, { cloneDeep, isString, set } from "lodash";
|
||||
import _, { cloneDeep, isString, set, uniq } from "lodash";
|
||||
import WidgetFactory from "utils/WidgetFactory";
|
||||
import { resetWidgetMetaProperty } from "actions/metaActions";
|
||||
import {
|
||||
|
|
@ -56,7 +56,7 @@ import log from "loglevel";
|
|||
import { navigateToCanvas } from "pages/Editor/Explorer/Widgets/utils";
|
||||
import {
|
||||
getCurrentPageId,
|
||||
getWidgetSpacesSelectorForContainer,
|
||||
getContainerWidgetSpacesSelector,
|
||||
} from "selectors/editorSelectors";
|
||||
import { selectMultipleWidgetsInitAction } from "actions/widgetSelectionActions";
|
||||
|
||||
|
|
@ -596,7 +596,7 @@ function* batchUpdateWidgetPropertySaga(
|
|||
"ms",
|
||||
);
|
||||
// Save the layout
|
||||
yield put(updateAndSaveLayout(widgets, undefined, shouldReplay));
|
||||
yield put(updateAndSaveLayout(widgets, { shouldReplay }));
|
||||
}
|
||||
|
||||
function* batchUpdateMultipleWidgetsPropertiesSaga(
|
||||
|
|
@ -620,6 +620,8 @@ function* batchUpdateMultipleWidgetsPropertiesSaga(
|
|||
stateWidgets,
|
||||
);
|
||||
|
||||
const updatedWidgetIds = uniq(updatedWidgets.map((each) => each.widgetId));
|
||||
|
||||
log.debug(
|
||||
"Batch multi-widget properties update calculations took: ",
|
||||
performance.now() - start,
|
||||
|
|
@ -627,7 +629,11 @@ function* batchUpdateMultipleWidgetsPropertiesSaga(
|
|||
);
|
||||
|
||||
// Save the layout
|
||||
yield put(updateAndSaveLayout(updatedStateWidgets));
|
||||
yield put(
|
||||
updateAndSaveLayout(updatedStateWidgets, {
|
||||
updatedWidgetIds,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function* removeWidgetProperties(widget: WidgetProps, paths: string[]) {
|
||||
|
|
@ -1055,7 +1061,7 @@ function* getNewPositionsBasedOnSelectedWidgets(
|
|||
maxGridColumns: GridDefaults.DEFAULT_GRID_COLUMNS,
|
||||
};
|
||||
|
||||
const reflowSpacesSelector = getWidgetSpacesSelectorForContainer(parentId);
|
||||
const reflowSpacesSelector = getContainerWidgetSpacesSelector(parentId);
|
||||
const widgetSpaces: WidgetSpace[] = yield select(reflowSpacesSelector) || [];
|
||||
|
||||
// Ids of each pasting are changed just for reflow
|
||||
|
|
@ -1145,7 +1151,7 @@ function* getNewPositionsBasedOnMousePositions(
|
|||
|
||||
if (!snapGrid || !mousePositions) return {};
|
||||
|
||||
const reflowSpacesSelector = getWidgetSpacesSelectorForContainer(canvasId);
|
||||
const reflowSpacesSelector = getContainerWidgetSpacesSelector(canvasId);
|
||||
const widgetSpaces: WidgetSpace[] = yield select(reflowSpacesSelector) || [];
|
||||
|
||||
let mouseTopRow = mousePositions.top;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ import {
|
|||
getStickyCanvasName,
|
||||
POSITIONED_WIDGET,
|
||||
} from "constants/componentClassNameConstants";
|
||||
import { getWidgetSpacesSelectorForContainer } from "selectors/editorSelectors";
|
||||
import { getContainerWidgetSpacesSelector } from "selectors/editorSelectors";
|
||||
import { reflow } from "reflow";
|
||||
import { getBottomRowAfterReflow } from "utils/reflowHookUtils";
|
||||
import { DataTreeWidget } from "entities/DataTree/dataTreeFactory";
|
||||
|
|
@ -1197,7 +1197,7 @@ export const groupWidgetsIntoContainer = function*(
|
|||
|
||||
// if there are no collision already then reflow the below widgets by 2 rows.
|
||||
if (!isThereACollision) {
|
||||
const widgetSpacesSelector = getWidgetSpacesSelectorForContainer(
|
||||
const widgetSpacesSelector = getContainerWidgetSpacesSelector(
|
||||
pastingIntoWidgetId,
|
||||
);
|
||||
const widgetSpaces: WidgetSpace[] = yield select(widgetSpacesSelector) ||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,17 @@ import {
|
|||
getJSCollectionsForCurrentPage,
|
||||
} from "./entitiesSelector";
|
||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||
import { DataTree, DataTreeFactory } from "entities/DataTree/dataTreeFactory";
|
||||
import {
|
||||
DataTree,
|
||||
DataTreeFactory,
|
||||
DataTreeWidget,
|
||||
} from "entities/DataTree/dataTreeFactory";
|
||||
import { getWidgets, getWidgetsMeta } from "sagas/selectors";
|
||||
import "url-search-params-polyfill";
|
||||
import { getPageList } from "./appViewSelectors";
|
||||
import { AppState } from "reducers";
|
||||
import { getSelectedAppThemeProperties } from "./appThemingSelectors";
|
||||
import { LoadingEntitiesState } from "reducers/evaluationReducers/loadingEntitiesReducer";
|
||||
|
||||
export const getUnevaluatedDataTree = createSelector(
|
||||
getActionsForCurrentPage,
|
||||
|
|
@ -56,6 +61,12 @@ export const getEvaluationInverseDependencyMap = (state: AppState) =>
|
|||
export const getLoadingEntities = (state: AppState) =>
|
||||
state.evaluations.loadingEntities;
|
||||
|
||||
export const getIsWidgetLoading = createSelector(
|
||||
[getLoadingEntities, (_state: AppState, widgetName: string) => widgetName],
|
||||
(loadingEntities: LoadingEntitiesState, widgetName: string) =>
|
||||
loadingEntities.has(widgetName),
|
||||
);
|
||||
|
||||
/**
|
||||
* returns evaluation tree object
|
||||
*
|
||||
|
|
@ -64,6 +75,11 @@ export const getLoadingEntities = (state: AppState) =>
|
|||
export const getDataTree = (state: AppState): DataTree =>
|
||||
state.evaluations.tree;
|
||||
|
||||
export const getWidgetEvalValues = createSelector(
|
||||
[getDataTree, (_state: AppState, widgetName: string) => widgetName],
|
||||
(tree: DataTree, widgetName: string) => tree[widgetName] as DataTreeWidget,
|
||||
);
|
||||
|
||||
// For autocomplete. Use actions cached responses if
|
||||
// there isn't a response already
|
||||
export const getDataTreeForAutocomplete = createSelector(
|
||||
|
|
|
|||
|
|
@ -18,23 +18,27 @@ import {
|
|||
import {
|
||||
MAIN_CONTAINER_WIDGET_ID,
|
||||
RenderModes,
|
||||
WIDGET_STATIC_PROPS,
|
||||
} from "constants/WidgetConstants";
|
||||
import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer";
|
||||
import {
|
||||
DataTree,
|
||||
DataTreeWidget,
|
||||
ENTITY_TYPE,
|
||||
} from "entities/DataTree/dataTreeFactory";
|
||||
import { DataTree, DataTreeWidget } from "entities/DataTree/dataTreeFactory";
|
||||
import { ContainerWidgetProps } from "widgets/ContainerWidget/widget";
|
||||
import { find, pick, sortBy } from "lodash";
|
||||
import WidgetFactory from "utils/WidgetFactory";
|
||||
import { find, sortBy } from "lodash";
|
||||
import { APP_MODE } from "entities/App";
|
||||
import { getDataTree, getLoadingEntities } from "selectors/dataTreeSelectors";
|
||||
import { Page } from "@appsmith/constants/ReduxActionConstants";
|
||||
import { PLACEHOLDER_APP_SLUG, PLACEHOLDER_PAGE_SLUG } from "constants/routes";
|
||||
import { ApplicationVersion } from "actions/applicationActions";
|
||||
import { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer";
|
||||
import {
|
||||
buildChildWidgetTree,
|
||||
createCanvasWidget,
|
||||
createLoadingWidget,
|
||||
} from "utils/widgetRenderUtils";
|
||||
|
||||
const getIsDraggingOrResizing = (state: AppState) =>
|
||||
state.ui.widgetDragResize.isResizing || state.ui.widgetDragResize.isDragging;
|
||||
|
||||
const getIsResizing = (state: AppState) => state.ui.widgetDragResize.isResizing;
|
||||
|
||||
export const getWidgetConfigs = (state: AppState) =>
|
||||
state.entities.widgetConfig;
|
||||
|
|
@ -233,16 +237,25 @@ export const getWidgetCards = createSelector(
|
|||
},
|
||||
);
|
||||
|
||||
const getMainContainer = (
|
||||
export const computeMainContainerWidget = (
|
||||
widget: FlattenedWidgetProps,
|
||||
mainCanvasProps: MainCanvasReduxState,
|
||||
) => ({
|
||||
...widget,
|
||||
rightColumn: mainCanvasProps.width,
|
||||
minHeight: mainCanvasProps.height,
|
||||
});
|
||||
|
||||
export const getMainContainer = (
|
||||
canvasWidgets: CanvasWidgetsReduxState,
|
||||
evaluatedDataTree: DataTree,
|
||||
mainCanvasProps: MainCanvasReduxState,
|
||||
) => {
|
||||
const canvasWidget = {
|
||||
...canvasWidgets[MAIN_CONTAINER_WIDGET_ID],
|
||||
rightColumn: mainCanvasProps.width,
|
||||
minHeight: mainCanvasProps.height,
|
||||
};
|
||||
const canvasWidget = computeMainContainerWidget(
|
||||
canvasWidgets[MAIN_CONTAINER_WIDGET_ID],
|
||||
mainCanvasProps,
|
||||
);
|
||||
|
||||
//TODO: Need to verify why `evaluatedDataTree` is required here.
|
||||
const evaluatedWidget = find(evaluatedDataTree, {
|
||||
widgetId: MAIN_CONTAINER_WIDGET_ID,
|
||||
|
|
@ -294,6 +307,16 @@ export const getCanvasWidgetDsl = createSelector(
|
|||
},
|
||||
);
|
||||
|
||||
export const getChildWidgets = createSelector(
|
||||
[
|
||||
getCanvasWidgets,
|
||||
getDataTree,
|
||||
getLoadingEntities,
|
||||
(_state: AppState, widgetId: string) => widgetId,
|
||||
],
|
||||
buildChildWidgetTree,
|
||||
);
|
||||
|
||||
const getOccupiedSpacesForContainer = (
|
||||
containerWidgetId: string,
|
||||
widgets: FlattenedWidgetProps[],
|
||||
|
|
@ -329,101 +352,181 @@ const getWidgetSpacesForContainer = (
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Method to build occupied spaces
|
||||
*
|
||||
* @param widgets canvas Widgets
|
||||
* @param fetchNow would return undefined if false
|
||||
* @returns An array of occupied spaces
|
||||
*/
|
||||
const generateOccupiedSpacesMap = (
|
||||
widgets: CanvasWidgetsReduxState,
|
||||
fetchNow = true,
|
||||
): { [containerWidgetId: string]: OccupiedSpace[] } | undefined => {
|
||||
const occupiedSpaces: {
|
||||
[containerWidgetId: string]: OccupiedSpace[];
|
||||
} = {};
|
||||
if (!fetchNow) return;
|
||||
// Get all widgets with type "CONTAINER_WIDGET" and has children
|
||||
const containerWidgets: FlattenedWidgetProps[] = Object.values(
|
||||
widgets,
|
||||
).filter((widget) => widget.children && widget.children.length > 0);
|
||||
|
||||
// If we have any container widgets
|
||||
if (containerWidgets) {
|
||||
containerWidgets.forEach((containerWidget: FlattenedWidgetProps) => {
|
||||
const containerWidgetId = containerWidget.widgetId;
|
||||
// Get child widgets for the container
|
||||
const childWidgets = Object.keys(widgets).filter(
|
||||
(widgetId) =>
|
||||
containerWidget.children &&
|
||||
containerWidget.children.indexOf(widgetId) > -1 &&
|
||||
!widgets[widgetId].detachFromLayout,
|
||||
);
|
||||
// Get the occupied spaces in this container
|
||||
// Assign it to the containerWidgetId key in occupiedSpaces
|
||||
occupiedSpaces[containerWidgetId] = getOccupiedSpacesForContainer(
|
||||
containerWidgetId,
|
||||
childWidgets.map((widgetId) => widgets[widgetId]),
|
||||
);
|
||||
});
|
||||
}
|
||||
// Return undefined if there are no occupiedSpaces.
|
||||
return Object.keys(occupiedSpaces).length > 0 ? occupiedSpaces : undefined;
|
||||
};
|
||||
|
||||
// returns occupied spaces
|
||||
export const getOccupiedSpaces = createSelector(
|
||||
getWidgets,
|
||||
(
|
||||
widgets: CanvasWidgetsReduxState,
|
||||
): { [containerWidgetId: string]: OccupiedSpace[] } | undefined => {
|
||||
const occupiedSpaces: {
|
||||
[containerWidgetId: string]: OccupiedSpace[];
|
||||
} = {};
|
||||
// Get all widgets with type "CONTAINER_WIDGET" and has children
|
||||
const containerWidgets: FlattenedWidgetProps[] = Object.values(
|
||||
widgets,
|
||||
).filter((widget) => widget.children && widget.children.length > 0);
|
||||
|
||||
// If we have any container widgets
|
||||
if (containerWidgets) {
|
||||
containerWidgets.forEach((containerWidget: FlattenedWidgetProps) => {
|
||||
const containerWidgetId = containerWidget.widgetId;
|
||||
// Get child widgets for the container
|
||||
const childWidgets = Object.keys(widgets).filter(
|
||||
(widgetId) =>
|
||||
containerWidget.children &&
|
||||
containerWidget.children.indexOf(widgetId) > -1 &&
|
||||
!widgets[widgetId].detachFromLayout,
|
||||
);
|
||||
// Get the occupied spaces in this container
|
||||
// Assign it to the containerWidgetId key in occupiedSpaces
|
||||
occupiedSpaces[containerWidgetId] = getOccupiedSpacesForContainer(
|
||||
containerWidgetId,
|
||||
childWidgets.map((widgetId) => widgets[widgetId]),
|
||||
);
|
||||
});
|
||||
}
|
||||
// Return undefined if there are no occupiedSpaces.
|
||||
return Object.keys(occupiedSpaces).length > 0 ? occupiedSpaces : undefined;
|
||||
},
|
||||
generateOccupiedSpacesMap,
|
||||
);
|
||||
|
||||
// returns occupied spaces only while dragging or moving
|
||||
export const getOccupiedSpacesWhileMoving = createSelector(
|
||||
getWidgets,
|
||||
getIsDraggingOrResizing,
|
||||
generateOccupiedSpacesMap,
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param widgets
|
||||
* @param fetchNow returns undined if false
|
||||
* @param containerId id of container whose occupied spaces we are fetching
|
||||
* @returns
|
||||
*/
|
||||
const generateOccupiedSpacesForContainer = (
|
||||
widgets: CanvasWidgetsReduxState,
|
||||
fetchNow: boolean,
|
||||
containerId: string | undefined,
|
||||
): OccupiedSpace[] | undefined => {
|
||||
if (containerId === null || containerId === undefined || !fetchNow)
|
||||
return undefined;
|
||||
|
||||
const containerWidget: FlattenedWidgetProps = widgets[containerId];
|
||||
|
||||
if (!containerWidget || !containerWidget.children) return undefined;
|
||||
|
||||
// Get child widgets for the container
|
||||
const childWidgets = Object.keys(widgets).filter(
|
||||
(widgetId) =>
|
||||
containerWidget.children &&
|
||||
containerWidget.children.indexOf(widgetId) > -1 &&
|
||||
!widgets[widgetId].detachFromLayout,
|
||||
);
|
||||
|
||||
const occupiedSpaces = getOccupiedSpacesForContainer(
|
||||
containerId,
|
||||
childWidgets.map((widgetId) => widgets[widgetId]),
|
||||
);
|
||||
return occupiedSpaces;
|
||||
};
|
||||
|
||||
// same as getOccupiedSpaces but gets only the container specific ocupied Spaces
|
||||
export function getOccupiedSpacesSelectorForContainer(
|
||||
containerId: string | undefined,
|
||||
) {
|
||||
return createSelector(getWidgets, (widgets: CanvasWidgetsReduxState):
|
||||
| OccupiedSpace[]
|
||||
| undefined => {
|
||||
if (containerId === null || containerId === undefined) return undefined;
|
||||
return createSelector(getWidgets, (widgets: CanvasWidgetsReduxState) => {
|
||||
return generateOccupiedSpacesForContainer(widgets, true, containerId);
|
||||
});
|
||||
}
|
||||
|
||||
const containerWidget: FlattenedWidgetProps = widgets[containerId];
|
||||
/**
|
||||
*
|
||||
* @param widgets
|
||||
* @param fetchNow returns undined if false
|
||||
* @param containerId id of container whose occupied spaces we are fetching
|
||||
* @returns
|
||||
*/
|
||||
const generateWidgetSpacesForContainer = (
|
||||
widgets: CanvasWidgetsReduxState,
|
||||
fetchNow: boolean,
|
||||
containerId: string | undefined,
|
||||
): WidgetSpace[] | undefined => {
|
||||
if (containerId === null || containerId === undefined || !fetchNow)
|
||||
return undefined;
|
||||
|
||||
if (!containerWidget || !containerWidget.children) return undefined;
|
||||
const containerWidget: FlattenedWidgetProps = widgets[containerId];
|
||||
|
||||
// Get child widgets for the container
|
||||
const childWidgets = Object.keys(widgets).filter(
|
||||
(widgetId) =>
|
||||
containerWidget.children &&
|
||||
containerWidget.children.indexOf(widgetId) > -1 &&
|
||||
!widgets[widgetId].detachFromLayout,
|
||||
);
|
||||
if (!containerWidget || !containerWidget.children) return undefined;
|
||||
|
||||
const occupiedSpaces = getOccupiedSpacesForContainer(
|
||||
containerId,
|
||||
childWidgets.map((widgetId) => widgets[widgetId]),
|
||||
);
|
||||
return occupiedSpaces;
|
||||
// Get child widgets for the container
|
||||
const childWidgets = Object.keys(widgets).filter(
|
||||
(widgetId) =>
|
||||
containerWidget.children &&
|
||||
containerWidget.children.indexOf(widgetId) > -1 &&
|
||||
!widgets[widgetId].detachFromLayout,
|
||||
);
|
||||
|
||||
const occupiedSpaces = getWidgetSpacesForContainer(
|
||||
containerId,
|
||||
childWidgets.map((widgetId) => widgets[widgetId]),
|
||||
);
|
||||
return occupiedSpaces;
|
||||
};
|
||||
|
||||
// same as getOccupiedSpaces but gets only the container specific ocupied Spaces only while resizing
|
||||
export function getContainerOccupiedSpacesSelectorWhileResizing(
|
||||
containerId: string | undefined,
|
||||
) {
|
||||
return createSelector(
|
||||
getWidgets,
|
||||
getIsResizing,
|
||||
(widgets: CanvasWidgetsReduxState, isResizing: boolean) => {
|
||||
return generateOccupiedSpacesForContainer(
|
||||
widgets,
|
||||
isResizing,
|
||||
containerId,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// same as getOccupiedSpaces but gets only the container specific occupied Spaces
|
||||
export function getContainerWidgetSpacesSelector(
|
||||
containerId: string | undefined,
|
||||
) {
|
||||
return createSelector(getWidgets, (widgets: CanvasWidgetsReduxState) => {
|
||||
return generateWidgetSpacesForContainer(widgets, true, containerId);
|
||||
});
|
||||
}
|
||||
|
||||
// same as getOccupiedSpaces but gets only the container specific occupied Spaces
|
||||
export function getWidgetSpacesSelectorForContainer(
|
||||
export function getContainerWidgetSpacesSelectorWhileMoving(
|
||||
containerId: string | undefined,
|
||||
) {
|
||||
return createSelector(getWidgets, (widgets: CanvasWidgetsReduxState):
|
||||
| WidgetSpace[]
|
||||
| undefined => {
|
||||
if (containerId === null || containerId === undefined) return undefined;
|
||||
|
||||
const containerWidget: FlattenedWidgetProps = widgets[containerId];
|
||||
|
||||
if (!containerWidget || !containerWidget.children) return undefined;
|
||||
|
||||
// Get child widgets for the container
|
||||
const childWidgets = Object.keys(widgets).filter(
|
||||
(widgetId) =>
|
||||
containerWidget.children &&
|
||||
containerWidget.children.indexOf(widgetId) > -1 &&
|
||||
!widgets[widgetId].detachFromLayout,
|
||||
);
|
||||
|
||||
const occupiedSpaces = getWidgetSpacesForContainer(
|
||||
containerId,
|
||||
childWidgets.map((widgetId) => widgets[widgetId]),
|
||||
);
|
||||
return occupiedSpaces;
|
||||
});
|
||||
return createSelector(
|
||||
getWidgets,
|
||||
getIsDraggingOrResizing,
|
||||
(widgets: CanvasWidgetsReduxState, isDraggingOrResizing: boolean) => {
|
||||
return generateWidgetSpacesForContainer(
|
||||
widgets,
|
||||
isDraggingOrResizing,
|
||||
containerId,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export const getActionById = createSelector(
|
||||
[getActions, (state: any, props: any) => props.match.params.apiId],
|
||||
(actions, id) => {
|
||||
|
|
@ -436,45 +539,6 @@ export const getActionById = createSelector(
|
|||
},
|
||||
);
|
||||
|
||||
const createCanvasWidget = (
|
||||
canvasWidget: FlattenedWidgetProps,
|
||||
evaluatedWidget: DataTreeWidget,
|
||||
) => {
|
||||
const widgetStaticProps = pick(
|
||||
canvasWidget,
|
||||
Object.keys(WIDGET_STATIC_PROPS),
|
||||
);
|
||||
return {
|
||||
...evaluatedWidget,
|
||||
...widgetStaticProps,
|
||||
};
|
||||
};
|
||||
|
||||
const WidgetTypes = WidgetFactory.widgetTypes;
|
||||
const createLoadingWidget = (
|
||||
canvasWidget: FlattenedWidgetProps,
|
||||
): DataTreeWidget => {
|
||||
const widgetStaticProps = pick(
|
||||
canvasWidget,
|
||||
Object.keys(WIDGET_STATIC_PROPS),
|
||||
) as WidgetProps;
|
||||
return {
|
||||
...widgetStaticProps,
|
||||
type: WidgetTypes.SKELETON_WIDGET,
|
||||
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
|
||||
bindingPaths: {},
|
||||
reactivePaths: {},
|
||||
triggerPaths: {},
|
||||
validationPaths: {},
|
||||
logBlackList: {},
|
||||
isLoading: true,
|
||||
propertyOverrideDependency: {},
|
||||
overridingPropertyPaths: {},
|
||||
privateWidgets: {},
|
||||
meta: {},
|
||||
};
|
||||
};
|
||||
|
||||
export const getJSCollectionById = createSelector(
|
||||
[
|
||||
getJSCollections,
|
||||
|
|
|
|||
|
|
@ -466,6 +466,9 @@ export const getAppStoreData = (state: AppState): AppStoreState =>
|
|||
export const getCanvasWidgets = (state: AppState): CanvasWidgetsReduxState =>
|
||||
state.entities.canvasWidgets;
|
||||
|
||||
export const getCanvasWidgetsStructure = (state: AppState) =>
|
||||
state.entities.canvasWidgetsStructure;
|
||||
|
||||
const getPageWidgets = (state: AppState) => state.ui.pageWidgets;
|
||||
export const getCurrentPageWidgets = createSelector(
|
||||
getPageWidgets,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import {
|
|||
} from "./entitiesSelector";
|
||||
import { getSelectedWidget } from "./ui";
|
||||
import { GuidedTourEntityNames } from "pages/Editor/GuidedTour/constants";
|
||||
import { previewModeSelector } from "./editorSelectors";
|
||||
|
||||
// Signposting selectors
|
||||
export const getEnableFirstTimeUserOnboarding = (state: AppState) => {
|
||||
|
|
@ -46,6 +45,10 @@ export const getInOnboardingWidgetSelection = (state: AppState) =>
|
|||
export const getIsOnboardingWidgetSelection = (state: AppState) =>
|
||||
state.ui.onBoarding.inOnboardingWidgetSelection;
|
||||
|
||||
const previewModeSelector = (state: AppState) => {
|
||||
return state.ui.editor.isPreviewMode;
|
||||
};
|
||||
|
||||
export const getIsOnboardingTasksView = createSelector(
|
||||
getCanvasWidgets,
|
||||
getIsFirstTimeUserOnboardingEnabled,
|
||||
|
|
|
|||
|
|
@ -153,6 +153,19 @@ const populateEvaluatedWidgetProperties = (
|
|||
return evaluatedProperties;
|
||||
};
|
||||
|
||||
const getCurrentEvaluatedWidget = createSelector(
|
||||
getCurrentWidgetProperties,
|
||||
getDataTree,
|
||||
(
|
||||
widget: WidgetProps | undefined,
|
||||
evaluatedTree: DataTree,
|
||||
): DataTreeWidget => {
|
||||
return (widget?.widgetName
|
||||
? evaluatedTree[widget.widgetName]
|
||||
: {}) as DataTreeWidget;
|
||||
},
|
||||
);
|
||||
|
||||
export const getWidgetPropsForPropertyName = (
|
||||
propertyName: string,
|
||||
dependencies: string[] = [],
|
||||
|
|
@ -160,15 +173,11 @@ export const getWidgetPropsForPropertyName = (
|
|||
) => {
|
||||
return createSelector(
|
||||
getCurrentWidgetProperties,
|
||||
getDataTree,
|
||||
getCurrentEvaluatedWidget,
|
||||
(
|
||||
widget: WidgetProps | undefined,
|
||||
evaluatedTree: DataTree,
|
||||
evaluatedWidget: DataTreeWidget,
|
||||
): WidgetProperties => {
|
||||
const evaluatedWidget = find(evaluatedTree, {
|
||||
widgetId: widget?.widgetId,
|
||||
}) as DataTreeWidget;
|
||||
|
||||
const widgetProperties = populateWidgetProperties(
|
||||
widget,
|
||||
propertyName,
|
||||
|
|
|
|||
|
|
@ -5,10 +5,14 @@ import { getExistingWidgetNames } from "sagas/selectors";
|
|||
import { getNextEntityName } from "utils/AppsmithUtils";
|
||||
|
||||
import WidgetFactory from "utils/WidgetFactory";
|
||||
import { getParentToOpenIfAny } from "utils/hooks/useClickToSelectWidget";
|
||||
|
||||
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;
|
||||
export const getModalDropdownList = createSelector(
|
||||
getCanvasWidgets,
|
||||
|
|
@ -52,3 +56,17 @@ export const getParentWidget = createSelector(
|
|||
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);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getCamelCaseString } from "utils/AppsmithUtils";
|
||||
import { areArraysEqual, getCamelCaseString } from "utils/AppsmithUtils";
|
||||
|
||||
describe("getCamelCaseString", () => {
|
||||
it("Should return a string in camelCase", () => {
|
||||
|
|
@ -11,3 +11,21 @@ describe("getCamelCaseString", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("test areArraysEqual", () => {
|
||||
it("test areArraysEqual method", () => {
|
||||
const OGArray = ["test1", "test2", "test3"];
|
||||
|
||||
let testArray: string[] = [];
|
||||
expect(areArraysEqual(OGArray, testArray)).toBe(false);
|
||||
|
||||
testArray = ["test1", "test3"];
|
||||
expect(areArraysEqual(OGArray, testArray)).toBe(false);
|
||||
|
||||
testArray = ["test1", "test2", "test3"];
|
||||
expect(areArraysEqual(OGArray, testArray)).toBe(true);
|
||||
|
||||
testArray = ["test1", "test3", "test2"];
|
||||
expect(areArraysEqual(OGArray, testArray)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -397,3 +397,17 @@ export const base64ToBlob = (
|
|||
export const isMacOs = () => {
|
||||
return osName === "Mac OS";
|
||||
};
|
||||
|
||||
/**
|
||||
* checks if array of strings are equal regardless of order
|
||||
* @param arr1
|
||||
* @param arr2
|
||||
* @returns
|
||||
*/
|
||||
export function areArraysEqual(arr1: string[], arr2: string[]) {
|
||||
if (arr1.length !== arr2.length) return false;
|
||||
|
||||
if (arr1.sort().join(",") === arr2.sort().join(",")) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
import {
|
||||
WidgetBuilder,
|
||||
WidgetDataProps,
|
||||
WidgetProps,
|
||||
WidgetState,
|
||||
} from "widgets/BaseWidget";
|
||||
import { WidgetBuilder, WidgetProps, WidgetState } from "widgets/BaseWidget";
|
||||
import React from "react";
|
||||
import { PropertyPaneConfig } from "constants/PropertyControlConstants";
|
||||
|
||||
|
|
@ -16,6 +11,7 @@ import {
|
|||
convertFunctionsToString,
|
||||
enhancePropertyPaneConfig,
|
||||
} from "./WidgetFactoryHelpers";
|
||||
import { CanvasWidgetStructure } from "widgets/constants";
|
||||
|
||||
type WidgetDerivedPropertyType = any;
|
||||
export type DerivedPropertiesMap = Record<string, string>;
|
||||
|
|
@ -25,7 +21,7 @@ class WidgetFactory {
|
|||
static widgetTypes: Record<string, string> = {};
|
||||
static widgetMap: Map<
|
||||
WidgetType,
|
||||
WidgetBuilder<WidgetProps, WidgetState>
|
||||
WidgetBuilder<CanvasWidgetStructure, WidgetState>
|
||||
> = new Map();
|
||||
static widgetDerivedPropertiesGetterMap: Map<
|
||||
WidgetType,
|
||||
|
|
@ -146,10 +142,10 @@ class WidgetFactory {
|
|||
}
|
||||
|
||||
static createWidget(
|
||||
widgetData: WidgetDataProps,
|
||||
widgetData: CanvasWidgetStructure,
|
||||
renderMode: RenderMode,
|
||||
): React.ReactNode {
|
||||
const widgetProps: WidgetProps = {
|
||||
const widgetProps = {
|
||||
key: widgetData.widgetId,
|
||||
isVisible: true,
|
||||
...widgetData,
|
||||
|
|
@ -157,7 +153,6 @@ class WidgetFactory {
|
|||
};
|
||||
const widgetBuilder = this.widgetMap.get(widgetData.type);
|
||||
if (widgetBuilder) {
|
||||
// TODO validate props here
|
||||
const widget = widgetBuilder.buildWidget(widgetProps);
|
||||
return widget;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -12,12 +12,15 @@ import { generateReactKey } from "./generators";
|
|||
import { memoize } from "lodash";
|
||||
import { WidgetFeatureProps } from "./WidgetFeatures";
|
||||
import { WidgetConfiguration } from "widgets/constants";
|
||||
import withWidgetProps from "widgets/withWidgetProps";
|
||||
|
||||
const generateWidget = memoize(function getWidgetComponent(
|
||||
Widget: typeof BaseWidget,
|
||||
needsMeta: boolean,
|
||||
) {
|
||||
const widget = needsMeta ? withMeta(Widget) : Widget;
|
||||
let widget = needsMeta ? withMeta(Widget) : Widget;
|
||||
//@ts-expect-error: type mismatch
|
||||
widget = withWidgetProps(widget);
|
||||
return Sentry.withProfiler(
|
||||
// @ts-expect-error: Types are not available
|
||||
widget,
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ import { AppState } from "reducers";
|
|||
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import { APP_MODE } from "entities/App";
|
||||
import { getAppMode } from "selectors/applicationSelectors";
|
||||
import { getWidgets } from "sagas/selectors";
|
||||
import { useWidgetSelection } from "./useWidgetSelection";
|
||||
import React, { ReactNode, useCallback } from "react";
|
||||
import { stopEventPropagation } from "utils/AppsmithUtils";
|
||||
import { getFocusedParentToOpen } from "selectors/widgetSelectors";
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -103,7 +103,6 @@ export const useClickToSelectWidget = () => {
|
|||
const { focusWidget, selectWidget } = useWidgetSelection();
|
||||
const isPropPaneVisible = useSelector(getIsPropertyPaneVisible);
|
||||
const isTableFilterPaneVisible = useSelector(getIsTableFilterPaneVisible);
|
||||
const widgets: CanvasWidgetsReduxState = useSelector(getWidgets);
|
||||
const selectedWidgetId = useSelector(getCurrentWidgetId);
|
||||
const focusedWidgetId = useSelector(
|
||||
(state: AppState) => state.ui.widgetDragResize.focusedWidget,
|
||||
|
|
@ -119,7 +118,7 @@ export const useClickToSelectWidget = () => {
|
|||
(state: AppState) => state.ui.widgetDragResize.isDragging,
|
||||
);
|
||||
|
||||
const parentWidgetToOpen = getParentToOpenIfAny(focusedWidgetId, widgets);
|
||||
const parentWidgetToOpen = useSelector(getFocusedParentToOpen);
|
||||
const clickToSelectWidget = (e: any, targetWidgetId: string) => {
|
||||
// ignore click captures
|
||||
// 1. if the component was resizing or dragging coz it is handled internally in draggable component
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import { scrollbarWidth } from "utils/helpers";
|
|||
import { useWindowSizeHooks } from "./dragResizeHooks";
|
||||
import { getAppMode } from "selectors/entitiesSelector";
|
||||
import { updateCanvasLayoutAction } from "actions/editorActions";
|
||||
import { calculateDynamicHeight } from "utils/DSLMigrations";
|
||||
import { getIsCanvasInitialized } from "selectors/mainCanvasSelectors";
|
||||
import { calculateDynamicHeight } from "utils/DSLMigrations";
|
||||
|
||||
const BORDERS_WIDTH = 2;
|
||||
const GUTTER_WIDTH = 72;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { OccupiedSpace, WidgetSpace } from "constants/CanvasEditorConstants";
|
|||
import { isEmpty, throttle } from "lodash";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { getWidgetSpacesSelectorForContainer } from "selectors/editorSelectors";
|
||||
import { getContainerWidgetSpacesSelectorWhileMoving } from "selectors/editorSelectors";
|
||||
import { reflow } from "reflow";
|
||||
import {
|
||||
CollidingSpace,
|
||||
|
|
@ -70,7 +70,9 @@ export const useReflow = (
|
|||
|
||||
const isReflowing = useRef<boolean>(false);
|
||||
|
||||
const reflowSpacesSelector = getWidgetSpacesSelectorForContainer(parentId);
|
||||
const reflowSpacesSelector = getContainerWidgetSpacesSelectorWhileMoving(
|
||||
parentId,
|
||||
);
|
||||
const widgetSpaces: WidgetSpace[] = useSelector(reflowSpacesSelector) || [];
|
||||
|
||||
const prevPositions = useRef<OccupiedSpace[] | undefined>(OGPositions);
|
||||
|
|
|
|||
266
app/client/src/utils/widgetRenderUtils.test.ts
Normal file
266
app/client/src/utils/widgetRenderUtils.test.ts
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import { buildChildWidgetTree } from "./widgetRenderUtils";
|
||||
|
||||
describe("test EditorUtils methods", () => {
|
||||
describe("should test buildChildWidgetTree method", () => {
|
||||
const canvasWidgets = ({
|
||||
"1": {
|
||||
children: ["2"],
|
||||
type: "FORM_WIDGET",
|
||||
widgetId: "1",
|
||||
parentId: "0",
|
||||
topRow: 0,
|
||||
bottomRow: 10,
|
||||
widgetName: "one",
|
||||
},
|
||||
"2": {
|
||||
children: ["3", "4"],
|
||||
type: "CANVAS",
|
||||
widgetId: "2",
|
||||
parentId: "1",
|
||||
topRow: 0,
|
||||
bottomRow: 100,
|
||||
widgetName: "two",
|
||||
},
|
||||
"3": {
|
||||
children: [],
|
||||
type: "TEXT",
|
||||
widgetId: "3",
|
||||
parentId: "2",
|
||||
topRow: 4,
|
||||
bottomRow: 5,
|
||||
widgetName: "three",
|
||||
},
|
||||
"4": {
|
||||
children: [],
|
||||
type: "BUTTON",
|
||||
widgetId: "4",
|
||||
parentId: "2",
|
||||
topRow: 6,
|
||||
bottomRow: 18,
|
||||
widgetName: "four",
|
||||
},
|
||||
} as unknown) as CanvasWidgetsReduxState;
|
||||
|
||||
const dataTree = ({
|
||||
one: {
|
||||
children: ["2"],
|
||||
type: "FORM_WIDGET",
|
||||
widgetId: "1",
|
||||
parentId: "0",
|
||||
topRow: 0,
|
||||
bottomRow: 10,
|
||||
widgetName: "one",
|
||||
skipForFormWidget: "test",
|
||||
value: "test",
|
||||
isDirty: true,
|
||||
isValid: true,
|
||||
},
|
||||
two: {
|
||||
children: ["3", "4"],
|
||||
type: "CANVAS",
|
||||
widgetId: "2",
|
||||
parentId: "1",
|
||||
topRow: 0,
|
||||
bottomRow: 100,
|
||||
widgetName: "two",
|
||||
skipForFormWidget: "test",
|
||||
value: "test",
|
||||
isDirty: true,
|
||||
isValid: true,
|
||||
},
|
||||
three: {
|
||||
children: [],
|
||||
type: "TEXT",
|
||||
widgetId: "3",
|
||||
parentId: "2",
|
||||
topRow: 4,
|
||||
bottomRow: 5,
|
||||
widgetName: "three",
|
||||
skipForFormWidget: "test",
|
||||
value: "test",
|
||||
isDirty: true,
|
||||
isValid: true,
|
||||
},
|
||||
four: {
|
||||
children: [],
|
||||
type: "BUTTON",
|
||||
widgetId: "4",
|
||||
parentId: "2",
|
||||
topRow: 6,
|
||||
bottomRow: 18,
|
||||
widgetName: "four",
|
||||
skipForFormWidget: "test",
|
||||
value: "test",
|
||||
isDirty: true,
|
||||
isValid: true,
|
||||
},
|
||||
} as unknown) as DataTree;
|
||||
|
||||
it("should return a complete childwidgets Tree", () => {
|
||||
const childWidgetTree = [
|
||||
{
|
||||
bottomRow: 5,
|
||||
children: [],
|
||||
skipForFormWidget: "test",
|
||||
isDirty: true,
|
||||
isLoading: false,
|
||||
isValid: true,
|
||||
parentId: "2",
|
||||
topRow: 4,
|
||||
type: "TEXT",
|
||||
value: "test",
|
||||
widgetId: "3",
|
||||
widgetName: "three",
|
||||
},
|
||||
{
|
||||
bottomRow: 18,
|
||||
children: [],
|
||||
skipForFormWidget: "test",
|
||||
isDirty: true,
|
||||
isLoading: false,
|
||||
isValid: true,
|
||||
parentId: "2",
|
||||
topRow: 6,
|
||||
type: "BUTTON",
|
||||
value: "test",
|
||||
widgetId: "4",
|
||||
widgetName: "four",
|
||||
},
|
||||
];
|
||||
|
||||
expect(
|
||||
buildChildWidgetTree(
|
||||
canvasWidgets,
|
||||
dataTree,
|
||||
new Set<string>("one"),
|
||||
"2",
|
||||
),
|
||||
).toEqual(childWidgetTree);
|
||||
});
|
||||
|
||||
it("should return a partial childwidgets Tree with properties specified", () => {
|
||||
const childWidgetTree = [
|
||||
{
|
||||
bottomRow: 100,
|
||||
children: [
|
||||
{
|
||||
bottomRow: 5,
|
||||
children: [],
|
||||
isDirty: true,
|
||||
isLoading: false,
|
||||
isValid: true,
|
||||
parentId: "2",
|
||||
topRow: 4,
|
||||
type: "TEXT",
|
||||
value: "test",
|
||||
widgetId: "3",
|
||||
widgetName: "three",
|
||||
},
|
||||
{
|
||||
bottomRow: 18,
|
||||
children: [],
|
||||
isDirty: true,
|
||||
isLoading: false,
|
||||
isValid: true,
|
||||
parentId: "2",
|
||||
topRow: 6,
|
||||
type: "BUTTON",
|
||||
value: "test",
|
||||
widgetId: "4",
|
||||
widgetName: "four",
|
||||
},
|
||||
],
|
||||
isDirty: true,
|
||||
isLoading: false,
|
||||
isValid: true,
|
||||
parentId: "1",
|
||||
topRow: 0,
|
||||
type: "CANVAS",
|
||||
value: "test",
|
||||
widgetId: "2",
|
||||
widgetName: "two",
|
||||
},
|
||||
];
|
||||
|
||||
expect(
|
||||
buildChildWidgetTree(
|
||||
canvasWidgets,
|
||||
dataTree,
|
||||
new Set<string>("two"),
|
||||
"1",
|
||||
),
|
||||
).toEqual(childWidgetTree);
|
||||
});
|
||||
|
||||
it("should return a partial childwidgets Tree with just loading widgets", () => {
|
||||
const childWidgetTree = [
|
||||
{
|
||||
ENTITY_TYPE: "WIDGET",
|
||||
bindingPaths: {},
|
||||
bottomRow: 100,
|
||||
children: [
|
||||
{
|
||||
ENTITY_TYPE: "WIDGET",
|
||||
bindingPaths: {},
|
||||
bottomRow: 5,
|
||||
children: [],
|
||||
isLoading: false,
|
||||
logBlackList: {},
|
||||
meta: {},
|
||||
overridingPropertyPaths: {},
|
||||
parentId: "2",
|
||||
privateWidgets: {},
|
||||
propertyOverrideDependency: {},
|
||||
reactivePaths: {},
|
||||
topRow: 4,
|
||||
triggerPaths: {},
|
||||
type: undefined,
|
||||
validationPaths: {},
|
||||
widgetId: "3",
|
||||
widgetName: "three",
|
||||
},
|
||||
{
|
||||
ENTITY_TYPE: "WIDGET",
|
||||
bindingPaths: {},
|
||||
bottomRow: 18,
|
||||
children: [],
|
||||
isLoading: false,
|
||||
logBlackList: {},
|
||||
meta: {},
|
||||
overridingPropertyPaths: {},
|
||||
parentId: "2",
|
||||
privateWidgets: {},
|
||||
propertyOverrideDependency: {},
|
||||
reactivePaths: {},
|
||||
topRow: 6,
|
||||
triggerPaths: {},
|
||||
type: undefined,
|
||||
validationPaths: {},
|
||||
widgetId: "4",
|
||||
widgetName: "four",
|
||||
},
|
||||
],
|
||||
isLoading: false,
|
||||
logBlackList: {},
|
||||
meta: {},
|
||||
overridingPropertyPaths: {},
|
||||
parentId: "1",
|
||||
privateWidgets: {},
|
||||
propertyOverrideDependency: {},
|
||||
reactivePaths: {},
|
||||
topRow: 0,
|
||||
triggerPaths: {},
|
||||
type: undefined,
|
||||
validationPaths: {},
|
||||
widgetId: "2",
|
||||
widgetName: "two",
|
||||
},
|
||||
];
|
||||
expect(
|
||||
buildChildWidgetTree(canvasWidgets, {}, new Set<string>("one"), "1"),
|
||||
).toEqual(childWidgetTree);
|
||||
});
|
||||
});
|
||||
});
|
||||
122
app/client/src/utils/widgetRenderUtils.tsx
Normal file
122
app/client/src/utils/widgetRenderUtils.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import {
|
||||
CanvasWidgetsReduxState,
|
||||
FlattenedWidgetProps,
|
||||
} from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import {
|
||||
DataTree,
|
||||
DataTreeWidget,
|
||||
ENTITY_TYPE,
|
||||
} from "entities/DataTree/dataTreeFactory";
|
||||
import { pick } from "lodash";
|
||||
import { WIDGET_STATIC_PROPS } from "constants/WidgetConstants";
|
||||
import WidgetFactory from "./WidgetFactory";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import { LoadingEntitiesState } from "reducers/evaluationReducers/loadingEntitiesReducer";
|
||||
|
||||
export const createCanvasWidget = (
|
||||
canvasWidget: FlattenedWidgetProps,
|
||||
evaluatedWidget: DataTreeWidget,
|
||||
specificChildProps?: string[],
|
||||
) => {
|
||||
const widgetStaticProps = pick(
|
||||
canvasWidget,
|
||||
Object.keys(WIDGET_STATIC_PROPS),
|
||||
);
|
||||
|
||||
//Pick required only contents for specific widgets
|
||||
const evaluatedStaticProps = specificChildProps
|
||||
? pick(evaluatedWidget, specificChildProps)
|
||||
: evaluatedWidget;
|
||||
|
||||
return {
|
||||
...evaluatedStaticProps,
|
||||
...widgetStaticProps,
|
||||
} as DataTreeWidget;
|
||||
};
|
||||
|
||||
const WidgetTypes = WidgetFactory.widgetTypes;
|
||||
export const createLoadingWidget = (
|
||||
canvasWidget: FlattenedWidgetProps,
|
||||
): DataTreeWidget => {
|
||||
const widgetStaticProps = pick(
|
||||
canvasWidget,
|
||||
Object.keys(WIDGET_STATIC_PROPS),
|
||||
) as WidgetProps;
|
||||
return {
|
||||
...widgetStaticProps,
|
||||
type: WidgetTypes.SKELETON_WIDGET,
|
||||
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
|
||||
bindingPaths: {},
|
||||
reactivePaths: {},
|
||||
triggerPaths: {},
|
||||
validationPaths: {},
|
||||
logBlackList: {},
|
||||
isLoading: true,
|
||||
propertyOverrideDependency: {},
|
||||
overridingPropertyPaths: {},
|
||||
privateWidgets: {},
|
||||
meta: {},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Method to build a child widget tree
|
||||
* This method is used to build the child widgets array for widgets like Form, or List widget,
|
||||
* That need to know the state of its child or grandChild to derive properties
|
||||
* This can be replaced with deived properties of the individual widgets
|
||||
*
|
||||
* @param canvasWidgets
|
||||
* @param evaluatedDataTree
|
||||
* @param loadingEntities
|
||||
* @param widgetId
|
||||
* @param requiredWidgetProps
|
||||
* @returns
|
||||
*/
|
||||
export function buildChildWidgetTree(
|
||||
canvasWidgets: CanvasWidgetsReduxState,
|
||||
evaluatedDataTree: DataTree,
|
||||
loadingEntities: LoadingEntitiesState,
|
||||
widgetId: string,
|
||||
requiredWidgetProps?: string[],
|
||||
) {
|
||||
const parentWidget = canvasWidgets[widgetId];
|
||||
|
||||
// specificChildProps are the only properties required by the parent to derive it's properties
|
||||
const specificChildProps =
|
||||
requiredWidgetProps ||
|
||||
getWidgetSpecificChildProps(canvasWidgets[widgetId].type);
|
||||
|
||||
if (parentWidget.children) {
|
||||
return parentWidget.children.map((childWidgetId) => {
|
||||
const childWidget = canvasWidgets[childWidgetId];
|
||||
const evaluatedWidget = evaluatedDataTree[
|
||||
childWidget.widgetName
|
||||
] as DataTreeWidget;
|
||||
const widget = evaluatedWidget
|
||||
? createCanvasWidget(childWidget, evaluatedWidget, specificChildProps)
|
||||
: createLoadingWidget(childWidget);
|
||||
|
||||
widget.isLoading = loadingEntities.has(childWidget.widgetName);
|
||||
|
||||
if (widget?.children?.length > 0) {
|
||||
widget.children = buildChildWidgetTree(
|
||||
canvasWidgets,
|
||||
evaluatedDataTree,
|
||||
loadingEntities,
|
||||
childWidgetId,
|
||||
specificChildProps,
|
||||
);
|
||||
}
|
||||
|
||||
return widget;
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function getWidgetSpecificChildProps(type: string) {
|
||||
if (type === "FORM_WIDGET") {
|
||||
return ["value", "isDirty", "isValid", "isLoading", "children"];
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,9 @@ import { BatchPropertyUpdatePayload } from "actions/controlActions";
|
|||
import AppsmithConsole from "utils/AppsmithConsole";
|
||||
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
||||
import PreviewModeComponent from "components/editorComponents/PreviewModeComponent";
|
||||
import { CanvasWidgetStructure } from "./constants";
|
||||
import { DataTreeWidget } from "entities/DataTree/dataTreeFactory";
|
||||
import Skeleton from "./Skeleton";
|
||||
|
||||
/***
|
||||
* BaseWidget
|
||||
|
|
@ -299,11 +302,32 @@ abstract class BaseWidget<
|
|||
);
|
||||
}
|
||||
|
||||
getWidgetComponent = () => {
|
||||
const { renderMode, type } = this.props;
|
||||
|
||||
/**
|
||||
* The widget mount calls the withWidgetProps with the widgetId and type to fetch the
|
||||
* widget props. During the computation of the props (in withWidgetProps) if the evaluated
|
||||
* values are not present (which will not be during mount), the widget type is changed to
|
||||
* SKELETON_WIDGET.
|
||||
*
|
||||
* Note: This is done to retain the old rendering flow without any breaking changes.
|
||||
* This could be refactored into not changing the widget type but to have a boolean flag.
|
||||
*/
|
||||
if (type === "SKELETON_WIDGET") {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
return renderMode === RenderModes.CANVAS
|
||||
? this.getCanvasView()
|
||||
: this.getPageView();
|
||||
};
|
||||
|
||||
private getWidgetView(): ReactNode {
|
||||
let content: ReactNode;
|
||||
switch (this.props.renderMode) {
|
||||
case RenderModes.CANVAS:
|
||||
content = this.getCanvasView();
|
||||
content = this.getWidgetComponent();
|
||||
content = this.addPreviewModeWidget(content);
|
||||
if (!this.props.detachFromLayout) {
|
||||
if (!this.props.resizeDisabled) content = this.makeResizable(content);
|
||||
|
|
@ -317,7 +341,7 @@ abstract class BaseWidget<
|
|||
|
||||
// return this.getCanvasView();
|
||||
case RenderModes.PAGE:
|
||||
content = this.getPageView();
|
||||
content = this.getWidgetComponent();
|
||||
if (this.props.isVisible) {
|
||||
content = this.addErrorBoundary(content);
|
||||
if (!this.props.detachFromLayout) {
|
||||
|
|
@ -399,7 +423,10 @@ export interface BaseStyle {
|
|||
|
||||
export type WidgetState = Record<string, unknown>;
|
||||
|
||||
export interface WidgetBuilder<T extends WidgetProps, S extends WidgetState> {
|
||||
export interface WidgetBuilder<
|
||||
T extends CanvasWidgetStructure,
|
||||
S extends WidgetState
|
||||
> {
|
||||
buildWidget(widgetProps: T): JSX.Element;
|
||||
}
|
||||
|
||||
|
|
@ -410,6 +437,7 @@ export interface WidgetBaseProps {
|
|||
parentId?: string;
|
||||
renderMode: RenderMode;
|
||||
version: number;
|
||||
childWidgets?: DataTreeWidget[];
|
||||
}
|
||||
|
||||
export type WidgetRowCols = {
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ import { WidgetProps } from "widgets/BaseWidget";
|
|||
import ContainerWidget, {
|
||||
ContainerWidgetProps,
|
||||
} from "widgets/ContainerWidget/widget";
|
||||
import { GridDefaults, RenderModes } from "constants/WidgetConstants";
|
||||
import { GridDefaults } from "constants/WidgetConstants";
|
||||
import DropTargetComponent from "components/editorComponents/DropTargetComponent";
|
||||
import { getCanvasSnapRows } from "utils/WidgetPropsUtils";
|
||||
import { getCanvasClassName } from "utils/generators";
|
||||
import WidgetFactory, { DerivedPropertiesMap } from "utils/WidgetFactory";
|
||||
import { CanvasWidgetStructure } from "./constants";
|
||||
import { CANVAS_DEFAULT_MIN_HEIGHT_PX } from "constants/AppConstants";
|
||||
|
||||
class CanvasWidget extends ContainerWidget {
|
||||
|
|
@ -43,29 +44,19 @@ class CanvasWidget extends ContainerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
renderChildWidget(childWidgetData: WidgetProps): React.ReactNode {
|
||||
renderChildWidget(childWidgetData: CanvasWidgetStructure): React.ReactNode {
|
||||
if (!childWidgetData) return null;
|
||||
// For now, isVisible prop defines whether to render a detached widget
|
||||
if (childWidgetData.detachFromLayout && !childWidgetData.isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We don't render invisible widgets in view mode
|
||||
if (
|
||||
this.props.renderMode === RenderModes.PAGE &&
|
||||
!childWidgetData.isVisible
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const childWidget = { ...childWidgetData };
|
||||
|
||||
const snapSpaces = this.getSnapSpaces();
|
||||
|
||||
childWidgetData.parentColumnSpace = snapSpaces.snapColumnSpace;
|
||||
childWidgetData.parentRowSpace = snapSpaces.snapRowSpace;
|
||||
if (this.props.noPad) childWidgetData.noContainerOffset = true;
|
||||
childWidgetData.parentId = this.props.widgetId;
|
||||
childWidget.parentColumnSpace = snapSpaces.snapColumnSpace;
|
||||
childWidget.parentRowSpace = snapSpaces.snapRowSpace;
|
||||
if (this.props.noPad) childWidget.noContainerOffset = true;
|
||||
childWidget.parentId = this.props.widgetId;
|
||||
|
||||
return WidgetFactory.createWidget(childWidgetData, this.props.renderMode);
|
||||
return WidgetFactory.createWidget(childWidget, this.props.renderMode);
|
||||
}
|
||||
|
||||
getPageView() {
|
||||
|
|
|
|||
|
|
@ -269,25 +269,21 @@ class ContainerWidget extends BaseWidget<
|
|||
};
|
||||
|
||||
renderChildWidget(childWidgetData: WidgetProps): React.ReactNode {
|
||||
// For now, isVisible prop defines whether to render a detached widget
|
||||
if (childWidgetData.detachFromLayout && !childWidgetData.isVisible) {
|
||||
return null;
|
||||
}
|
||||
const childWidget = { ...childWidgetData };
|
||||
|
||||
const { componentHeight, componentWidth } = this.getComponentDimensions();
|
||||
|
||||
childWidgetData.rightColumn = componentWidth;
|
||||
childWidgetData.bottomRow = this.props.shouldScrollContents
|
||||
? childWidgetData.bottomRow
|
||||
childWidget.rightColumn = componentWidth;
|
||||
childWidget.bottomRow = this.props.shouldScrollContents
|
||||
? childWidget.bottomRow
|
||||
: componentHeight;
|
||||
childWidgetData.minHeight = componentHeight;
|
||||
childWidgetData.isVisible = this.props.isVisible;
|
||||
childWidgetData.shouldScrollContents = false;
|
||||
childWidgetData.canExtend = this.props.shouldScrollContents;
|
||||
childWidget.minHeight = componentHeight;
|
||||
childWidget.shouldScrollContents = false;
|
||||
childWidget.canExtend = this.props.shouldScrollContents;
|
||||
|
||||
childWidgetData.parentId = this.props.widgetId;
|
||||
childWidget.parentId = this.props.widgetId;
|
||||
|
||||
return WidgetFactory.createWidget(childWidgetData, this.props.renderMode);
|
||||
return WidgetFactory.createWidget(childWidget, this.props.renderMode);
|
||||
}
|
||||
|
||||
renderChildren = () => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class FormWidget extends ContainerWidget {
|
|||
checkInvalidChildren = (children: WidgetProps[]): boolean => {
|
||||
return some(children, (child) => {
|
||||
if ("children" in child) {
|
||||
return this.checkInvalidChildren(child.children);
|
||||
return this.checkInvalidChildren(child.children || []);
|
||||
}
|
||||
if ("isValid" in child) {
|
||||
return !child.isValid;
|
||||
|
|
@ -29,9 +29,7 @@ class FormWidget extends ContainerWidget {
|
|||
this.updateFormData();
|
||||
|
||||
// Check if the form is dirty
|
||||
const hasChanges = this.checkFormValueChanges(
|
||||
get(this.props, "children[0]"),
|
||||
);
|
||||
const hasChanges = this.checkFormValueChanges(this.getChildContainer());
|
||||
|
||||
if (hasChanges !== this.props.hasChanges) {
|
||||
this.props.updateWidgetMetaProperty("hasChanges", hasChanges);
|
||||
|
|
@ -42,9 +40,7 @@ class FormWidget extends ContainerWidget {
|
|||
super.componentDidUpdate(prevProps);
|
||||
this.updateFormData();
|
||||
// Check if the form is dirty
|
||||
const hasChanges = this.checkFormValueChanges(
|
||||
get(this.props, "children[0]"),
|
||||
);
|
||||
const hasChanges = this.checkFormValueChanges(this.getChildContainer());
|
||||
|
||||
if (hasChanges !== this.props.hasChanges) {
|
||||
this.props.updateWidgetMetaProperty("hasChanges", hasChanges);
|
||||
|
|
@ -60,7 +56,7 @@ class FormWidget extends ContainerWidget {
|
|||
if (!hasChanges) {
|
||||
return childWidgets.some(
|
||||
(child) =>
|
||||
child.children &&
|
||||
child.children?.length &&
|
||||
this.checkFormValueChanges(get(child, "children[0]")),
|
||||
);
|
||||
}
|
||||
|
|
@ -68,8 +64,13 @@ class FormWidget extends ContainerWidget {
|
|||
return hasChanges;
|
||||
}
|
||||
|
||||
getChildContainer = () => {
|
||||
const { childWidgets = [] } = this.props;
|
||||
return { ...childWidgets[0] };
|
||||
};
|
||||
|
||||
updateFormData() {
|
||||
const firstChild = get(this.props, "children[0]");
|
||||
const firstChild = this.getChildContainer();
|
||||
if (firstChild) {
|
||||
const formData = this.getFormData(firstChild);
|
||||
if (!isEqual(formData, this.props.data)) {
|
||||
|
|
@ -89,16 +90,23 @@ class FormWidget extends ContainerWidget {
|
|||
return formData;
|
||||
}
|
||||
|
||||
renderChildWidget(childWidgetData: WidgetProps): React.ReactNode {
|
||||
if (childWidgetData.children) {
|
||||
const isInvalid = this.checkInvalidChildren(childWidgetData.children);
|
||||
childWidgetData.children.forEach((grandChild: WidgetProps) => {
|
||||
if (isInvalid) grandChild.isFormValid = false;
|
||||
// Add submit and reset handlers
|
||||
grandChild.onReset = this.handleResetInputs;
|
||||
});
|
||||
renderChildWidget(): React.ReactNode {
|
||||
const childContainer = this.getChildContainer();
|
||||
|
||||
if (childContainer.children) {
|
||||
const isInvalid = this.checkInvalidChildren(childContainer.children);
|
||||
childContainer.children = childContainer.children.map(
|
||||
(child: WidgetProps) => {
|
||||
const grandChild = { ...child };
|
||||
if (isInvalid) grandChild.isFormValid = false;
|
||||
// Add submit and reset handlers
|
||||
grandChild.onReset = this.handleResetInputs;
|
||||
return grandChild;
|
||||
},
|
||||
);
|
||||
}
|
||||
return super.renderChildWidget(childWidgetData);
|
||||
|
||||
return super.renderChildWidget(childContainer);
|
||||
}
|
||||
|
||||
static getWidgetType(): WidgetType {
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class ListWidget extends BaseWidget<ListWidgetProps<WidgetProps>, WidgetState> {
|
|||
}
|
||||
this.props.updateWidgetMetaProperty(
|
||||
"templateBottomRow",
|
||||
get(this.props.children, "0.children.0.bottomRow"),
|
||||
get(this.props.childWidgets, "0.children.0.bottomRow"),
|
||||
);
|
||||
|
||||
// generate childMetaPropertyMap
|
||||
|
|
@ -268,12 +268,12 @@ class ListWidget extends BaseWidget<ListWidgetProps<WidgetProps>, WidgetState> {
|
|||
}
|
||||
|
||||
if (
|
||||
get(this.props.children, "0.children.0.bottomRow") !==
|
||||
get(prevProps.children, "0.children.0.bottomRow")
|
||||
get(this.props.childWidgets, "0.children.0.bottomRow") !==
|
||||
get(prevProps.childWidgets, "0.children.0.bottomRow")
|
||||
) {
|
||||
this.props.updateWidgetMetaProperty(
|
||||
"templateBottomRow",
|
||||
get(this.props.children, "0.children.0.bottomRow"),
|
||||
get(this.props.childWidgets, "0.children.0.bottomRow"),
|
||||
{
|
||||
triggerPropertyName: "onPageSizeChange",
|
||||
dynamicString: this.props.onPageSizeChange,
|
||||
|
|
@ -663,11 +663,26 @@ class ListWidget extends BaseWidget<ListWidgetProps<WidgetProps>, WidgetState> {
|
|||
return updatedChildren;
|
||||
};
|
||||
|
||||
/**
|
||||
* We add a flag here to not fetch the widgets from the canvasWidgets
|
||||
* in the metaHOC base on the widget id. Rather use the props as is.
|
||||
*/
|
||||
addFlags = (children: DSLWidget[]) => {
|
||||
return (children || []).map((childWidget) => {
|
||||
childWidget.skipWidgetPropsHydration = true;
|
||||
|
||||
childWidget.children = this.addFlags(childWidget?.children || []);
|
||||
|
||||
return childWidget;
|
||||
});
|
||||
};
|
||||
|
||||
updateGridChildrenProps = (children: DSLWidget[]) => {
|
||||
let updatedChildren = this.useNewValues(children);
|
||||
updatedChildren = this.updateActions(updatedChildren);
|
||||
updatedChildren = this.paginateItems(updatedChildren);
|
||||
updatedChildren = this.updatePosition(updatedChildren);
|
||||
updatedChildren = this.addFlags(updatedChildren);
|
||||
|
||||
return updatedChildren;
|
||||
};
|
||||
|
|
@ -707,12 +722,12 @@ class ListWidget extends BaseWidget<ListWidgetProps<WidgetProps>, WidgetState> {
|
|||
*/
|
||||
renderChildren = () => {
|
||||
if (
|
||||
this.props.children &&
|
||||
this.props.children.length > 0 &&
|
||||
this.props.childWidgets &&
|
||||
this.props.childWidgets.length > 0 &&
|
||||
this.props.listData
|
||||
) {
|
||||
const { page } = this.state;
|
||||
const children = removeFalsyEntries(klona(this.props.children));
|
||||
const children = removeFalsyEntries(klona(this.props.childWidgets));
|
||||
const childCanvas = children[0];
|
||||
const { perPage } = this.shouldPaginate();
|
||||
|
||||
|
|
@ -814,7 +829,7 @@ class ListWidget extends BaseWidget<ListWidgetProps<WidgetProps>, WidgetState> {
|
|||
const { pageNo, serverSidePaginationEnabled } = this.props;
|
||||
const { perPage, shouldPaginate } = this.shouldPaginate();
|
||||
const templateBottomRow = get(
|
||||
this.props.children,
|
||||
this.props.childWidgets,
|
||||
"0.children.0.bottomRow",
|
||||
);
|
||||
const templateHeight =
|
||||
|
|
|
|||
|
|
@ -200,18 +200,19 @@ export class ModalWidget extends BaseWidget<ModalWidgetProps, WidgetState> {
|
|||
}
|
||||
|
||||
renderChildWidget = (childWidgetData: WidgetProps): ReactNode => {
|
||||
childWidgetData.parentId = this.props.widgetId;
|
||||
childWidgetData.shouldScrollContents = false;
|
||||
childWidgetData.canExtend = this.props.shouldScrollContents;
|
||||
childWidgetData.bottomRow = this.props.shouldScrollContents
|
||||
? Math.max(childWidgetData.bottomRow, this.props.height)
|
||||
const childData = { ...childWidgetData };
|
||||
childData.parentId = this.props.widgetId;
|
||||
childData.shouldScrollContents = false;
|
||||
childData.canExtend = this.props.shouldScrollContents;
|
||||
childData.bottomRow = this.props.shouldScrollContents
|
||||
? Math.max(childData.bottomRow, this.props.height)
|
||||
: this.props.height;
|
||||
childWidgetData.isVisible = this.props.isVisible;
|
||||
childWidgetData.containerStyle = "none";
|
||||
childWidgetData.minHeight = this.props.height;
|
||||
childWidgetData.rightColumn =
|
||||
childData.containerStyle = "none";
|
||||
childData.minHeight = this.props.height;
|
||||
childData.rightColumn =
|
||||
this.getModalWidth(this.props.width) + WIDGET_PADDING * 2;
|
||||
return WidgetFactory.createWidget(childWidgetData, this.props.renderMode);
|
||||
|
||||
return WidgetFactory.createWidget(childData, this.props.renderMode);
|
||||
};
|
||||
|
||||
onModalClose = () => {
|
||||
|
|
|
|||
|
|
@ -964,6 +964,7 @@ class MultiSelectWidget extends BaseWidget<
|
|||
// Check if defaultOptionValue is string
|
||||
let isStringArray = false;
|
||||
if (
|
||||
this.props.defaultOptionValue &&
|
||||
this.props.defaultOptionValue.some(
|
||||
(value: any) => isString(value) || isFinite(value),
|
||||
)
|
||||
|
|
|
|||
13
app/client/src/widgets/Skeleton.tsx
Normal file
13
app/client/src/widgets/Skeleton.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
export const SkeletonWrapper = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
function Skeleton() {
|
||||
return <SkeletonWrapper className="bp3-skeleton" />;
|
||||
}
|
||||
|
||||
export default Skeleton;
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { WidgetType } from "constants/WidgetConstants";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import ContainerWidget from "widgets/ContainerWidget";
|
||||
|
||||
import { ValidationTypes } from "constants/WidgetValidation";
|
||||
|
|
@ -211,17 +210,6 @@ class StatboxWidget extends ContainerWidget {
|
|||
];
|
||||
}
|
||||
|
||||
renderChildWidget(childWidgetData: WidgetProps): React.ReactNode {
|
||||
if (childWidgetData.children) {
|
||||
childWidgetData.children.forEach((grandChild: WidgetProps) => {
|
||||
if (grandChild.type === "ICON_BUTTON_WIDGET" && !!grandChild.onClick) {
|
||||
grandChild.boxShadow = "VARIANT1";
|
||||
}
|
||||
});
|
||||
}
|
||||
return super.renderChildWidget(childWidgetData);
|
||||
}
|
||||
|
||||
static getWidgetType(): WidgetType {
|
||||
return "STATBOX_WIDGET";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,28 @@ import {
|
|||
widgetCanvasFactory,
|
||||
} from "test/factories/WidgetFactoryUtils";
|
||||
import { render, fireEvent } from "test/testUtils";
|
||||
import * as widgetRenderUtils from "utils/widgetRenderUtils";
|
||||
import * as dataTreeSelectors from "selectors/dataTreeSelectors";
|
||||
import * as editorSelectors from "selectors/editorSelectors";
|
||||
import Canvas from "pages/Editor/Canvas";
|
||||
import React from "react";
|
||||
import { MockPageDSL } from "test/testCommon";
|
||||
import {
|
||||
mockCreateCanvasWidget,
|
||||
mockGetWidgetEvalValues,
|
||||
MockPageDSL,
|
||||
} from "test/testCommon";
|
||||
|
||||
describe("Tabs widget functional cases", () => {
|
||||
jest
|
||||
.spyOn(dataTreeSelectors, "getWidgetEvalValues")
|
||||
.mockImplementation(mockGetWidgetEvalValues);
|
||||
jest
|
||||
.spyOn(editorSelectors, "computeMainContainerWidget")
|
||||
.mockImplementation((widget) => widget as any);
|
||||
jest
|
||||
.spyOn(widgetRenderUtils, "createCanvasWidget")
|
||||
.mockImplementation(mockCreateCanvasWidget);
|
||||
|
||||
it("Should render 2 tabs by default", () => {
|
||||
const children: any = buildChildren([{ type: "TABS_WIDGET" }]);
|
||||
const dsl: any = widgetCanvasFactory.build({
|
||||
|
|
@ -15,7 +32,11 @@ describe("Tabs widget functional cases", () => {
|
|||
});
|
||||
const component = render(
|
||||
<MockPageDSL dsl={dsl}>
|
||||
<Canvas dsl={dsl} pageId="" />
|
||||
<Canvas
|
||||
canvasWidth={dsl.rightColumn}
|
||||
pageId="page_id"
|
||||
widgetsStructure={dsl}
|
||||
/>
|
||||
</MockPageDSL>,
|
||||
);
|
||||
const tab1 = component.queryByText("Tab 1");
|
||||
|
|
@ -41,7 +62,11 @@ describe("Tabs widget functional cases", () => {
|
|||
});
|
||||
const component = render(
|
||||
<MockPageDSL dsl={dsl}>
|
||||
<Canvas dsl={dsl} pageId="" />
|
||||
<Canvas
|
||||
canvasWidth={dsl.rightColumn}
|
||||
pageId="page_id"
|
||||
widgetsStructure={dsl}
|
||||
/>
|
||||
</MockPageDSL>,
|
||||
);
|
||||
const tab1 = component.queryByText("Tab 1");
|
||||
|
|
|
|||
|
|
@ -452,11 +452,11 @@ class TabsWidget extends BaseWidget<
|
|||
|
||||
renderComponent = () => {
|
||||
const selectedTabWidgetId = this.props.selectedTabWidgetId;
|
||||
const childWidgetData: TabContainerWidgetProps = this.props.children
|
||||
?.filter(Boolean)
|
||||
.filter((item) => {
|
||||
const childWidgetData = {
|
||||
...this.props.children?.filter(Boolean).filter((item) => {
|
||||
return selectedTabWidgetId === item.widgetId;
|
||||
})[0];
|
||||
})[0],
|
||||
};
|
||||
if (!childWidgetData) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { IconNames } from "@blueprintjs/icons";
|
||||
import { PropertyPaneConfig } from "constants/PropertyControlConstants";
|
||||
import { WIDGET_STATIC_PROPS } from "constants/WidgetConstants";
|
||||
import { omit } from "lodash";
|
||||
import { WidgetConfigProps } from "reducers/entityReducers/widgetConfigReducer";
|
||||
import { DerivedPropertiesMap } from "utils/WidgetFactory";
|
||||
import { WidgetFeatures } from "utils/WidgetFeatures";
|
||||
|
|
@ -43,6 +45,14 @@ export interface DSLWidget extends WidgetProps {
|
|||
children?: DSLWidget[];
|
||||
}
|
||||
|
||||
const staticProps = omit(WIDGET_STATIC_PROPS, "children");
|
||||
export type CanvasWidgetStructure = Pick<
|
||||
WidgetProps,
|
||||
keyof typeof staticProps
|
||||
> & {
|
||||
children?: CanvasWidgetStructure[];
|
||||
};
|
||||
|
||||
export enum FileDataTypes {
|
||||
Base64 = "Base64",
|
||||
Text = "Text",
|
||||
|
|
|
|||
124
app/client/src/widgets/withWidgetProps.tsx
Normal file
124
app/client/src/widgets/withWidgetProps.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import equal from "fast-deep-equal/es6";
|
||||
import React from "react";
|
||||
|
||||
import BaseWidget, { WidgetProps } from "./BaseWidget";
|
||||
import {
|
||||
MAIN_CONTAINER_WIDGET_ID,
|
||||
RenderModes,
|
||||
} from "constants/WidgetConstants";
|
||||
import {
|
||||
getWidgetEvalValues,
|
||||
getIsWidgetLoading,
|
||||
} from "selectors/dataTreeSelectors";
|
||||
import {
|
||||
getMainCanvasProps,
|
||||
computeMainContainerWidget,
|
||||
getChildWidgets,
|
||||
getRenderMode,
|
||||
} from "selectors/editorSelectors";
|
||||
import { AppState } from "reducers";
|
||||
import { useSelector } from "react-redux";
|
||||
import { getWidget } from "sagas/selectors";
|
||||
import {
|
||||
createCanvasWidget,
|
||||
createLoadingWidget,
|
||||
} from "utils/widgetRenderUtils";
|
||||
|
||||
const WIDGETS_WITH_CHILD_WIDGETS = ["LIST_WIDGET", "FORM_WIDGET"];
|
||||
|
||||
function withWidgetProps(WrappedWidget: typeof BaseWidget) {
|
||||
function WrappedPropsComponent(
|
||||
props: WidgetProps & { skipWidgetPropsHydration?: boolean },
|
||||
) {
|
||||
const { children, skipWidgetPropsHydration, type, widgetId } = props;
|
||||
|
||||
const canvasWidget = useSelector((state: AppState) =>
|
||||
getWidget(state, widgetId),
|
||||
);
|
||||
const mainCanvasProps = useSelector((state: AppState) =>
|
||||
getMainCanvasProps(state),
|
||||
);
|
||||
const renderMode = useSelector(getRenderMode);
|
||||
const evaluatedWidget = useSelector((state: AppState) =>
|
||||
getWidgetEvalValues(state, canvasWidget?.widgetName),
|
||||
);
|
||||
const isLoading = useSelector((state: AppState) =>
|
||||
getIsWidgetLoading(state, canvasWidget?.widgetName),
|
||||
);
|
||||
|
||||
const childWidgets = useSelector((state: AppState) => {
|
||||
if (!WIDGETS_WITH_CHILD_WIDGETS.includes(type)) return undefined;
|
||||
return getChildWidgets(state, widgetId);
|
||||
}, equal);
|
||||
|
||||
let widgetProps: WidgetProps = {} as WidgetProps;
|
||||
|
||||
if (!skipWidgetPropsHydration) {
|
||||
const canvasWidgetProps = (() => {
|
||||
if (widgetId === MAIN_CONTAINER_WIDGET_ID) {
|
||||
return computeMainContainerWidget(canvasWidget, mainCanvasProps);
|
||||
}
|
||||
|
||||
return evaluatedWidget
|
||||
? createCanvasWidget(canvasWidget, evaluatedWidget)
|
||||
: createLoadingWidget(canvasWidget);
|
||||
})();
|
||||
|
||||
widgetProps = { ...canvasWidgetProps };
|
||||
/**
|
||||
* MODAL_WIDGET by default is to be hidden unless the isVisible property is found.
|
||||
* If the isVisible property is undefined and the widget is MODAL_WIDGET then isVisible
|
||||
* is set to false
|
||||
* If the isVisible property is undefined and the widget is not MODAL_WIDGET then isVisible
|
||||
* is set to true
|
||||
*/
|
||||
widgetProps.isVisible =
|
||||
canvasWidgetProps.isVisible ??
|
||||
canvasWidgetProps.type !== "MODAL_WIDGET";
|
||||
|
||||
if (
|
||||
props.type === "CANVAS_WIDGET" &&
|
||||
widgetId !== MAIN_CONTAINER_WIDGET_ID
|
||||
) {
|
||||
widgetProps.rightColumn = props.rightColumn;
|
||||
widgetProps.bottomRow = props.bottomRow;
|
||||
widgetProps.minHeight = props.minHeight;
|
||||
widgetProps.shouldScrollContents = props.shouldScrollContents;
|
||||
widgetProps.canExtend = props.canExtend;
|
||||
widgetProps.parentId = props.parentId;
|
||||
} else if (widgetId !== MAIN_CONTAINER_WIDGET_ID) {
|
||||
widgetProps.parentColumnSpace = props.parentColumnSpace;
|
||||
widgetProps.parentRowSpace = props.parentRowSpace;
|
||||
widgetProps.parentId = props.parentId;
|
||||
|
||||
// Form Widget Props
|
||||
widgetProps.onReset = props.onReset;
|
||||
if ("isFormValid" in props) widgetProps.isFormValid = props.isFormValid;
|
||||
}
|
||||
|
||||
widgetProps.children = children;
|
||||
|
||||
widgetProps.isLoading = isLoading;
|
||||
widgetProps.childWidgets = childWidgets;
|
||||
}
|
||||
|
||||
//merging with original props
|
||||
widgetProps = { ...props, ...widgetProps, renderMode };
|
||||
|
||||
// isVisible prop defines whether to render a detached widget
|
||||
if (widgetProps.detachFromLayout && !widgetProps.isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We don't render invisible widgets in view mode
|
||||
if (renderMode === RenderModes.PAGE && !widgetProps.isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <WrappedWidget {...widgetProps} />;
|
||||
}
|
||||
|
||||
return WrappedPropsComponent;
|
||||
}
|
||||
|
||||
export default withWidgetProps;
|
||||
|
|
@ -141,5 +141,5 @@ export const TabsFactory = Factory.Sync.makeFactory<WidgetProps>({
|
|||
widgetName: Factory.each((i) => `Tabs${i + 1}`),
|
||||
widgetId: generateReactKey(),
|
||||
renderMode: "CANVAS",
|
||||
version: 1,
|
||||
version: 3,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ import { getCanvasWidgets } from "selectors/entitiesSelector";
|
|||
|
||||
import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer";
|
||||
import { DSLWidget } from "widgets/constants";
|
||||
import { DataTreeWidget } from "entities/DataTree/dataTreeFactory";
|
||||
import { AppState } from "reducers";
|
||||
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsStructureReducer";
|
||||
import urlBuilder from "entities/URLRedirect/URLAssembly";
|
||||
|
||||
export const useMockDsl = (dsl: any) => {
|
||||
|
|
@ -87,6 +90,48 @@ export const mockGetCanvasWidgetDsl = createSelector(
|
|||
},
|
||||
);
|
||||
|
||||
const getChildWidgets = (
|
||||
canvasWidgets: CanvasWidgetsReduxState,
|
||||
widgetId: string,
|
||||
) => {
|
||||
const parentWidget = canvasWidgets[widgetId];
|
||||
|
||||
if (parentWidget.children) {
|
||||
return parentWidget.children.map((childWidgetId) => {
|
||||
const childWidget = { ...canvasWidgets[childWidgetId] } as DataTreeWidget;
|
||||
|
||||
if (childWidget?.children?.length > 0) {
|
||||
childWidget.children = getChildWidgets(canvasWidgets, childWidgetId);
|
||||
}
|
||||
|
||||
return childWidget;
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export const mockGetChildWidgets = (state: AppState, widgetId: string) => {
|
||||
return getChildWidgets(state.entities.canvasWidgets, widgetId);
|
||||
};
|
||||
|
||||
export const mockCreateCanvasWidget = (
|
||||
canvasWidget: FlattenedWidgetProps,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
evaluatedWidget: DataTreeWidget,
|
||||
): any => {
|
||||
return { ...canvasWidget };
|
||||
};
|
||||
|
||||
export const mockGetWidgetEvalValues = (
|
||||
state: AppState,
|
||||
widgetName: string,
|
||||
) => {
|
||||
return Object.values(state.entities.canvasWidgets).find(
|
||||
(widget) => widget.widgetName === widgetName,
|
||||
) as DataTreeWidget;
|
||||
};
|
||||
|
||||
export const syntheticTestMouseEvent = (
|
||||
event: MouseEvent,
|
||||
optionsToAdd = {},
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ import Canvas from "pages/Editor/Canvas";
|
|||
import MainContainer from "pages/Editor/MainContainer";
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { mockGetCanvasWidgetDsl, useMockDsl } from "./testCommon";
|
||||
import { getCanvasWidgetsStructure } from "selectors/entitiesSelector";
|
||||
import { useMockDsl } from "./testCommon";
|
||||
|
||||
export function MockCanvas() {
|
||||
const dsl = useSelector(mockGetCanvasWidgetDsl);
|
||||
return <Canvas dsl={dsl} pageId="" />;
|
||||
const canvasWidgetsStructure = useSelector(getCanvasWidgetsStructure);
|
||||
return <Canvas widgetsStructure={canvasWidgetsStructure} />;
|
||||
}
|
||||
export function UpdatedMainContainer({ dsl }: any) {
|
||||
useMockDsl(dsl);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user