[Feature] Widget Grouping Phase - 3 (Cut Copy Paste) (#5083)
* Cut copy paste first cut * removed different parent groups logic * mouseup on the outer canvas removes selections. * bug fix * remove unwanted dead code. * Adding tests * build fix * min height fixes * fixing specs. * fixing specs.
This commit is contained in:
parent
d4dfa836b1
commit
cf19b8e44d
|
|
@ -7,10 +7,17 @@ describe("Canvas Resize", function() {
|
|||
});
|
||||
it("Deleting bottom widget should resize canvas", function() {
|
||||
const InitHeight = "2960px";
|
||||
const FinalHeight = "1292px";
|
||||
cy.get(commonlocators.dropTarget).should("have.css", "height", InitHeight);
|
||||
cy.openPropertyPane("textwidget");
|
||||
cy.intercept("PUT", "/api/v1/layouts/*/pages/*").as("deleteUpdate");
|
||||
cy.get(commonlocators.deleteWidget).click();
|
||||
cy.get(commonlocators.dropTarget).should("have.css", "height", FinalHeight);
|
||||
cy.wait("@deleteUpdate").then((response) => {
|
||||
const dsl = response.response.body.data.dsl;
|
||||
cy.get(commonlocators.dropTarget).should(
|
||||
"have.css",
|
||||
"height",
|
||||
`${dsl.bottomRow}px`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants";
|
||||
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
|
||||
|
||||
export const selectWidgetAction = (
|
||||
widgetId?: string,
|
||||
|
|
@ -16,7 +17,7 @@ export const selectWidgetInitAction = (
|
|||
payload: { widgetId, isMultiSelect },
|
||||
});
|
||||
|
||||
export const selectAllWidgetsAction = (
|
||||
export const selectMultipleWidgetsAction = (
|
||||
widgetIds?: string[],
|
||||
): ReduxAction<{ widgetIds?: string[] }> => {
|
||||
return {
|
||||
|
|
@ -25,7 +26,7 @@ export const selectAllWidgetsAction = (
|
|||
};
|
||||
};
|
||||
|
||||
export const selectMultipleWidgetsAction = (
|
||||
export const silentAddSelectionsAction = (
|
||||
widgetIds?: string[],
|
||||
): ReduxAction<{ widgetIds?: string[] }> => {
|
||||
return {
|
||||
|
|
@ -43,9 +44,21 @@ export const deselectMultipleWidgetsAction = (
|
|||
};
|
||||
};
|
||||
|
||||
export const selectAllWidgetsInitAction = () => {
|
||||
export const selectAllWidgetsInCanvasInitAction = (
|
||||
canvasId = MAIN_CONTAINER_WIDGET_ID,
|
||||
): ReduxAction<{ canvasId: string }> => {
|
||||
return {
|
||||
type: ReduxActionTypes.SELECT_ALL_WIDGETS_IN_CANVAS_INIT,
|
||||
payload: {
|
||||
canvasId,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const selectMultipleWidgetsInitAction = (widgetIds: string[]) => {
|
||||
return {
|
||||
type: ReduxActionTypes.SELECT_MULTIPLE_WIDGETS_INIT,
|
||||
payload: { widgetIds },
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { ComponentProps } from "./BaseComponent";
|
|||
import { invisible } from "constants/DefaultTheme";
|
||||
import { Color } from "constants/Colors";
|
||||
import { generateClassName, getCanvasClassName } from "utils/generators";
|
||||
import { useCanvasMinHeightUpdateHook } from "utils/hooks/useCanvasMinHeightUpdateHook";
|
||||
|
||||
const scrollContents = css`
|
||||
overflow-y: auto;
|
||||
|
|
@ -48,7 +49,7 @@ const StyledContainerComponent = styled.div<
|
|||
function ContainerComponent(props: ContainerComponentProps) {
|
||||
const containerStyle = props.containerStyle || "card";
|
||||
const containerRef: RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
|
||||
|
||||
useCanvasMinHeightUpdateHook(props.widgetId, props.minHeight);
|
||||
useEffect(() => {
|
||||
if (!props.shouldScrollContents) {
|
||||
const supportsNativeSmoothScroll =
|
||||
|
|
@ -89,6 +90,7 @@ export interface ContainerComponentProps extends ComponentProps {
|
|||
resizeDisabled?: boolean;
|
||||
selected?: boolean;
|
||||
focused?: boolean;
|
||||
minHeight?: number;
|
||||
}
|
||||
|
||||
export default ContainerComponent;
|
||||
|
|
|
|||
|
|
@ -306,6 +306,7 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
SELECT_WIDGET: "SELECT_WIDGET",
|
||||
SELECT_MULTIPLE_WIDGETS: "SELECT_MULTIPLE_WIDGETS",
|
||||
SELECT_MULTIPLE_WIDGETS_INIT: "SELECT_MULTIPLE_WIDGETS_INIT",
|
||||
SELECT_ALL_WIDGETS_IN_CANVAS_INIT: "SELECT_ALL_WIDGETS_IN_CANVAS_INIT",
|
||||
DESELECT_WIDGETS: "DESELECT_WIDGETS",
|
||||
SELECT_WIDGETS: "SELECT_WIDGETS",
|
||||
FOCUS_WIDGET: "FOCUS_WIDGET",
|
||||
|
|
|
|||
|
|
@ -103,8 +103,6 @@ export function PageContextMenu(props: {
|
|||
});
|
||||
}
|
||||
|
||||
console.log({ props });
|
||||
|
||||
if (!props.isDefaultPage) {
|
||||
optionTree.push({
|
||||
value: "delete",
|
||||
|
|
|
|||
|
|
@ -1,29 +1,9 @@
|
|||
// These need to be at the top to avoid imports not being mocked. ideally should be in setup.ts but will override for all other tests
|
||||
const mockGenerator = function*() {
|
||||
yield all([]);
|
||||
};
|
||||
|
||||
// top avoid the first middleware run which wud initiate all sagas.
|
||||
jest.mock("sagas", () => ({
|
||||
rootSaga: mockGenerator,
|
||||
}));
|
||||
|
||||
// only the deafault exports are mocked to avoid overriding utilities exported out of them. defaults are marked to avoid worker initiation and page api calls in tests.
|
||||
jest.mock("sagas/EvaluationsSaga", () => ({
|
||||
...jest.requireActual("sagas/EvaluationsSaga"),
|
||||
default: mockGenerator,
|
||||
}));
|
||||
jest.mock("sagas/PageSagas", () => ({
|
||||
...jest.requireActual("sagas/PageSagas"),
|
||||
default: mockGenerator,
|
||||
}));
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
buildChildren,
|
||||
widgetCanvasFactory,
|
||||
} from "test/factories/WidgetFactoryUtils";
|
||||
import { render, fireEvent } from "test/testUtils";
|
||||
import { act, render, fireEvent } from "test/testUtils";
|
||||
import GlobalHotKeys from "./GlobalHotKeys";
|
||||
import MainContainer from "./MainContainer";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
|
|
@ -34,72 +14,316 @@ import { all } from "@redux-saga/core/effects";
|
|||
import {
|
||||
dispatchTestKeyboardEventWithCode,
|
||||
MockApplication,
|
||||
mockGetCanvasWidgetDsl,
|
||||
MockPageDSL,
|
||||
useMockDsl,
|
||||
} from "test/testCommon";
|
||||
const mockGetCanvasWidgetDsl = jest.spyOn(utilities, "getCanvasWidgetDsl");
|
||||
const mockGetIsFetchingPage = jest.spyOn(utilities, "getIsFetchingPage");
|
||||
function UpdatedMainContaner({ dsl }: any) {
|
||||
useMockDsl(dsl);
|
||||
return <MainContainer />;
|
||||
}
|
||||
|
||||
it("Cmd + A - select all widgets on canvas", () => {
|
||||
const children: any = buildChildren([
|
||||
{ type: "TABS_WIDGET" },
|
||||
{ type: "SWITCH_WIDGET" },
|
||||
]);
|
||||
const dsl: any = widgetCanvasFactory.build({
|
||||
children,
|
||||
});
|
||||
mockGetCanvasWidgetDsl.mockImplementation(() => dsl);
|
||||
mockGetIsFetchingPage.mockImplementation(() => false);
|
||||
|
||||
const component = render(
|
||||
<MemoryRouter initialEntries={["/applications/app_id/pages/page_id/edit"]}>
|
||||
<MockApplication>
|
||||
<GlobalHotKeys>
|
||||
<UpdatedMainContaner dsl={dsl} />
|
||||
</GlobalHotKeys>
|
||||
</MockApplication>
|
||||
</MemoryRouter>,
|
||||
{ initialState: store.getState(), sagasToRun: sagasToRunForTests },
|
||||
);
|
||||
let propPane = component.queryByTestId("t--propertypane");
|
||||
expect(propPane).toBeNull();
|
||||
const canvasWidgets = component.queryAllByTestId("test-widget");
|
||||
expect(canvasWidgets.length).toBe(2);
|
||||
if (canvasWidgets[1].firstChild) {
|
||||
fireEvent.mouseOver(canvasWidgets[1].firstChild);
|
||||
fireEvent.click(canvasWidgets[1].firstChild);
|
||||
import { MockCanvas } from "test/testMockedWidgets";
|
||||
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
|
||||
describe("Select all hotkey", () => {
|
||||
const mockGetIsFetchingPage = jest.spyOn(utilities, "getIsFetchingPage");
|
||||
const spyGetCanvasWidgetDsl = jest.spyOn(utilities, "getCanvasWidgetDsl");
|
||||
function UpdatedMainContainer({ dsl }: any) {
|
||||
useMockDsl(dsl);
|
||||
return <MainContainer />;
|
||||
}
|
||||
propPane = component.queryByTestId("t--propertypane");
|
||||
expect(propPane).not.toBeNull();
|
||||
// These need to be at the top to avoid imports not being mocked. ideally should be in setup.ts but will override for all other tests
|
||||
beforeAll(() => {
|
||||
const mockGenerator = function*() {
|
||||
yield all([]);
|
||||
};
|
||||
|
||||
const artBoard: any = component.queryByTestId("t--canvas-artboard");
|
||||
// deselect all other widgets
|
||||
fireEvent.click(artBoard);
|
||||
// top avoid the first middleware run which wud initiate all sagas.
|
||||
jest.mock("sagas", () => ({
|
||||
rootSaga: mockGenerator,
|
||||
}));
|
||||
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"A",
|
||||
65,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
let selectedWidgets = component.queryAllByTestId(
|
||||
"t--widget-propertypane-toggle",
|
||||
);
|
||||
expect(selectedWidgets.length).toBe(2);
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"escape",
|
||||
27,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
selectedWidgets = component.queryAllByTestId("t--widget-propertypane-toggle");
|
||||
expect(selectedWidgets.length).toBe(0);
|
||||
// only the deafault exports are mocked to avoid overriding utilities exported out of them. defaults are marked to avoid worker initiation and page api calls in tests.
|
||||
jest.mock("sagas/EvaluationsSaga", () => ({
|
||||
...jest.requireActual("sagas/EvaluationsSaga"),
|
||||
default: mockGenerator,
|
||||
}));
|
||||
jest.mock("sagas/PageSagas", () => ({
|
||||
...jest.requireActual("sagas/PageSagas"),
|
||||
default: mockGenerator,
|
||||
}));
|
||||
});
|
||||
|
||||
it("Cmd + A - select all widgets on canvas", () => {
|
||||
const children: any = buildChildren([
|
||||
{ type: "TABS_WIDGET" },
|
||||
{ type: "SWITCH_WIDGET" },
|
||||
]);
|
||||
const dsl: any = widgetCanvasFactory.build({
|
||||
children,
|
||||
});
|
||||
spyGetCanvasWidgetDsl.mockImplementation(mockGetCanvasWidgetDsl);
|
||||
mockGetIsFetchingPage.mockImplementation(() => false);
|
||||
|
||||
const component = render(
|
||||
<MemoryRouter
|
||||
initialEntries={["/applications/app_id/pages/page_id/edit"]}
|
||||
>
|
||||
<MockApplication>
|
||||
<GlobalHotKeys>
|
||||
<UpdatedMainContainer dsl={dsl} />
|
||||
</GlobalHotKeys>
|
||||
</MockApplication>
|
||||
</MemoryRouter>,
|
||||
{ initialState: store.getState(), sagasToRun: sagasToRunForTests },
|
||||
);
|
||||
let propPane = component.queryByTestId("t--propertypane");
|
||||
expect(propPane).toBeNull();
|
||||
const canvasWidgets = component.queryAllByTestId("test-widget");
|
||||
expect(canvasWidgets.length).toBe(2);
|
||||
if (canvasWidgets[1].firstChild) {
|
||||
fireEvent.mouseOver(canvasWidgets[1].firstChild);
|
||||
fireEvent.click(canvasWidgets[1].firstChild);
|
||||
}
|
||||
propPane = component.queryByTestId("t--propertypane");
|
||||
expect(propPane).not.toBeNull();
|
||||
|
||||
const artBoard: any = component.queryByTestId("t--canvas-artboard");
|
||||
// deselect all other widgets
|
||||
fireEvent.click(artBoard);
|
||||
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"A",
|
||||
65,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
let selectedWidgets = component.queryAllByTestId(
|
||||
"t--widget-propertypane-toggle",
|
||||
);
|
||||
expect(selectedWidgets.length).toBe(2);
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"escape",
|
||||
27,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
selectedWidgets = component.queryAllByTestId(
|
||||
"t--widget-propertypane-toggle",
|
||||
);
|
||||
expect(selectedWidgets.length).toBe(0);
|
||||
act(() => {
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"A",
|
||||
65,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
selectedWidgets = component.queryAllByTestId(
|
||||
"t--widget-propertypane-toggle",
|
||||
);
|
||||
expect(selectedWidgets.length).toBe(2);
|
||||
act(() => {
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"C",
|
||||
67,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
});
|
||||
act(() => {
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"V",
|
||||
86,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
});
|
||||
selectedWidgets = component.queryAllByTestId(
|
||||
"t--widget-propertypane-toggle",
|
||||
);
|
||||
expect(selectedWidgets.length).toBe(2);
|
||||
});
|
||||
afterAll(() => jest.resetModules());
|
||||
});
|
||||
|
||||
describe("Cut/Copy/Paste hotkey", () => {
|
||||
it("Should copy and paste all selected widgets with hotkey cmd + c and cmd + v ", async () => {
|
||||
const children: any = buildChildren([
|
||||
{
|
||||
type: "TABS_WIDGET",
|
||||
topRow: 5,
|
||||
bottomRow: 30,
|
||||
leftColumn: 5,
|
||||
rightColumn: 30,
|
||||
},
|
||||
{
|
||||
type: "SWITCH_WIDGET",
|
||||
topRow: 5,
|
||||
bottomRow: 10,
|
||||
leftColumn: 40,
|
||||
rightColumn: 48,
|
||||
},
|
||||
]);
|
||||
const dsl: any = widgetCanvasFactory.build({
|
||||
children,
|
||||
});
|
||||
const component = render(
|
||||
<MockPageDSL dsl={dsl}>
|
||||
<GlobalHotKeys>
|
||||
<MockCanvas />
|
||||
</GlobalHotKeys>
|
||||
</MockPageDSL>,
|
||||
);
|
||||
const artBoard: any = await component.queryByTestId("t--canvas-artboard");
|
||||
// deselect all other widgets
|
||||
fireEvent.click(artBoard);
|
||||
act(() => {
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"A",
|
||||
65,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
let selectedWidgets = await component.queryAllByTestId(
|
||||
"t--widget-propertypane-toggle",
|
||||
);
|
||||
expect(selectedWidgets.length).toBe(2);
|
||||
act(() => {
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"C",
|
||||
67,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
});
|
||||
act(() => {
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"V",
|
||||
86,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
});
|
||||
await component.findByText(children[0].widgetName + "Copy");
|
||||
act(() => {
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"A",
|
||||
65,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
selectedWidgets = await component.queryAllByTestId(
|
||||
"t--widget-propertypane-toggle",
|
||||
);
|
||||
expect(selectedWidgets.length).toBe(4);
|
||||
});
|
||||
it("Should cut and paste all selected widgets with hotkey cmd + x and cmd + v ", async () => {
|
||||
const children: any = buildChildren([
|
||||
{
|
||||
type: "TABS_WIDGET",
|
||||
topRow: 5,
|
||||
bottomRow: 30,
|
||||
leftColumn: 5,
|
||||
rightColumn: 30,
|
||||
parentId: MAIN_CONTAINER_WIDGET_ID,
|
||||
},
|
||||
{
|
||||
type: "SWITCH_WIDGET",
|
||||
topRow: 5,
|
||||
bottomRow: 10,
|
||||
leftColumn: 40,
|
||||
rightColumn: 48,
|
||||
parentId: MAIN_CONTAINER_WIDGET_ID,
|
||||
},
|
||||
]);
|
||||
const dsl: any = widgetCanvasFactory.build({
|
||||
children,
|
||||
});
|
||||
const component = render(
|
||||
<MockPageDSL dsl={dsl}>
|
||||
<GlobalHotKeys>
|
||||
<MockCanvas />
|
||||
</GlobalHotKeys>
|
||||
</MockPageDSL>,
|
||||
);
|
||||
const artBoard: any = await component.queryByTestId("t--canvas-artboard");
|
||||
// deselect all other widgets
|
||||
fireEvent.click(artBoard);
|
||||
act(() => {
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"A",
|
||||
65,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
let selectedWidgets = await component.queryAllByTestId(
|
||||
"t--widget-propertypane-toggle",
|
||||
);
|
||||
expect(selectedWidgets.length).toBe(2);
|
||||
act(() => {
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"X",
|
||||
88,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
});
|
||||
await component.findByTestId("canvas-0");
|
||||
selectedWidgets = await component.queryAllByTestId(
|
||||
"t--widget-propertypane-toggle",
|
||||
);
|
||||
expect(selectedWidgets.length).toBe(0);
|
||||
act(() => {
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"V",
|
||||
86,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
});
|
||||
await component.findByText(children[0].widgetName);
|
||||
act(() => {
|
||||
dispatchTestKeyboardEventWithCode(
|
||||
component.container,
|
||||
"keydown",
|
||||
"A",
|
||||
65,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
selectedWidgets = await component.queryAllByTestId(
|
||||
"t--widget-propertypane-toggle",
|
||||
);
|
||||
expect(selectedWidgets.length).toBe(2);
|
||||
});
|
||||
});
|
||||
afterAll(() => jest.resetModules());
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import {
|
|||
pasteWidget,
|
||||
} from "actions/widgetActions";
|
||||
import {
|
||||
selectAllWidgetsInitAction,
|
||||
selectAllWidgetsAction,
|
||||
selectAllWidgetsInCanvasInitAction,
|
||||
selectMultipleWidgetsAction,
|
||||
} from "actions/widgetSelectionActions";
|
||||
import { toggleShowGlobalSearchModal } from "actions/globalSearchActions";
|
||||
import { isMac } from "utils/helpers";
|
||||
|
|
@ -64,12 +64,6 @@ class GlobalHotKeys extends React.Component<Props> {
|
|||
return false;
|
||||
}
|
||||
|
||||
public areMultipleWidgetsSelected() {
|
||||
const multipleWidgetsSelected =
|
||||
this.props.selectedWidgets && this.props.selectedWidgets.length >= 2;
|
||||
return !!multipleWidgetsSelected;
|
||||
}
|
||||
|
||||
public onOnmnibarHotKeyDown(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
this.props.toggleShowGlobalSearchModal();
|
||||
|
|
@ -129,10 +123,7 @@ class GlobalHotKeys extends React.Component<Props> {
|
|||
group="Canvas"
|
||||
label="Copy Widget"
|
||||
onKeyDown={(e: any) => {
|
||||
if (
|
||||
this.stopPropagationIfWidgetSelected(e) &&
|
||||
!this.areMultipleWidgetsSelected()
|
||||
) {
|
||||
if (this.stopPropagationIfWidgetSelected(e)) {
|
||||
this.props.copySelectedWidget();
|
||||
}
|
||||
}}
|
||||
|
|
@ -174,10 +165,7 @@ class GlobalHotKeys extends React.Component<Props> {
|
|||
group="Canvas"
|
||||
label="Cut Widget"
|
||||
onKeyDown={(e: any) => {
|
||||
if (
|
||||
this.stopPropagationIfWidgetSelected(e) &&
|
||||
!this.areMultipleWidgetsSelected()
|
||||
) {
|
||||
if (this.stopPropagationIfWidgetSelected(e)) {
|
||||
this.props.cutSelectedWidget();
|
||||
}
|
||||
}}
|
||||
|
|
@ -250,8 +238,8 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||
resetCommentMode: () => dispatch(setCommentModeAction(false)),
|
||||
openDebugger: () => dispatch(showDebugger()),
|
||||
closeProppane: () => dispatch(closePropertyPane()),
|
||||
selectAllWidgetsInit: () => dispatch(selectAllWidgetsInitAction()),
|
||||
deselectAllWidgets: () => dispatch(selectAllWidgetsAction([])),
|
||||
selectAllWidgetsInit: () => dispatch(selectAllWidgetsInCanvasInitAction()),
|
||||
deselectAllWidgets: () => dispatch(selectMultipleWidgetsAction([])),
|
||||
executeAction: () => dispatch(runActionViaShortcut()),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -158,10 +158,12 @@ export const CanvasSelectionArena = memo(
|
|||
|
||||
const onMouseLeave = () => {
|
||||
document.body.addEventListener("mouseup", onMouseUp, false);
|
||||
document.body.addEventListener("click", onClick, false);
|
||||
};
|
||||
|
||||
const onMouseEnter = () => {
|
||||
document.body.removeEventListener("mouseup", onMouseUp);
|
||||
document.body.removeEventListener("click", onClick);
|
||||
};
|
||||
|
||||
const onClick = (e: any) => {
|
||||
|
|
@ -236,6 +238,7 @@ export const CanvasSelectionArena = memo(
|
|||
currentPageId,
|
||||
mainContainer.rightColumn,
|
||||
mainContainer.bottomRow,
|
||||
mainContainer.minHeight,
|
||||
]);
|
||||
|
||||
return appMode === APP_MODE.EDIT ? (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { selectAllWidgetsAction } from "actions/widgetSelectionActions";
|
||||
import { selectMultipleWidgetsAction } from "actions/widgetSelectionActions";
|
||||
import { OccupiedSpace } from "constants/editorConstants";
|
||||
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import {
|
||||
|
|
@ -99,7 +99,7 @@ function* selectAllWidgetsInAreaSaga(
|
|||
const currentSelectedWidgets: string[] = yield select(getSelectedWidgets);
|
||||
|
||||
if (!isEqual(filteredWidgetsToSelect, currentSelectedWidgets)) {
|
||||
yield put(selectAllWidgetsAction(filteredWidgetsToSelect));
|
||||
yield put(selectMultipleWidgetsAction(filteredWidgetsToSelect));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,7 @@ import {
|
|||
CanvasWidgetsReduxState,
|
||||
FlattenedWidgetProps,
|
||||
} from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import {
|
||||
getSelectedWidget,
|
||||
getWidget,
|
||||
getWidgetMetaProps,
|
||||
getWidgets,
|
||||
} from "./selectors";
|
||||
import { getSelectedWidget, getWidget, getWidgets } from "./selectors";
|
||||
import {
|
||||
generateWidgetProps,
|
||||
updateWidgetPosition,
|
||||
|
|
@ -93,7 +88,10 @@ import {
|
|||
closePropertyPane,
|
||||
forceOpenPropertyPane,
|
||||
} from "actions/widgetActions";
|
||||
import { selectWidgetInitAction } from "actions/widgetSelectionActions";
|
||||
import {
|
||||
selectMultipleWidgetsInitAction,
|
||||
selectWidgetInitAction,
|
||||
} from "actions/widgetSelectionActions";
|
||||
|
||||
import { getDataTree } from "selectors/dataTreeSelectors";
|
||||
import {
|
||||
|
|
@ -124,7 +122,9 @@ import AppsmithConsole from "utils/AppsmithConsole";
|
|||
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
||||
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
||||
import {
|
||||
checkIfPastingIntoListWidget,
|
||||
doesTriggerPathsContainPropertyPath,
|
||||
getParentWidgetIdForPasting,
|
||||
getWidgetChildren,
|
||||
handleSpecificCasesWhilePasting,
|
||||
} from "./WidgetOperationUtils";
|
||||
|
|
@ -443,7 +443,10 @@ const resizeCanvasToLowestWidget = (
|
|||
return;
|
||||
}
|
||||
|
||||
let lowestBottomRow = 0;
|
||||
let lowestBottomRow = Math.ceil(
|
||||
(finalWidgets[parentId].minHeight || 0) /
|
||||
GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
||||
);
|
||||
const childIds = finalWidgets[parentId].children || [];
|
||||
// find lowest row
|
||||
childIds.forEach((cId) => {
|
||||
|
|
@ -492,6 +495,9 @@ export function* deleteAllSelectedWidgetsSaga(
|
|||
parentUpdatedWidgets,
|
||||
falttendedWidgets.map((widgets: any) => widgets.widgetId),
|
||||
);
|
||||
// assuming only widgets with same parent can be selected
|
||||
const parentId = widgets[selectedWidgets[0]].parentId;
|
||||
resizeCanvasToLowestWidget(finalWidgets, parentId);
|
||||
|
||||
yield put(updateAndSaveLayout(finalWidgets));
|
||||
yield put(selectWidgetInitAction(""));
|
||||
|
|
@ -815,8 +821,15 @@ export function* undoDeleteSaga(action: ReduxAction<{ widgetId: string }>) {
|
|||
},
|
||||
stateWidgets,
|
||||
);
|
||||
|
||||
const parentId = deletedWidgets[0].parentId;
|
||||
if (parentId) {
|
||||
resizeCanvasToLowestWidget(finalWidgets, parentId);
|
||||
}
|
||||
yield put(updateAndSaveLayout(finalWidgets));
|
||||
deletedWidgetIds.forEach((widgetId) => {
|
||||
setTimeout(() => flashElementById(widgetId), 100);
|
||||
});
|
||||
yield put(selectMultipleWidgetsInitAction(deletedWidgetIds));
|
||||
if (deletedWidgetIds.length === 1) {
|
||||
yield put(forceOpenPropertyPane(action.payload.widgetId));
|
||||
}
|
||||
|
|
@ -1319,14 +1332,26 @@ function* updateCanvasSize(
|
|||
}
|
||||
}
|
||||
|
||||
function* createWidgetCopy() {
|
||||
const selectedWidget = yield select(getSelectedWidget);
|
||||
if (!selectedWidget) return;
|
||||
const widgets = yield select(getWidgets);
|
||||
const widgetsToStore = getAllWidgetsInTree(selectedWidget.widgetId, widgets);
|
||||
return yield saveCopiedWidgets(
|
||||
JSON.stringify({ widgetId: selectedWidget.widgetId, list: widgetsToStore }),
|
||||
function* createWidgetCopy(widget: FlattenedWidgetProps) {
|
||||
const allWidgets: { [widgetId: string]: FlattenedWidgetProps } = yield select(
|
||||
getWidgets,
|
||||
);
|
||||
const widgetsToStore = getAllWidgetsInTree(widget.widgetId, allWidgets);
|
||||
return {
|
||||
widgetId: widget.widgetId,
|
||||
list: widgetsToStore,
|
||||
parentId: widget.parentId,
|
||||
};
|
||||
}
|
||||
|
||||
function* createSelectedWidgetsCopy(selectedWidgets: FlattenedWidgetProps[]) {
|
||||
if (!selectedWidgets || !selectedWidgets.length) return;
|
||||
const widgetListsToStore: {
|
||||
widgetId: string;
|
||||
parentId: string;
|
||||
list: FlattenedWidgetProps[];
|
||||
}[] = yield all(selectedWidgets.map((each) => call(createWidgetCopy, each)));
|
||||
return yield saveCopiedWidgets(JSON.stringify(widgetListsToStore));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1337,8 +1362,11 @@ function* createWidgetCopy() {
|
|||
* @returns
|
||||
*/
|
||||
function* copyWidgetSaga(action: ReduxAction<{ isShortcut: boolean }>) {
|
||||
const selectedWidget = yield select(getSelectedWidget);
|
||||
if (!selectedWidget) {
|
||||
const allWidgets: { [widgetId: string]: FlattenedWidgetProps } = yield select(
|
||||
getWidgets,
|
||||
);
|
||||
const selectedWidgets: string[] = yield select(getSelectedWidgets);
|
||||
if (!selectedWidgets) {
|
||||
Toaster.show({
|
||||
text: createMessage(ERROR_WIDGET_COPY_NO_WIDGET_SELECTED),
|
||||
variant: Variant.info,
|
||||
|
|
@ -1346,7 +1374,11 @@ function* copyWidgetSaga(action: ReduxAction<{ isShortcut: boolean }>) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (selectedWidget.disallowCopy === true) {
|
||||
const allAllowedToCopy = selectedWidgets.some((each) => {
|
||||
return !allWidgets[each].disallowCopy;
|
||||
});
|
||||
|
||||
if (!allAllowedToCopy) {
|
||||
Toaster.show({
|
||||
text: createMessage(ERROR_WIDGET_COPY_NOT_ALLOWED),
|
||||
variant: Variant.info,
|
||||
|
|
@ -1354,20 +1386,28 @@ function* copyWidgetSaga(action: ReduxAction<{ isShortcut: boolean }>) {
|
|||
|
||||
return;
|
||||
}
|
||||
const selectedWidgetProps = selectedWidgets.map((each) => allWidgets[each]);
|
||||
|
||||
const saveResult = yield createWidgetCopy();
|
||||
const saveResult = yield createSelectedWidgetsCopy(selectedWidgetProps);
|
||||
|
||||
const eventName = action.payload.isShortcut
|
||||
? "WIDGET_COPY_VIA_SHORTCUT"
|
||||
: "WIDGET_COPY";
|
||||
AnalyticsUtil.logEvent(eventName, {
|
||||
widgetName: selectedWidget.widgetName,
|
||||
widgetType: selectedWidget.type,
|
||||
selectedWidgetProps.forEach((each) => {
|
||||
const eventName = action.payload.isShortcut
|
||||
? "WIDGET_COPY_VIA_SHORTCUT"
|
||||
: "WIDGET_COPY";
|
||||
AnalyticsUtil.logEvent(eventName, {
|
||||
widgetName: each.widgetName,
|
||||
widgetType: each.type,
|
||||
});
|
||||
});
|
||||
|
||||
if (saveResult) {
|
||||
Toaster.show({
|
||||
text: createMessage(WIDGET_COPY, selectedWidget.widgetName),
|
||||
text: createMessage(
|
||||
WIDGET_COPY,
|
||||
selectedWidgetProps.length > 1
|
||||
? `${selectedWidgetProps.length} Widgets`
|
||||
: selectedWidgetProps[0].widgetName,
|
||||
),
|
||||
variant: Variant.success,
|
||||
});
|
||||
}
|
||||
|
|
@ -1377,16 +1417,26 @@ export function calculateNewWidgetPosition(
|
|||
widget: WidgetProps,
|
||||
parentId: string,
|
||||
canvasWidgets: { [widgetId: string]: FlattenedWidgetProps },
|
||||
parentBottomRow?: number,
|
||||
persistColumnPosition = false,
|
||||
) {
|
||||
// Note: This is a very simple algorithm.
|
||||
// We take the bottom most widget in the canvas, then calculate the top,left,right,bottom
|
||||
// co-ordinates for the new widget, such that it can be placed at the bottom of the canvas.
|
||||
const nextAvailableRow = nextAvailableRowInContainer(parentId, canvasWidgets);
|
||||
const nextAvailableRow = parentBottomRow
|
||||
? parentBottomRow
|
||||
: nextAvailableRowInContainer(parentId, canvasWidgets);
|
||||
return {
|
||||
leftColumn: 0,
|
||||
rightColumn: widget.rightColumn - widget.leftColumn,
|
||||
topRow: nextAvailableRow,
|
||||
bottomRow: nextAvailableRow + (widget.bottomRow - widget.topRow),
|
||||
leftColumn: persistColumnPosition ? widget.leftColumn : 0,
|
||||
rightColumn: persistColumnPosition
|
||||
? widget.rightColumn
|
||||
: widget.rightColumn - widget.leftColumn,
|
||||
topRow: parentBottomRow
|
||||
? nextAvailableRow + widget.topRow
|
||||
: nextAvailableRow,
|
||||
bottomRow: parentBottomRow
|
||||
? nextAvailableRow + widget.bottomRow
|
||||
: nextAvailableRow + (widget.bottomRow - widget.topRow),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -1398,7 +1448,12 @@ function* getEntityNames() {
|
|||
function getNextWidgetName(
|
||||
widgets: CanvasWidgetsReduxState,
|
||||
type: WidgetType,
|
||||
evalTree: Record<string, unknown>,
|
||||
evalTree: {
|
||||
bottomRow: any;
|
||||
leftColumn: any;
|
||||
rightColumn: any;
|
||||
topRow: any;
|
||||
},
|
||||
options?: Record<string, unknown>,
|
||||
) {
|
||||
// Compute the new widget's name
|
||||
|
|
@ -1423,286 +1478,274 @@ function getNextWidgetName(
|
|||
* this saga create a new widget from the copied one to store
|
||||
*/
|
||||
function* pasteWidgetSaga() {
|
||||
const copiedWidgets: {
|
||||
const copiedWidgetGroups: {
|
||||
widgetId: string;
|
||||
parentId: string;
|
||||
list: WidgetProps[];
|
||||
} = yield getCopiedWidgets();
|
||||
// Don't try to paste if there is no copied widget
|
||||
if (!copiedWidgets) return;
|
||||
const copiedWidgetId = copiedWidgets.widgetId;
|
||||
const copiedWidget = copiedWidgets.list.find(
|
||||
(widget) => widget.widgetId === copiedWidgetId,
|
||||
}[] = yield getCopiedWidgets();
|
||||
if (!Array.isArray(copiedWidgetGroups)) {
|
||||
return;
|
||||
// to avoid invoking old copied widgets
|
||||
}
|
||||
const stateWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
|
||||
let selectedWidget: FlattenedWidgetProps | undefined = yield select(
|
||||
getSelectedWidget,
|
||||
);
|
||||
if (copiedWidget) {
|
||||
// Log the paste event
|
||||
AnalyticsUtil.logEvent("WIDGET_PASTE", {
|
||||
widgetName: copiedWidget.widgetName,
|
||||
widgetType: copiedWidget.type,
|
||||
});
|
||||
|
||||
const stateWidgets = yield select(getWidgets);
|
||||
let widgets = { ...stateWidgets };
|
||||
const pastingIntoWidgetId: string = yield getParentWidgetIdForPasting(
|
||||
{ ...stateWidgets },
|
||||
selectedWidget,
|
||||
);
|
||||
|
||||
let selectedWidget = yield select(getSelectedWidget);
|
||||
|
||||
// when list widget is selected, if the user is pasting, we want it to be pasted in the template
|
||||
// which is first children of list widget
|
||||
if (selectedWidget?.type === WidgetTypes.LIST_WIDGET) {
|
||||
const childrenIds: string[] = yield call(
|
||||
getWidgetChildren,
|
||||
selectedWidget.children[0],
|
||||
);
|
||||
const firstChildId = childrenIds[0];
|
||||
|
||||
selectedWidget = yield select(getWidget, firstChildId);
|
||||
}
|
||||
|
||||
let newWidgetParentId = MAIN_CONTAINER_WIDGET_ID;
|
||||
let parentWidget = widgets[MAIN_CONTAINER_WIDGET_ID];
|
||||
|
||||
// If the selected widget is not the main container
|
||||
if (
|
||||
selectedWidget &&
|
||||
selectedWidget.widgetId !== MAIN_CONTAINER_WIDGET_ID
|
||||
) {
|
||||
// Select the parent of the selected widget if parent is not
|
||||
// the main container
|
||||
if (
|
||||
selectedWidget.parentId !== MAIN_CONTAINER_WIDGET_ID &&
|
||||
widgets[selectedWidget.parentId] &&
|
||||
widgets[selectedWidget.parentId].children &&
|
||||
widgets[selectedWidget.parentId].children.length > 0
|
||||
) {
|
||||
parentWidget = widgets[selectedWidget.parentId];
|
||||
newWidgetParentId = selectedWidget.parentId;
|
||||
}
|
||||
// Select the selected widget if the widget is container like
|
||||
if (selectedWidget.children) {
|
||||
parentWidget = widgets[selectedWidget.widgetId];
|
||||
}
|
||||
}
|
||||
|
||||
// If the parent widget in which to paste the copied widget
|
||||
// is not the main container and is not a canvas widget
|
||||
if (
|
||||
parentWidget.widgetId !== MAIN_CONTAINER_WIDGET_ID &&
|
||||
parentWidget.type !== WidgetTypes.CANVAS_WIDGET
|
||||
) {
|
||||
let childWidget;
|
||||
// If the widget in which to paste the new widget is NOT
|
||||
// a tabs widget
|
||||
if (parentWidget.type !== WidgetTypes.TABS_WIDGET) {
|
||||
// The child will be a CANVAS_WIDGET, as we've established
|
||||
// this parent widget to be a container like widget
|
||||
// Which always has its first child as a canvas widget
|
||||
childWidget = widgets[parentWidget.children[0]];
|
||||
} else {
|
||||
// If the widget in which to paste the new widget is a tabs widget
|
||||
// Find the currently selected tab canvas widget
|
||||
const { selectedTabWidgetId } = yield select(
|
||||
getWidgetMetaProps,
|
||||
parentWidget.widgetId,
|
||||
selectedWidget = yield checkIfPastingIntoListWidget(selectedWidget);
|
||||
let widgets = { ...stateWidgets };
|
||||
const newlyCreatedWidgetIds: string[] = [];
|
||||
const sortedWidgetList = copiedWidgetGroups.sort(
|
||||
(a, b) => a.list[0].topRow - b.list[0].topRow,
|
||||
);
|
||||
const copiedGroupTopRow = sortedWidgetList[0].list[0].topRow;
|
||||
const nextAvailableRow: number = nextAvailableRowInContainer(
|
||||
pastingIntoWidgetId,
|
||||
widgets,
|
||||
);
|
||||
yield all(
|
||||
copiedWidgetGroups.map((copiedWidgets) =>
|
||||
call(function*() {
|
||||
// Don't try to paste if there is no copied widget
|
||||
if (!copiedWidgets) return;
|
||||
const copiedWidgetId = copiedWidgets.widgetId;
|
||||
const unUpdatedCopyOfWidget = copiedWidgets.list.find(
|
||||
(widget) => widget.widgetId === copiedWidgetId,
|
||||
);
|
||||
if (selectedTabWidgetId) childWidget = widgets[selectedTabWidgetId];
|
||||
}
|
||||
// If the finally selected parent in which to paste the widget
|
||||
// is a CANVAS_WIDGET, use its widgetId as the new widget's parent Id
|
||||
if (childWidget && childWidget.type === WidgetTypes.CANVAS_WIDGET) {
|
||||
newWidgetParentId = childWidget.widgetId;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the new widget's positional properties
|
||||
const {
|
||||
bottomRow,
|
||||
leftColumn,
|
||||
rightColumn,
|
||||
topRow,
|
||||
} = yield calculateNewWidgetPosition(
|
||||
copiedWidget,
|
||||
newWidgetParentId,
|
||||
widgets,
|
||||
);
|
||||
if (unUpdatedCopyOfWidget) {
|
||||
const copiedWidget = {
|
||||
...unUpdatedCopyOfWidget,
|
||||
topRow: unUpdatedCopyOfWidget.topRow - copiedGroupTopRow,
|
||||
bottomRow: unUpdatedCopyOfWidget.bottomRow - copiedGroupTopRow,
|
||||
};
|
||||
|
||||
const evalTree = yield select(getDataTree);
|
||||
// Log the paste event
|
||||
AnalyticsUtil.logEvent("WIDGET_PASTE", {
|
||||
widgetName: copiedWidget.widgetName,
|
||||
widgetType: copiedWidget.type,
|
||||
});
|
||||
|
||||
// Get a flat list of all the widgets to be updated
|
||||
const widgetList = copiedWidgets.list;
|
||||
const widgetIdMap: Record<string, string> = {};
|
||||
const widgetNameMap: Record<string, string> = {};
|
||||
const newWidgetList: FlattenedWidgetProps[] = [];
|
||||
let newWidgetId: string = copiedWidget.widgetId;
|
||||
// Generate new widgetIds for the flat list of all the widgets to be updated
|
||||
widgetList.forEach((widget) => {
|
||||
// Create a copy of the widget properties
|
||||
const newWidget = cloneDeep(widget);
|
||||
newWidget.widgetId = generateReactKey();
|
||||
// Add the new widget id so that it maps the previous widget id
|
||||
widgetIdMap[widget.widgetId] = newWidget.widgetId;
|
||||
|
||||
// Add the new widget to the list
|
||||
newWidgetList.push(newWidget);
|
||||
});
|
||||
|
||||
// For each of the new widgets generated
|
||||
for (let i = 0; i < newWidgetList.length; i++) {
|
||||
const widget = newWidgetList[i];
|
||||
const oldWidgetName = widget.widgetName;
|
||||
|
||||
// Update the children widgetIds if it has children
|
||||
if (widget.children && widget.children.length > 0) {
|
||||
widget.children.forEach((childWidgetId: string, index: number) => {
|
||||
if (widget.children) {
|
||||
widget.children[index] = widgetIdMap[childWidgetId];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update the tabs for the tabs widget.
|
||||
if (widget.tabsObj && widget.type === WidgetTypes.TABS_WIDGET) {
|
||||
try {
|
||||
const tabs = Object.values(widget.tabsObj);
|
||||
if (Array.isArray(tabs)) {
|
||||
widget.tabsObj = tabs.reduce((obj: any, tab) => {
|
||||
tab.widgetId = widgetIdMap[tab.widgetId];
|
||||
obj[tab.id] = tab;
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
} catch (error) {
|
||||
log.debug("Error updating tabs", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the table widget column properties
|
||||
if (widget.type === WidgetTypes.TABLE_WIDGET) {
|
||||
try {
|
||||
const oldWidgetName = widget.widgetName;
|
||||
const newWidgetName = getNextWidgetName(
|
||||
// Compute the new widget's positional properties
|
||||
const {
|
||||
bottomRow,
|
||||
leftColumn,
|
||||
rightColumn,
|
||||
topRow,
|
||||
} = yield calculateNewWidgetPosition(
|
||||
copiedWidget,
|
||||
pastingIntoWidgetId,
|
||||
widgets,
|
||||
widget.type,
|
||||
evalTree,
|
||||
nextAvailableRow,
|
||||
true,
|
||||
);
|
||||
// If the primaryColumns of the table exist
|
||||
if (widget.primaryColumns) {
|
||||
// For each column
|
||||
for (const [columnId, column] of Object.entries(
|
||||
widget.primaryColumns,
|
||||
)) {
|
||||
// For each property in the column
|
||||
for (const [key, value] of Object.entries(
|
||||
column as ColumnProperties,
|
||||
)) {
|
||||
// Replace reference of previous widget with the new widgetName
|
||||
// This handles binding scenarios like `{{Table2.tableData.map((currentRow) => (currentRow.id))}}`
|
||||
widget.primaryColumns[columnId][key] = isString(value)
|
||||
? value.replace(`${oldWidgetName}.`, `${newWidgetName}.`)
|
||||
: value;
|
||||
// goToNextAvailableRow = true,
|
||||
// persistColumnPosition = false,
|
||||
|
||||
const evalTree = yield select(getDataTree);
|
||||
|
||||
// Get a flat list of all the widgets to be updated
|
||||
const widgetList = copiedWidgets.list;
|
||||
const widgetIdMap: Record<string, string> = {};
|
||||
const widgetNameMap: Record<string, string> = {};
|
||||
const newWidgetList: FlattenedWidgetProps[] = [];
|
||||
let newWidgetId: string = copiedWidget.widgetId;
|
||||
// Generate new widgetIds for the flat list of all the widgets to be updated
|
||||
widgetList.forEach((widget) => {
|
||||
// Create a copy of the widget properties
|
||||
const newWidget = cloneDeep(widget);
|
||||
newWidget.widgetId = generateReactKey();
|
||||
// Add the new widget id so that it maps the previous widget id
|
||||
widgetIdMap[widget.widgetId] = newWidget.widgetId;
|
||||
|
||||
// Add the new widget to the list
|
||||
newWidgetList.push(newWidget);
|
||||
});
|
||||
|
||||
// For each of the new widgets generated
|
||||
for (let i = 0; i < newWidgetList.length; i++) {
|
||||
const widget = newWidgetList[i];
|
||||
const oldWidgetName = widget.widgetName;
|
||||
|
||||
// Update the children widgetIds if it has children
|
||||
if (widget.children && widget.children.length > 0) {
|
||||
widget.children.forEach(
|
||||
(childWidgetId: string, index: number) => {
|
||||
if (widget.children) {
|
||||
widget.children[index] = widgetIdMap[childWidgetId];
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Update the tabs for the tabs widget.
|
||||
if (widget.tabsObj && widget.type === WidgetTypes.TABS_WIDGET) {
|
||||
try {
|
||||
const tabs = Object.values(widget.tabsObj);
|
||||
if (Array.isArray(tabs)) {
|
||||
widget.tabsObj = tabs.reduce((obj: any, tab) => {
|
||||
tab.widgetId = widgetIdMap[tab.widgetId];
|
||||
obj[tab.id] = tab;
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
} catch (error) {
|
||||
log.debug("Error updating tabs", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use the new widget name we used to replace the column properties above.
|
||||
widget.widgetName = newWidgetName;
|
||||
} catch (error) {
|
||||
log.debug("Error updating table widget properties", error);
|
||||
}
|
||||
}
|
||||
|
||||
// If it is the copied widget, update position properties
|
||||
if (widget.widgetId === widgetIdMap[copiedWidget.widgetId]) {
|
||||
newWidgetId = widget.widgetId;
|
||||
widget.leftColumn = leftColumn;
|
||||
widget.topRow = topRow;
|
||||
widget.bottomRow = bottomRow;
|
||||
widget.rightColumn = rightColumn;
|
||||
widget.parentId = newWidgetParentId;
|
||||
// Also, update the parent widget in the canvas widgets
|
||||
// to include this new copied widget's id in the parent's children
|
||||
let parentChildren = [widget.widgetId];
|
||||
if (
|
||||
widgets[newWidgetParentId].children &&
|
||||
Array.isArray(widgets[newWidgetParentId].children)
|
||||
) {
|
||||
// Add the new child to existing children
|
||||
parentChildren = parentChildren.concat(
|
||||
widgets[newWidgetParentId].children,
|
||||
);
|
||||
}
|
||||
widgets = {
|
||||
...widgets,
|
||||
[newWidgetParentId]: {
|
||||
...widgets[newWidgetParentId],
|
||||
children: parentChildren,
|
||||
},
|
||||
};
|
||||
// If the copied widget's boundaries exceed the parent's
|
||||
// Make the parent scrollable
|
||||
if (
|
||||
widgets[newWidgetParentId].bottomRow *
|
||||
widgets[widget.parentId].parentRowSpace <=
|
||||
widget.bottomRow * widget.parentRowSpace
|
||||
) {
|
||||
if (widget.parentId !== MAIN_CONTAINER_WIDGET_ID) {
|
||||
const parent = widgets[widgets[newWidgetParentId].parentId];
|
||||
widgets[widgets[newWidgetParentId].parentId] = {
|
||||
...parent,
|
||||
shouldScrollContents: true,
|
||||
};
|
||||
// Update the table widget column properties
|
||||
if (widget.type === WidgetTypes.TABLE_WIDGET) {
|
||||
try {
|
||||
const oldWidgetName = widget.widgetName;
|
||||
const newWidgetName = getNextWidgetName(
|
||||
widgets,
|
||||
widget.type,
|
||||
evalTree,
|
||||
);
|
||||
// If the primaryColumns of the table exist
|
||||
if (widget.primaryColumns) {
|
||||
// For each column
|
||||
for (const [columnId, column] of Object.entries(
|
||||
widget.primaryColumns,
|
||||
)) {
|
||||
// For each property in the column
|
||||
for (const [key, value] of Object.entries(
|
||||
column as ColumnProperties,
|
||||
)) {
|
||||
// Replace reference of previous widget with the new widgetName
|
||||
// This handles binding scenarios like `{{Table2.tableData.map((currentRow) => (currentRow.id))}}`
|
||||
widget.primaryColumns[columnId][key] = isString(value)
|
||||
? value.replace(
|
||||
`${oldWidgetName}.`,
|
||||
`${newWidgetName}.`,
|
||||
)
|
||||
: value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use the new widget name we used to replace the column properties above.
|
||||
widget.widgetName = newWidgetName;
|
||||
} catch (error) {
|
||||
log.debug("Error updating table widget properties", error);
|
||||
}
|
||||
}
|
||||
|
||||
// If it is the copied widget, update position properties
|
||||
if (widget.widgetId === widgetIdMap[copiedWidget.widgetId]) {
|
||||
newWidgetId = widget.widgetId;
|
||||
widget.leftColumn = leftColumn;
|
||||
widget.topRow = topRow;
|
||||
widget.bottomRow = bottomRow;
|
||||
widget.rightColumn = rightColumn;
|
||||
widget.parentId = pastingIntoWidgetId;
|
||||
// Also, update the parent widget in the canvas widgets
|
||||
// to include this new copied widget's id in the parent's children
|
||||
let parentChildren = [widget.widgetId];
|
||||
const widgetChildren = widgets[pastingIntoWidgetId].children;
|
||||
if (widgetChildren && Array.isArray(widgetChildren)) {
|
||||
// Add the new child to existing children
|
||||
parentChildren = parentChildren.concat(widgetChildren);
|
||||
}
|
||||
const updateBottomRow =
|
||||
widget.bottomRow * widget.parentRowSpace >
|
||||
widgets[pastingIntoWidgetId].bottomRow;
|
||||
widgets = {
|
||||
...widgets,
|
||||
[pastingIntoWidgetId]: {
|
||||
...widgets[pastingIntoWidgetId],
|
||||
...(updateBottomRow
|
||||
? {
|
||||
bottomRow: widget.bottomRow * widget.parentRowSpace,
|
||||
}
|
||||
: {}),
|
||||
children: parentChildren,
|
||||
},
|
||||
};
|
||||
// If the copied widget's boundaries exceed the parent's
|
||||
// Make the parent scrollable
|
||||
if (
|
||||
widgets[pastingIntoWidgetId].bottomRow *
|
||||
widgets[widget.parentId].parentRowSpace <=
|
||||
widget.bottomRow * widget.parentRowSpace
|
||||
) {
|
||||
const parentOfPastingWidget =
|
||||
widgets[pastingIntoWidgetId].parentId;
|
||||
if (
|
||||
parentOfPastingWidget &&
|
||||
widget.parentId !== MAIN_CONTAINER_WIDGET_ID
|
||||
) {
|
||||
const parent = widgets[parentOfPastingWidget];
|
||||
widgets[parentOfPastingWidget] = {
|
||||
...parent,
|
||||
shouldScrollContents: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For all other widgets in the list
|
||||
// (These widgets will be descendants of the copied widget)
|
||||
// This means, that their parents will also be newly copied widgets
|
||||
// Update widget's parent widget ids with the new parent widget ids
|
||||
const newParentId = newWidgetList.find((newWidget) =>
|
||||
widget.parentId
|
||||
? newWidget.widgetId === widgetIdMap[widget.parentId]
|
||||
: false,
|
||||
)?.widgetId;
|
||||
if (newParentId) widget.parentId = newParentId;
|
||||
}
|
||||
// Generate a new unique widget name
|
||||
widget.widgetName = getNextWidgetName(
|
||||
widgets,
|
||||
widget.type,
|
||||
evalTree,
|
||||
{
|
||||
prefix: oldWidgetName,
|
||||
startWithoutIndex: true,
|
||||
},
|
||||
);
|
||||
widgetNameMap[oldWidgetName] = widget.widgetName;
|
||||
// Add the new widget to the canvas widgets
|
||||
widgets[widget.widgetId] = widget;
|
||||
}
|
||||
newlyCreatedWidgetIds.push(widgetIdMap[copiedWidgetId]);
|
||||
// 1. updating template in the copied widget and deleting old template associations
|
||||
// 2. updating dynamicBindingPathList in the copied grid widget
|
||||
for (let i = 0; i < newWidgetList.length; i++) {
|
||||
const widget = newWidgetList[i];
|
||||
|
||||
widgets = handleSpecificCasesWhilePasting(
|
||||
widget,
|
||||
widgets,
|
||||
widgetNameMap,
|
||||
newWidgetList,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For all other widgets in the list
|
||||
// (These widgets will be descendants of the copied widget)
|
||||
// This means, that their parents will also be newly copied widgets
|
||||
// Update widget's parent widget ids with the new parent widget ids
|
||||
const newParentId = newWidgetList.find((newWidget) =>
|
||||
widget.parentId
|
||||
? newWidget.widgetId === widgetIdMap[widget.parentId]
|
||||
: false,
|
||||
)?.widgetId;
|
||||
if (newParentId) widget.parentId = newParentId;
|
||||
}
|
||||
// Generate a new unique widget name
|
||||
widget.widgetName = getNextWidgetName(widgets, widget.type, evalTree, {
|
||||
prefix: oldWidgetName,
|
||||
startWithoutIndex: true,
|
||||
});
|
||||
widgetNameMap[oldWidgetName] = widget.widgetName;
|
||||
// Add the new widget to the canvas widgets
|
||||
widgets[widget.widgetId] = widget;
|
||||
}
|
||||
|
||||
// 1. updating template in the copied widget and deleting old template associations
|
||||
// 2. updating dynamicBindingPathList in the copied grid widget
|
||||
for (let i = 0; i < newWidgetList.length; i++) {
|
||||
const widget = newWidgetList[i];
|
||||
|
||||
widgets = handleSpecificCasesWhilePasting(
|
||||
widget,
|
||||
widgets,
|
||||
widgetNameMap,
|
||||
newWidgetList,
|
||||
);
|
||||
}
|
||||
|
||||
// save the new DSL
|
||||
yield put(updateAndSaveLayout(widgets));
|
||||
|
||||
// hydrating enhancements map after save layout so that enhancement map
|
||||
// for newly copied widget is hydrated
|
||||
|
||||
// Flash the newly pasted widget once the DSL is re-rendered
|
||||
}),
|
||||
),
|
||||
);
|
||||
// save the new DSL
|
||||
yield put(updateAndSaveLayout(widgets));
|
||||
newlyCreatedWidgetIds.forEach((newWidgetId) => {
|
||||
setTimeout(() => flashElementById(newWidgetId), 100);
|
||||
yield put({
|
||||
type: ReduxActionTypes.SELECT_WIDGET_INIT,
|
||||
payload: { widgetId: newWidgetId },
|
||||
});
|
||||
}
|
||||
});
|
||||
// hydrating enhancements map after save layout so that enhancement map
|
||||
// for newly copied widget is hydrated
|
||||
yield put(selectMultipleWidgetsInitAction(newlyCreatedWidgetIds));
|
||||
}
|
||||
|
||||
function* cutWidgetSaga() {
|
||||
const selectedWidget = yield select(getSelectedWidget);
|
||||
if (!selectedWidget) {
|
||||
const allWidgets: { [widgetId: string]: FlattenedWidgetProps } = yield select(
|
||||
getWidgets,
|
||||
);
|
||||
const selectedWidgets: string[] = yield select(getSelectedWidgets);
|
||||
if (!selectedWidgets) {
|
||||
Toaster.show({
|
||||
text: createMessage(ERROR_WIDGET_CUT_NO_WIDGET_SELECTED),
|
||||
variant: Variant.info,
|
||||
|
|
@ -1710,17 +1753,26 @@ function* cutWidgetSaga() {
|
|||
return;
|
||||
}
|
||||
|
||||
const saveResult = yield createWidgetCopy();
|
||||
const selectedWidgetProps = selectedWidgets.map((each) => allWidgets[each]);
|
||||
|
||||
const eventName = "WIDGET_CUT_VIA_SHORTCUT"; // cut only supported through a shortcut
|
||||
AnalyticsUtil.logEvent(eventName, {
|
||||
widgetName: selectedWidget.widgetName,
|
||||
widgetType: selectedWidget.type,
|
||||
const saveResult = yield createSelectedWidgetsCopy(selectedWidgetProps);
|
||||
|
||||
selectedWidgetProps.forEach((each) => {
|
||||
const eventName = "WIDGET_CUT_VIA_SHORTCUT"; // cut only supported through a shortcut
|
||||
AnalyticsUtil.logEvent(eventName, {
|
||||
widgetName: each.widgetName,
|
||||
widgetType: each.type,
|
||||
});
|
||||
});
|
||||
|
||||
if (saveResult) {
|
||||
Toaster.show({
|
||||
text: createMessage(WIDGET_CUT, selectedWidget.widgetName),
|
||||
text: createMessage(
|
||||
WIDGET_CUT,
|
||||
selectedWidgetProps.length > 1
|
||||
? `${selectedWidgetProps.length} Widgets`
|
||||
: selectedWidgetProps[0].widgetName,
|
||||
),
|
||||
variant: Variant.success,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@ import {
|
|||
WidgetTypes,
|
||||
} from "constants/WidgetConstants";
|
||||
import { cloneDeep, get, isString, filter, set } from "lodash";
|
||||
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import {
|
||||
CanvasWidgetsReduxState,
|
||||
FlattenedWidgetProps,
|
||||
} from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import { call, select } from "redux-saga/effects";
|
||||
import { getDynamicBindings } from "utils/DynamicBindingUtils";
|
||||
import { getWidget } from "./selectors";
|
||||
import { getWidget, getWidgetMetaProps } from "./selectors";
|
||||
|
||||
/**
|
||||
* checks if triggerpaths contains property path passed
|
||||
|
|
@ -224,3 +227,85 @@ export function* getWidgetChildren(widgetId: string): any {
|
|||
}
|
||||
return childrenIds;
|
||||
}
|
||||
|
||||
export const getParentWidgetIdForPasting = function*(
|
||||
widgets: CanvasWidgetsReduxState,
|
||||
selectedWidget: FlattenedWidgetProps | undefined,
|
||||
) {
|
||||
let newWidgetParentId = MAIN_CONTAINER_WIDGET_ID;
|
||||
let parentWidget = widgets[MAIN_CONTAINER_WIDGET_ID];
|
||||
|
||||
// If the selected widget is not the main container
|
||||
if (selectedWidget && selectedWidget.widgetId !== MAIN_CONTAINER_WIDGET_ID) {
|
||||
// Select the parent of the selected widget if parent is not
|
||||
// the main container
|
||||
if (
|
||||
selectedWidget &&
|
||||
selectedWidget.parentId &&
|
||||
selectedWidget.parentId !== MAIN_CONTAINER_WIDGET_ID &&
|
||||
widgets[selectedWidget.parentId]
|
||||
) {
|
||||
const children = widgets[selectedWidget.parentId].children || [];
|
||||
if (children.length > 0) {
|
||||
parentWidget = widgets[selectedWidget.parentId];
|
||||
newWidgetParentId = selectedWidget.parentId;
|
||||
}
|
||||
}
|
||||
// Select the selected widget if the widget is container like
|
||||
if (selectedWidget.children) {
|
||||
parentWidget = widgets[selectedWidget.widgetId];
|
||||
}
|
||||
}
|
||||
|
||||
// If the parent widget in which to paste the copied widget
|
||||
// is not the main container and is not a canvas widget
|
||||
if (
|
||||
parentWidget.widgetId !== MAIN_CONTAINER_WIDGET_ID &&
|
||||
parentWidget.type !== WidgetTypes.CANVAS_WIDGET
|
||||
) {
|
||||
let childWidget;
|
||||
// If the widget in which to paste the new widget is NOT
|
||||
// a tabs widget
|
||||
if (parentWidget.type !== WidgetTypes.TABS_WIDGET) {
|
||||
// The child will be a CANVAS_WIDGET, as we've established
|
||||
// this parent widget to be a container like widget
|
||||
// Which always has its first child as a canvas widget
|
||||
childWidget = parentWidget.children && widgets[parentWidget.children[0]];
|
||||
} else {
|
||||
// If the widget in which to paste the new widget is a tabs widget
|
||||
// Find the currently selected tab canvas widget
|
||||
const { selectedTabWidgetId } = yield select(
|
||||
getWidgetMetaProps,
|
||||
parentWidget.widgetId,
|
||||
);
|
||||
if (selectedTabWidgetId) childWidget = widgets[selectedTabWidgetId];
|
||||
}
|
||||
// If the finally selected parent in which to paste the widget
|
||||
// is a CANVAS_WIDGET, use its widgetId as the new widget's parent Id
|
||||
if (childWidget && childWidget.type === WidgetTypes.CANVAS_WIDGET) {
|
||||
newWidgetParentId = childWidget.widgetId;
|
||||
}
|
||||
}
|
||||
return newWidgetParentId;
|
||||
};
|
||||
|
||||
export const checkIfPastingIntoListWidget = function*(
|
||||
selectedWidget: FlattenedWidgetProps | undefined,
|
||||
) {
|
||||
// when list widget is selected, if the user is pasting, we want it to be pasted in the template
|
||||
// which is first children of list widget
|
||||
if (
|
||||
selectedWidget &&
|
||||
selectedWidget.children &&
|
||||
selectedWidget?.type === WidgetTypes.LIST_WIDGET
|
||||
) {
|
||||
const childrenIds: string[] = yield call(
|
||||
getWidgetChildren,
|
||||
selectedWidget.children[0],
|
||||
);
|
||||
const firstChildId = childrenIds[0];
|
||||
|
||||
selectedWidget = yield select(getWidget, firstChildId);
|
||||
}
|
||||
return selectedWidget;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,15 +5,16 @@ import { getWidgetImmediateChildren, getWidgets } from "./selectors";
|
|||
import log from "loglevel";
|
||||
import {
|
||||
deselectMultipleWidgetsAction,
|
||||
selectAllWidgetsAction,
|
||||
selectMultipleWidgetsAction,
|
||||
selectWidgetAction,
|
||||
selectWidgetInitAction,
|
||||
silentAddSelectionsAction,
|
||||
} from "actions/widgetSelectionActions";
|
||||
import { Toaster } from "components/ads/Toast";
|
||||
import { createMessage, SELECT_ALL_WIDGETS_MSG } from "constants/messages";
|
||||
import { Variant } from "components/ads/common";
|
||||
import { getSelectedWidget, getSelectedWidgets } from "selectors/ui";
|
||||
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
|
||||
// The following is computed to be used in the entity explorer
|
||||
// Every time a widget is selected, we need to expand widget entities
|
||||
|
|
@ -59,13 +60,16 @@ function* selectedWidgetAncestrySaga(
|
|||
}
|
||||
}
|
||||
|
||||
function* selectAllWidgetsSaga() {
|
||||
const allWidgetsOnMainContainer: string[] = yield select(
|
||||
function* selectAllWidgetsInCanvasSaga(
|
||||
action: ReduxAction<{ canvasId: string }>,
|
||||
) {
|
||||
const { canvasId } = action.payload;
|
||||
const allWidgetsOnCanvas: string[] = yield select(
|
||||
getWidgetImmediateChildren,
|
||||
MAIN_CONTAINER_WIDGET_ID,
|
||||
canvasId,
|
||||
);
|
||||
if (allWidgetsOnMainContainer && allWidgetsOnMainContainer.length) {
|
||||
yield put(selectAllWidgetsAction(allWidgetsOnMainContainer));
|
||||
if (allWidgetsOnCanvas && allWidgetsOnCanvas.length) {
|
||||
yield put(selectMultipleWidgetsAction(allWidgetsOnCanvas));
|
||||
Toaster.show({
|
||||
text: createMessage(SELECT_ALL_WIDGETS_MSG),
|
||||
variant: Variant.info,
|
||||
|
|
@ -79,8 +83,8 @@ function* deselectNonSiblingsOfWidgetSaga(
|
|||
) {
|
||||
const { isMultiSelect, widgetId } = action.payload;
|
||||
if (isMultiSelect) {
|
||||
const allWidgets = yield select(getWidgets);
|
||||
const parentId = allWidgets[widgetId].parentId;
|
||||
const allWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
|
||||
const parentId: any = allWidgets[widgetId].parentId;
|
||||
const childWidgets: string[] = yield select(
|
||||
getWidgetImmediateChildren,
|
||||
parentId,
|
||||
|
|
@ -127,12 +131,32 @@ function* shiftSelectWidgetsSaga(
|
|||
: lastSelectedWidgetIndex;
|
||||
const unSelectedSiblings = siblingWidgets.slice(start + 1, end);
|
||||
if (unSelectedSiblings && unSelectedSiblings.length) {
|
||||
yield put(selectMultipleWidgetsAction(unSelectedSiblings));
|
||||
yield put(silentAddSelectionsAction(unSelectedSiblings));
|
||||
}
|
||||
}
|
||||
yield put(selectWidgetInitAction(widgetId, true));
|
||||
}
|
||||
|
||||
function* selectMultipleWidgetsSaga(
|
||||
action: ReduxAction<{ widgetIds: string[] }>,
|
||||
) {
|
||||
const { widgetIds } = action.payload;
|
||||
if (!widgetIds || !widgetIds.length) {
|
||||
return;
|
||||
}
|
||||
const allWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
|
||||
const parentToMatch = allWidgets[widgetIds[0]].parentId;
|
||||
const doesNotMatchParent = widgetIds.some((each) => {
|
||||
return allWidgets[each].parentId !== parentToMatch;
|
||||
});
|
||||
if (doesNotMatchParent) {
|
||||
return;
|
||||
} else {
|
||||
yield put(selectWidgetAction());
|
||||
yield put(selectMultipleWidgetsAction(widgetIds));
|
||||
}
|
||||
}
|
||||
|
||||
export function* widgetSelectionSagas() {
|
||||
yield all([
|
||||
takeLatest(
|
||||
|
|
@ -145,9 +169,13 @@ export function* widgetSelectionSagas() {
|
|||
ReduxActionTypes.SELECT_WIDGET_INIT,
|
||||
deselectNonSiblingsOfWidgetSaga,
|
||||
),
|
||||
takeLatest(
|
||||
ReduxActionTypes.SELECT_ALL_WIDGETS_IN_CANVAS_INIT,
|
||||
selectAllWidgetsInCanvasSaga,
|
||||
),
|
||||
takeLatest(
|
||||
ReduxActionTypes.SELECT_MULTIPLE_WIDGETS_INIT,
|
||||
selectAllWidgetsSaga,
|
||||
selectMultipleWidgetsSaga,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
30
app/client/src/utils/hooks/useCanvasMinHeightUpdateHook.ts
Normal file
30
app/client/src/utils/hooks/useCanvasMinHeightUpdateHook.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { updateWidget } from "actions/pageActions";
|
||||
import { WidgetTypes } from "constants/WidgetConstants";
|
||||
import { useEffect } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { AppState } from "reducers";
|
||||
import { getWidget } from "sagas/selectors";
|
||||
import { useSelector } from "store";
|
||||
import { WidgetOperations } from "widgets/BaseWidget";
|
||||
|
||||
export const useCanvasMinHeightUpdateHook = (
|
||||
widgetId: string,
|
||||
minHeight = 0,
|
||||
) => {
|
||||
const widget = useSelector((state: AppState) => getWidget(state, widgetId));
|
||||
const dispatch = useDispatch();
|
||||
useEffect(() => {
|
||||
if (
|
||||
widget &&
|
||||
widget.type === WidgetTypes.CANVAS_WIDGET &&
|
||||
widget.minHeight !== minHeight
|
||||
) {
|
||||
dispatch(
|
||||
updateWidget(WidgetOperations.UPDATE_PROPERTY, widgetId, {
|
||||
propertyPath: "minHeight",
|
||||
propertyValue: minHeight,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [minHeight]);
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { useDispatch } from "react-redux";
|
||||
import { focusWidget } from "actions/widgetActions";
|
||||
import {
|
||||
selectAllWidgetsAction,
|
||||
selectMultipleWidgetsAction,
|
||||
selectWidgetInitAction,
|
||||
shiftSelectWidgetsEntityExplorerInitAction,
|
||||
} from "actions/widgetSelectionActions";
|
||||
|
|
@ -29,7 +29,7 @@ export const useWidgetSelection = () => {
|
|||
(widgetId?: string) => dispatch(focusWidget(widgetId)),
|
||||
[dispatch],
|
||||
),
|
||||
deselectAll: useCallback(() => dispatch(selectAllWidgetsAction([])), [
|
||||
deselectAll: useCallback(() => dispatch(selectMultipleWidgetsAction([])), [
|
||||
dispatch,
|
||||
]),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ import { useDispatch } from "react-redux";
|
|||
import { extractCurrentDSL } from "utils/WidgetPropsUtils";
|
||||
import { setAppMode } from "actions/pageActions";
|
||||
import { APP_MODE } from "reducers/entityReducers/appReducer";
|
||||
import { createSelector } from "reselect";
|
||||
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import { getCanvasWidgets } from "selectors/entitiesSelector";
|
||||
import { ContainerWidgetProps } from "widgets/ContainerWidget";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer";
|
||||
|
||||
export const useMockDsl = (dsl: any) => {
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -69,6 +75,17 @@ export function MockPageDSL({ dsl, children }: any) {
|
|||
return children;
|
||||
}
|
||||
|
||||
export const mockGetCanvasWidgetDsl = createSelector(
|
||||
getCanvasWidgets,
|
||||
(
|
||||
canvasWidgets: CanvasWidgetsReduxState,
|
||||
): ContainerWidgetProps<WidgetProps> => {
|
||||
return CanvasWidgetsNormalizer.denormalize("0", {
|
||||
canvasWidgets,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export const syntheticTestMouseEvent = (
|
||||
event: MouseEvent,
|
||||
optionsToAdd = {},
|
||||
9
app/client/test/testMockedWidgets.tsx
Normal file
9
app/client/test/testMockedWidgets.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import Canvas from "pages/Editor/Canvas";
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { mockGetCanvasWidgetDsl } from "./testCommon";
|
||||
|
||||
export const MockCanvas = () => {
|
||||
const dsl = useSelector(mockGetCanvasWidgetDsl);
|
||||
return <Canvas dsl={dsl}></Canvas>;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user