chore: code changes for widget position observer and widget name on canvas (#27367)
## Description The PR contains non integrated code changes for below new features, The changes are not integrated to work but only contains the ground work code changes that can be added to css based layout/ Anvil once that is available in Release. - **Widget Position observer-** Since we are moving to css based layout, the positions of widgets will be unknown. To solve the issue we have introduced the above feature that stores/updates position of widgets on Redux state whenever a widget position updates. without manually triggering any action - **Widget Name on Canvas-** For the New Layout the existing widget name is inconsistent as it would cut off or visually not visible. to solve that the widget name will now be drawn on html canvas than it being a dom node component #### PR fixes following issue(s) Fixes #26945 Fixes #26948 #### Type of change - Chore (housekeeping or task changes that don't impact user perception) ## Testing #### How Has This Been Tested? - [ ] Manual - [ ] JUnit - [ ] Jest #### Test Plan > Add Testsmith test cases links that relate to this PR #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed --------- Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com> Co-authored-by: Abhinav Jha <abhinav@appsmith.com>
This commit is contained in:
parent
09657f4cea
commit
8a35e05923
|
|
@ -123,6 +123,7 @@
|
|||
"js-sha256": "^0.9.0",
|
||||
"jshint": "^2.13.4",
|
||||
"klona": "^2.0.5",
|
||||
"konva": "8.0.1",
|
||||
"libphonenumber-js": "^1.9.44",
|
||||
"linkedom": "^0.14.20",
|
||||
"localforage": "^1.7.3",
|
||||
|
|
@ -166,6 +167,7 @@
|
|||
"react-hook-form": "^7.28.0",
|
||||
"react-instantsearch-dom": "^6.4.0",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-konva": "17.0.2-6",
|
||||
"react-masonry-css": "^1.0.16",
|
||||
"react-media-recorder": "^1.6.1",
|
||||
"react-modal": "^3.15.1",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import pageListReducer from "reducers/entityReducers/pageListReducer";
|
|||
import pluginsReducer from "reducers/entityReducers/pluginsReducer";
|
||||
import autoHeightLayoutTreeReducer from "reducers/entityReducers/autoHeightReducers/autoHeightLayoutTreeReducer";
|
||||
import canvasLevelsReducer from "reducers/entityReducers/autoHeightReducers/canvasLevelsReducer";
|
||||
import widgetPositionsReducer from "layoutSystems/anvil/integrations/reducers/widgetPositionsReducer";
|
||||
|
||||
export const entityReducerObject = {
|
||||
canvasWidgets: canvasWidgetsReducer,
|
||||
|
|
@ -26,4 +27,5 @@ export const entityReducerObject = {
|
|||
jsActions: jsActionsReducer,
|
||||
autoHeightLayoutTree: autoHeightLayoutTreeReducer,
|
||||
canvasLevels: canvasLevelsReducer,
|
||||
widgetPositions: widgetPositionsReducer,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsR
|
|||
import type { layoutConversionReduxState } from "reducers/uiReducers/layoutConversionReducer";
|
||||
import type { OneClickBindingState } from "reducers/uiReducers/oneClickBindingReducer";
|
||||
|
||||
/* Reducers which are integrated into the core system when registering a pluggable module
|
||||
or done so by a module that is designed to be eventually pluggable */
|
||||
import type { WidgetPositionsReduxState } from "layoutSystems/anvil/integrations/reducers/widgetPositionsReducer";
|
||||
|
||||
export const reducerObject = {
|
||||
entities: entityReducer,
|
||||
ui: uiReducer,
|
||||
|
|
@ -152,6 +156,7 @@ export interface AppState {
|
|||
jsActions: JSCollectionDataState;
|
||||
autoHeightLayoutTree: AutoHeightLayoutTreeReduxState;
|
||||
canvasLevels: CanvasLevelsReduxState;
|
||||
widgetPositions: WidgetPositionsReduxState;
|
||||
};
|
||||
evaluations: {
|
||||
tree: EvaluatedTreeState;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ import oneClickBindingSaga from "sagas/OneClickBindingSaga";
|
|||
import entityNavigationSaga from "sagas/NavigationSagas";
|
||||
import communityTemplateSagas from "sagas/CommunityTemplatesSagas";
|
||||
|
||||
/* Sagas that are registered by a module that is designed to be independent of the core platform */
|
||||
import WidgetPositionSaga from "layoutSystems/anvil/integrations/sagas/WidgetPositionsSaga";
|
||||
|
||||
export const sagas = [
|
||||
initSagas,
|
||||
pageSagas,
|
||||
|
|
@ -105,5 +108,6 @@ export const sagas = [
|
|||
snapshotSagas,
|
||||
oneClickBindingSaga,
|
||||
entityNavigationSaga,
|
||||
WidgetPositionSaga,
|
||||
communityTemplateSagas,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -77,6 +77,21 @@ export const getDatasourceStructureById = (
|
|||
return state.entities.datasources.structure[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Selector to indicate if the widget name should be shown/drawn on canvas
|
||||
*/
|
||||
export const getShouldShowWidgetName = createSelector(
|
||||
(state: AppState) => state.ui.widgetDragResize.isResizing,
|
||||
(state: AppState) => state.ui.widgetDragResize.isDragging,
|
||||
(state: AppState) => state.ui.editor.isPreviewMode,
|
||||
(state: AppState) => state.ui.widgetDragResize.isAutoCanvasResizing,
|
||||
(isResizing, isDragging, isPreviewMode, isAutoCanvasResizing) => {
|
||||
return (
|
||||
!isResizing && !isDragging && !isPreviewMode && !isAutoCanvasResizing
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const getDatasourceTableColumns =
|
||||
(datasourceId: string, tableName: string) => (state: AppState) => {
|
||||
const structure = getDatasourceStructureById(state, datasourceId);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ export function getBaseWidgetClassName(id?: string) {
|
|||
|
||||
export const CANVAS_VIEWPORT = "canvas-viewport";
|
||||
|
||||
export const CANVAS_ART_BOARD = "art-board";
|
||||
|
||||
export const POSITIONED_WIDGET = "positioned-widget";
|
||||
|
||||
export const WIDGET_COMPONENT_BOUNDARY_CLASS = "widget-component-boundary";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
export type AnvilReduxAction<T> = {
|
||||
type: AnvilReduxActionTypes;
|
||||
payload: T;
|
||||
};
|
||||
|
||||
export enum AnvilReduxActionTypes {
|
||||
READ_WIDGET_POSITIONS = "READ_WIDGET_POSITIONS",
|
||||
UPDATE_WIDGET_POSITIONS = "UPDATE_WIDGET_POSITIONS",
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { AnvilReduxActionTypes } from "./actionTypes";
|
||||
|
||||
export const readWidgetPositions = (
|
||||
widgetsProcessQueue: {
|
||||
[widgetDOMId: string]: boolean;
|
||||
},
|
||||
layersProcessQueue: { [canvasId: string]: number },
|
||||
layoutsProcessQueue: { [layoutId: string]: boolean },
|
||||
) => {
|
||||
return {
|
||||
type: AnvilReduxActionTypes.READ_WIDGET_POSITIONS,
|
||||
payload: { widgetsProcessQueue, layersProcessQueue, layoutsProcessQueue },
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { AnvilReduxActionTypes } from "layoutSystems/anvil/integrations/actions/actionTypes";
|
||||
import type { AnvilReduxAction } from "layoutSystems/anvil/integrations/actions/actionTypes";
|
||||
import { createImmerReducer } from "utils/ReducerUtils";
|
||||
import type { WidgetPositions } from "layoutSystems/common/types";
|
||||
|
||||
const initialState: WidgetPositions = {};
|
||||
|
||||
export type WidgetPositionsReduxState = typeof initialState;
|
||||
|
||||
/**
|
||||
* Reducer used for storing Position of all widgets in the current layout
|
||||
* This reducer is useful for all on canvas UI (Ex: Dropzone Highlights, Widget Name component position, etc)
|
||||
*/
|
||||
const widgetPositionsReducer = createImmerReducer(initialState, {
|
||||
[AnvilReduxActionTypes.UPDATE_WIDGET_POSITIONS]: (
|
||||
WidgetPositionState: WidgetPositions,
|
||||
action: AnvilReduxAction<WidgetPositions>,
|
||||
) => {
|
||||
const widgetPositions = action.payload;
|
||||
|
||||
const widgetIds = Object.keys(widgetPositions);
|
||||
|
||||
for (const widgetId of widgetIds) {
|
||||
const newPosition = widgetPositions[widgetId];
|
||||
WidgetPositionState[widgetId].height = newPosition.height;
|
||||
WidgetPositionState[widgetId].width = newPosition.width;
|
||||
WidgetPositionState[widgetId].left = newPosition.left;
|
||||
WidgetPositionState[widgetId].top = newPosition.top;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default widgetPositionsReducer;
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import { AnvilReduxActionTypes } from "layoutSystems/anvil/integrations/actions/actionTypes";
|
||||
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
|
||||
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import type { WidgetPositions } from "layoutSystems/common/types";
|
||||
import { all, put, select, takeEvery } from "redux-saga/effects";
|
||||
import { getAnvilWidgetId } from "layoutSystems/common/utils/WidgetPositionsObserver/utils";
|
||||
import { getAffectedWidgetsFromLayers } from "layoutSystems/anvil/integrations/utils";
|
||||
import { getCanvasWidgets } from "@appsmith/selectors/entitiesSelector";
|
||||
import { CANVAS_ART_BOARD } from "constants/componentClassNameConstants";
|
||||
|
||||
/**
|
||||
* This saga is used to read and update widget position from the the list of widgets,
|
||||
* layers and layouts received from widget positions observer
|
||||
* Widgets : When triggered by the ResizeObserver wrapping the widget
|
||||
* Layers: When triggered by the ResizeObserver wrapping the layer (Flex layers)
|
||||
* Layouts: When triggered by the ResizeObserver wrapping the layout (Layout Components)
|
||||
* @param action All the widgets, layers and layouts that have changed and currently in queue to be processed
|
||||
*/
|
||||
function* readAndUpdateWidgetPositions(
|
||||
action: ReduxAction<{
|
||||
widgetsProcessQueue: {
|
||||
[widgetId: string]: boolean;
|
||||
};
|
||||
layersProcessQueue: { [canvasId: string]: number };
|
||||
layoutsProcessQueue: { [key: string]: boolean };
|
||||
}>,
|
||||
) {
|
||||
const widgets: CanvasWidgetsReduxState = yield select(getCanvasWidgets);
|
||||
|
||||
const { layersProcessQueue, widgetsProcessQueue } = action.payload;
|
||||
|
||||
//get additional widgets from affected layers
|
||||
const affectedWidgetsFromLayers: {
|
||||
[widgetDOMId: string]: boolean;
|
||||
} = getAffectedWidgetsFromLayers(layersProcessQueue, widgets);
|
||||
|
||||
const widgetsToProcess = {
|
||||
...widgetsProcessQueue,
|
||||
...affectedWidgetsFromLayers,
|
||||
};
|
||||
|
||||
const widgetDimensions: WidgetPositions = {};
|
||||
|
||||
const mainContainerDOMNode = document.getElementById(CANVAS_ART_BOARD);
|
||||
|
||||
const mainContainerDOMRect = mainContainerDOMNode?.getBoundingClientRect();
|
||||
|
||||
const { left = 0, top = 0 } = mainContainerDOMRect || {};
|
||||
|
||||
//for every affected widget get the bounding client Rect
|
||||
// If they do, we don't have to update the positions here.
|
||||
for (const widgetId of Object.keys(widgetsToProcess)) {
|
||||
const element = document.getElementById(getAnvilWidgetId(widgetId));
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
widgetDimensions[widgetId] = {
|
||||
left: rect.left - left,
|
||||
top: rect.top - top,
|
||||
height: rect.height,
|
||||
width: rect.width,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
yield put({
|
||||
type: AnvilReduxActionTypes.UPDATE_WIDGET_POSITIONS,
|
||||
payload: widgetDimensions,
|
||||
});
|
||||
}
|
||||
|
||||
export default function* WidgetPositionSaga() {
|
||||
yield all([
|
||||
takeEvery(
|
||||
AnvilReduxActionTypes.READ_WIDGET_POSITIONS,
|
||||
readAndUpdateWidgetPositions,
|
||||
),
|
||||
]);
|
||||
}
|
||||
151
app/client/src/layoutSystems/anvil/integrations/utils.test.ts
Normal file
151
app/client/src/layoutSystems/anvil/integrations/utils.test.ts
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import type { WidgetProps } from "widgets/BaseWidget";
|
||||
import { getAffectedWidgetsFromLayers, getAllChildWidgets } from "./utils";
|
||||
|
||||
const widgets = {
|
||||
"0": {
|
||||
children: ["1", "3", "4"],
|
||||
detachFromLayout: true,
|
||||
flexLayers: [
|
||||
{ children: [{ id: "1" }, { id: "3" }] },
|
||||
{ children: [{ id: "4" }] },
|
||||
],
|
||||
},
|
||||
"1": {
|
||||
children: ["2"],
|
||||
},
|
||||
"2": {
|
||||
children: ["5", "6", "7", "8"],
|
||||
detachFromLayout: true,
|
||||
flexLayers: [
|
||||
{ children: [{ id: "5" }] },
|
||||
{ children: [{ id: "6" }, { id: "7" }] },
|
||||
{ children: [{ id: "8" }] },
|
||||
],
|
||||
},
|
||||
"3": { children: [] },
|
||||
"4": { children: [] },
|
||||
"5": { children: [] },
|
||||
"6": { children: [] },
|
||||
"7": { children: [] },
|
||||
"8": { children: [] },
|
||||
} as unknown as CanvasWidgetsReduxState;
|
||||
|
||||
describe("should test getAffectedWidgetsFromLayers", () => {
|
||||
const layerQueue1 = {
|
||||
"0": 0,
|
||||
};
|
||||
|
||||
const layerQueue2 = {
|
||||
"0": 1,
|
||||
"2": 1,
|
||||
};
|
||||
|
||||
const layerQueue3 = {
|
||||
"2": 0,
|
||||
};
|
||||
|
||||
const layerQueue4 = {
|
||||
"0": 1,
|
||||
"2": 2,
|
||||
};
|
||||
|
||||
const affectedWidgets1 = {
|
||||
"1": true,
|
||||
"3": true,
|
||||
"4": true,
|
||||
"5": true,
|
||||
"6": true,
|
||||
"7": true,
|
||||
"8": true,
|
||||
};
|
||||
|
||||
const affectedWidgets2 = {
|
||||
"4": true,
|
||||
"6": true,
|
||||
"7": true,
|
||||
"8": true,
|
||||
};
|
||||
|
||||
const affectedWidgets3 = {
|
||||
"5": true,
|
||||
"6": true,
|
||||
"7": true,
|
||||
"8": true,
|
||||
};
|
||||
|
||||
const affectedWidgets4 = {
|
||||
"4": true,
|
||||
"8": true,
|
||||
};
|
||||
|
||||
it("should return all the affected widgets derived from layer queue", () => {
|
||||
expect(getAffectedWidgetsFromLayers(layerQueue1, widgets)).toEqual(
|
||||
affectedWidgets1,
|
||||
);
|
||||
expect(getAffectedWidgetsFromLayers(layerQueue2, widgets)).toEqual(
|
||||
affectedWidgets2,
|
||||
);
|
||||
expect(getAffectedWidgetsFromLayers(layerQueue3, widgets)).toEqual(
|
||||
affectedWidgets3,
|
||||
);
|
||||
expect(getAffectedWidgetsFromLayers(layerQueue4, widgets)).toEqual(
|
||||
affectedWidgets4,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("should test getAllChildWidgets", () => {
|
||||
const widget1 = {
|
||||
widgetId: "0",
|
||||
children: ["1", "3", "4"],
|
||||
} as unknown as WidgetProps;
|
||||
|
||||
const widget2 = {
|
||||
widgetId: "1",
|
||||
children: ["2"],
|
||||
} as unknown as WidgetProps;
|
||||
|
||||
const widget3 = {
|
||||
widgetId: "2",
|
||||
children: ["5", "6", "7", "8"],
|
||||
} as unknown as WidgetProps;
|
||||
|
||||
const widget4 = {
|
||||
widgetId: "3",
|
||||
children: [],
|
||||
} as unknown as WidgetProps;
|
||||
|
||||
const childWidgets1 = {
|
||||
"1": true,
|
||||
"3": true,
|
||||
"4": true,
|
||||
"5": true,
|
||||
"6": true,
|
||||
"7": true,
|
||||
"8": true,
|
||||
};
|
||||
|
||||
const childWidgets2 = {
|
||||
"5": true,
|
||||
"6": true,
|
||||
"7": true,
|
||||
"8": true,
|
||||
};
|
||||
|
||||
const childWidgets3 = {
|
||||
"5": true,
|
||||
"6": true,
|
||||
"7": true,
|
||||
"8": true,
|
||||
};
|
||||
|
||||
const childWidgets4 = {};
|
||||
|
||||
it("should return all the child widgets except canvas widgets", () => {
|
||||
expect(getAllChildWidgets(widget1, widgets)).toEqual(childWidgets1);
|
||||
expect(getAllChildWidgets(widget2, widgets)).toEqual(childWidgets2);
|
||||
expect(getAllChildWidgets(widget3, widgets)).toEqual(childWidgets3);
|
||||
expect(getAllChildWidgets(widget4, widgets)).toEqual(childWidgets4);
|
||||
});
|
||||
});
|
||||
90
app/client/src/layoutSystems/anvil/integrations/utils.ts
Normal file
90
app/client/src/layoutSystems/anvil/integrations/utils.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import type { WidgetProps } from "widgets/BaseWidget";
|
||||
|
||||
/**
|
||||
* This method is used to determine all the affected widgets from all the layers that have changed
|
||||
* @param layersProcessQueue Changed layer Ids, will have one layer id per canvas
|
||||
* @param widgets all widget dsl Properties
|
||||
* @returns list of all affected widgets
|
||||
*/
|
||||
export function getAffectedWidgetsFromLayers(
|
||||
layersProcessQueue: {
|
||||
[canvasId: string]: number;
|
||||
},
|
||||
widgets: CanvasWidgetsReduxState,
|
||||
) {
|
||||
let affectedWidgets: { [widgetDOMId: string]: boolean } = {};
|
||||
|
||||
//Even though it has many nested iterations it will go through all teh affected widgets only once
|
||||
//Iterate through all the canvases and it's first layer that got affected
|
||||
for (const [canvasId, layerIndex] of Object.entries(layersProcessQueue)) {
|
||||
const flexLayers = widgets[canvasId]?.flexLayers || [];
|
||||
|
||||
//iterate through all the layers below the changed layer id inculuding the layer
|
||||
for (let i = layerIndex; i < flexLayers.length; i++) {
|
||||
const children = flexLayers[i]?.children || [];
|
||||
//iterate through all the child widgets inside the layer
|
||||
for (const child of children) {
|
||||
const childWidget = widgets[child.id];
|
||||
|
||||
if (!childWidget) continue;
|
||||
|
||||
affectedWidgets[child.id] = true;
|
||||
|
||||
//if the widget has children get all the nested children
|
||||
if (childWidget.children && childWidget.children.length > 0) {
|
||||
affectedWidgets = {
|
||||
...affectedWidgets,
|
||||
...getAllChildWidgets(childWidget, widgets, layersProcessQueue),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return affectedWidgets;
|
||||
}
|
||||
|
||||
/**
|
||||
* This Method gets all the nested child widgets,
|
||||
* within the given widgets ignoring the canvas type widgets
|
||||
* @param widget Widget whose nested children have to be found
|
||||
* @param widgets all widget dsl Properties
|
||||
* @returns list of all the nested child widgets of widget
|
||||
*/
|
||||
export function getAllChildWidgets(
|
||||
widget: WidgetProps,
|
||||
widgets: CanvasWidgetsReduxState,
|
||||
layersProcessQueue?: {
|
||||
[canvasId: string]: number;
|
||||
},
|
||||
) {
|
||||
let childWidgets: { [widgetDOMId: string]: boolean } = {};
|
||||
|
||||
const children = widget.children;
|
||||
|
||||
//iterate through children if widget
|
||||
for (const childId of children) {
|
||||
const childWidget = widgets[childId];
|
||||
|
||||
if (!childWidget) continue;
|
||||
|
||||
//if the child widget is not a canvas add it to the list
|
||||
if (!childWidget.detachFromLayout) {
|
||||
childWidgets[childId] = true;
|
||||
} else if (layersProcessQueue) {
|
||||
//If it is a canvas widget remove the widget from the layer queue to avoid processing it again
|
||||
delete layersProcessQueue[childId];
|
||||
}
|
||||
|
||||
//if the widget further has nested children call the getAllChildWidgets recursively.
|
||||
if (childWidget.children && childWidget.children.length > 0) {
|
||||
childWidgets = {
|
||||
...childWidgets,
|
||||
...getAllChildWidgets(childWidget, widgets, layersProcessQueue),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return childWidgets;
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import { Colors } from "constants/Colors";
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
export const WIDGET_NAME_CANVAS = "widget-name-canvas";
|
||||
export const WIDGET_NAME_FONT_SIZE = 14;
|
||||
export const WIDGET_NAME_LINE_HEIGHT = Math.floor(WIDGET_NAME_FONT_SIZE * 1.2);
|
||||
export const WIDGET_NAME_VERTICAL_PADDING = 4;
|
||||
export const WIDGET_NAME_HORIZONTAL_PADDING = 6;
|
||||
export const WIDGET_NAME_ICON_PADDING = 16;
|
||||
|
||||
export const DEFAULT_WIDGET_NAME_CANVAS_HEIGHT = 600;
|
||||
export const WIDGET_NAME_CANVAS_PADDING = 20;
|
||||
|
||||
export const WIDGET_NAME_HEIGHT = Math.floor(
|
||||
WIDGET_NAME_LINE_HEIGHT + WIDGET_NAME_VERTICAL_PADDING * 1.5,
|
||||
);
|
||||
|
||||
export const WIDGET_NAME_TEXT_COLOR = Colors.WHITE;
|
||||
|
||||
//Adding this here as Konva accepts this type of path for SVG
|
||||
export const warningSVGPath =
|
||||
"M 18 9 C 18 13.9706 13.9706 18 9 18 C 4.0294 18 0 13.9706 0 9 C 0 4.0294 4.0294 0 9 0 C 13.9706 0 18 4.0294 18 9 Z M 7.875 3.9375 V 10.125 H 10.125 V 3.9375 H 7.875 Z M 9 14.0625 C 9.6213 14.0625 10.125 13.5588 10.125 12.9375 C 10.125 12.3162 9.6213 11.8125 9 11.8125 C 8.3787 11.8125 7.875 12.3162 7.875 12.9375 C 7.875 13.5588 8.3787 14.0625 9 14.0625 Z";
|
||||
|
||||
//Indicates the state of widget name
|
||||
export enum WidgetNameState {
|
||||
SELECTED = "SELECTED",
|
||||
ERROR = "ERROR",
|
||||
FOCUSED = "FOCUSED",
|
||||
}
|
||||
|
||||
//fill colors of widget name based on state
|
||||
export const WIDGET_NAME_FILL_COLORS = {
|
||||
[WidgetNameState.SELECTED]: Colors.JAFFA_DARK,
|
||||
[WidgetNameState.FOCUSED]: Colors.WATUSI,
|
||||
[WidgetNameState.ERROR]: Colors.DANGER_SOLID,
|
||||
};
|
||||
|
||||
//CSS properties of the wrapper object of the html canvas
|
||||
export const widgetNameWrapperStyle: CSSProperties = {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 2,
|
||||
pointerEvents: "none",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
};
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import type { WidgetPosition } from "layoutSystems/common/types";
|
||||
import type { WidgetNameState } from "./WidgetNameConstants";
|
||||
|
||||
export type WIDGET_NAME_TYPE = "selected" | "focused";
|
||||
|
||||
//Contains the data of widget which are required to draw widget names on canvas
|
||||
export type WidgetNameData = {
|
||||
id: string;
|
||||
position: WidgetPosition;
|
||||
widgetName: string;
|
||||
parentId: string;
|
||||
nameState: WidgetNameState;
|
||||
dragDisabled: boolean;
|
||||
};
|
||||
|
||||
//Position of the widget name on canvas, required to enable interaction on canvas
|
||||
export type WidgetNamePositionData = {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
widgetNameData: WidgetNameData;
|
||||
};
|
||||
|
||||
//Position of canvas with respect to client browser
|
||||
export type CanvasPositions = {
|
||||
top: number;
|
||||
left: number;
|
||||
xDiff: number;
|
||||
width: number;
|
||||
yDiff: number;
|
||||
height: number;
|
||||
};
|
||||
382
app/client/src/layoutSystems/common/WidgetNamesCanvas/index.tsx
Normal file
382
app/client/src/layoutSystems/common/WidgetNamesCanvas/index.tsx
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
import type { DragEventHandler, DragEvent } from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { throttle } from "lodash";
|
||||
import { Layer, Stage } from "react-konva/lib/ReactKonvaCore";
|
||||
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
|
||||
import { SelectionRequestType } from "sagas/WidgetSelectUtils";
|
||||
|
||||
import {
|
||||
useShowTableFilterPane,
|
||||
useWidgetDragResize,
|
||||
} from "utils/hooks/dragResizeHooks";
|
||||
import type {
|
||||
CanvasPositions,
|
||||
WidgetNameData,
|
||||
WidgetNamePositionData,
|
||||
WIDGET_NAME_TYPE,
|
||||
} from "./WidgetNameTypes";
|
||||
import {
|
||||
DEFAULT_WIDGET_NAME_CANVAS_HEIGHT,
|
||||
WIDGET_NAME_CANVAS_PADDING,
|
||||
widgetNameWrapperStyle,
|
||||
WIDGET_NAME_CANVAS,
|
||||
} from "./WidgetNameConstants";
|
||||
import {
|
||||
getFocusedWidgetNameData,
|
||||
getSelectedWidgetNameData,
|
||||
} from "../selectors";
|
||||
import type { WidgetPosition } from "layoutSystems/common/types";
|
||||
import { getShouldAllowDrag } from "selectors/widgetDragSelectors";
|
||||
import type { Stage as CanvasStageType } from "konva/lib/Stage";
|
||||
import type { Layer as KonvaLayer } from "konva/lib/Layer";
|
||||
import { getWidgetNameComponent } from "./utils";
|
||||
|
||||
/**
|
||||
* This Component contains logic to draw widget name on canvas
|
||||
* and also to make the widget name Intractable like selection of widget or dragging of widget
|
||||
* @param props Object that contains
|
||||
* @prop canvasWidth width of canvas in pixels
|
||||
* @prop containerRef ref of PageViewWrapper component
|
||||
* @prop parentRef ref of the MainContainerWrapper component i.e, the parent of the canvas component
|
||||
*/
|
||||
const OverlayCanvasContainer = (props: {
|
||||
canvasWidth: number;
|
||||
containerRef: React.RefObject<HTMLDivElement>;
|
||||
parentRef: React.RefObject<HTMLDivElement>;
|
||||
}) => {
|
||||
//widget name data of widgets
|
||||
const selectedWidgetNameData: WidgetNameData | undefined = useSelector(
|
||||
getSelectedWidgetNameData,
|
||||
);
|
||||
const focusedWidgetNameData: WidgetNameData | undefined = useSelector(
|
||||
getFocusedWidgetNameData,
|
||||
);
|
||||
|
||||
const shouldAllowDrag = useSelector(getShouldAllowDrag);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// used to keep track of positions of widgetName drawn on canvas to make it intractable
|
||||
const widgetNamePositions = useRef<{
|
||||
selected: WidgetNamePositionData | undefined;
|
||||
focused: WidgetNamePositionData | undefined;
|
||||
}>({ selected: undefined, focused: undefined });
|
||||
|
||||
const { setDraggingState } = useWidgetDragResize();
|
||||
const showTableFilterPane = useShowTableFilterPane();
|
||||
|
||||
//Positions of canvas
|
||||
const canvasPositions = useRef<CanvasPositions>({
|
||||
top: 0,
|
||||
left: 0,
|
||||
xDiff: 0,
|
||||
width: 0,
|
||||
yDiff: 0,
|
||||
height: 0,
|
||||
});
|
||||
|
||||
const scrollTop = useRef<number>(0);
|
||||
const isScrolling = useRef(0);
|
||||
const hasScroll = useRef<boolean>(false);
|
||||
const stageRef = useRef<CanvasStageType>(null);
|
||||
|
||||
const { selectWidget } = useWidgetSelection();
|
||||
|
||||
//used to set canvasPositions, which is used further to calculate the exact positions of widgets
|
||||
useEffect(() => {
|
||||
if (!stageRef?.current?.content || !wrapperRef?.current) return;
|
||||
|
||||
const HTMLCanvas: HTMLDivElement = stageRef?.current?.content;
|
||||
const rect: DOMRect = HTMLCanvas.getBoundingClientRect();
|
||||
|
||||
const wrapper: HTMLDivElement = wrapperRef?.current as HTMLDivElement;
|
||||
const wrapperRect: DOMRect = wrapper.getBoundingClientRect();
|
||||
|
||||
if (rect && wrapperRect) {
|
||||
canvasPositions.current = {
|
||||
...canvasPositions.current,
|
||||
height: wrapperRect.height,
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
width: rect.width,
|
||||
};
|
||||
}
|
||||
}, [wrapperRef?.current, props.canvasWidth]);
|
||||
|
||||
/**
|
||||
* Method used to add widget name to the Konva canvas' layer
|
||||
* @param layer Konva layer onto which the widget name is to be added
|
||||
* @param widgetNameData widget name data contains more information regarding the widget that is used in drawing the name
|
||||
* @param position position of widget in pixels
|
||||
* @param type if it's either selected or focused widget name
|
||||
*/
|
||||
const addWidgetNameToCanvas = (
|
||||
layer: KonvaLayer,
|
||||
widgetNameData: WidgetNameData,
|
||||
position: WidgetPosition,
|
||||
type: WIDGET_NAME_TYPE,
|
||||
) => {
|
||||
if (!position) return;
|
||||
|
||||
const { id: widgetId, widgetName } = widgetNameData;
|
||||
|
||||
//Get Widget Name
|
||||
if (widgetName) {
|
||||
const {
|
||||
canvasLeftOffset,
|
||||
canvasTopOffset,
|
||||
widgetNameComponent,
|
||||
widgetNamePosition,
|
||||
} = getWidgetNameComponent(
|
||||
position,
|
||||
widgetName,
|
||||
widgetNameData,
|
||||
props?.parentRef?.current,
|
||||
stageRef?.current?.content,
|
||||
scrollTop.current,
|
||||
);
|
||||
|
||||
widgetNamePositions.current[type] = { ...widgetNamePosition };
|
||||
|
||||
canvasPositions.current = {
|
||||
...canvasPositions.current,
|
||||
xDiff: canvasLeftOffset,
|
||||
yDiff: canvasTopOffset,
|
||||
};
|
||||
|
||||
//Make widget name clickable
|
||||
widgetNameComponent.on("click", () => {
|
||||
selectWidget(SelectionRequestType.One, [widgetId]);
|
||||
});
|
||||
|
||||
//Add widget name to canvas
|
||||
layer.add(widgetNameComponent);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is called whenever there is a change in state of canvas,
|
||||
* i.e, widget position is changed, canvas resized, selected widget changes
|
||||
* @param widgetPosition
|
||||
*/
|
||||
const updateSelectedWidgetPositions = (widgetPosition?: WidgetPosition) => {
|
||||
if (!stageRef?.current) return;
|
||||
|
||||
const stage = stageRef.current;
|
||||
const layer = stage.getLayers()[0];
|
||||
//destroy all drawings on canvas
|
||||
layer.destroyChildren();
|
||||
|
||||
//Check and draw selected Widget
|
||||
if (selectedWidgetNameData) {
|
||||
const { position: selectedWidgetPosition } = selectedWidgetNameData;
|
||||
|
||||
const position = widgetPosition || selectedWidgetPosition;
|
||||
|
||||
addWidgetNameToCanvas(
|
||||
layer,
|
||||
selectedWidgetNameData,
|
||||
position,
|
||||
"selected",
|
||||
);
|
||||
}
|
||||
|
||||
//Check and draw focused Widget
|
||||
if (focusedWidgetNameData) {
|
||||
const { position } = focusedWidgetNameData;
|
||||
|
||||
addWidgetNameToCanvas(layer, focusedWidgetNameData, position, "focused");
|
||||
}
|
||||
|
||||
layer.draw();
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouse Move event function, this tracks every mouse move on canvas such that
|
||||
* if the mouse position coincides with the positions of widget name, it makes the canvas intractable
|
||||
* This is throttled since it tracks every single mouse move
|
||||
*/
|
||||
const handleMouseMove = throttle((e: MouseEvent) => {
|
||||
const wrapper = wrapperRef?.current as HTMLDivElement;
|
||||
if (!wrapper) return;
|
||||
|
||||
//check if the mouse is coinciding with the widget name drawing on canvas
|
||||
const { cursor, isMouseOver } = getMouseOverDetails(e);
|
||||
|
||||
//if mouse over make the canvas intractable
|
||||
if (isMouseOver) {
|
||||
if (wrapper.style.pointerEvents === "none") {
|
||||
wrapper.style.pointerEvents = "auto";
|
||||
}
|
||||
} // if not mouse over then keep it default
|
||||
else if (wrapper.style.pointerEvents !== "none") {
|
||||
wrapper.style.pointerEvents = "none";
|
||||
wrapper.style.cursor = "default";
|
||||
}
|
||||
|
||||
//set cursor based on intractability
|
||||
if (!cursor) {
|
||||
wrapper.style.cursor = "default";
|
||||
} else if (wrapper.style.cursor !== cursor) {
|
||||
wrapper.style.cursor = cursor;
|
||||
}
|
||||
}, 20);
|
||||
|
||||
/**
|
||||
* on Drag Start event handler to enable drag of widget from the widget name component drawing on canvas
|
||||
* @param e
|
||||
*/
|
||||
const handleDragStart: DragEventHandler = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
//checks if the mouse is over the widget name, if so return it's details
|
||||
const { isMouseOver, widgetNameData } = getMouseOverDetails(
|
||||
e as unknown as MouseEvent,
|
||||
);
|
||||
|
||||
if (!isMouseOver || !shouldAllowDrag || widgetNameData?.dragDisabled)
|
||||
return;
|
||||
|
||||
//set dragging state
|
||||
const startPoints = {
|
||||
top: 0,
|
||||
left: 0,
|
||||
};
|
||||
showTableFilterPane();
|
||||
setDraggingState({
|
||||
isDragging: true,
|
||||
dragGroupActualParent: widgetNameData?.parentId,
|
||||
draggingGroupCenter: { widgetId: widgetNameData?.id },
|
||||
startPoints,
|
||||
draggedOn: widgetNameData?.parentId,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* handle Scroll of the canvas, this helps in keeping track og canvas scroll
|
||||
* so that the widget name remains accurately placed even when the canvas is scrolled
|
||||
*/
|
||||
const handleScroll = () => {
|
||||
if (!props.parentRef?.current) return;
|
||||
|
||||
const currentScrollTop: number = props.parentRef?.current?.scrollTop;
|
||||
|
||||
if (!isScrolling.current) {
|
||||
resetCanvas();
|
||||
}
|
||||
|
||||
clearTimeout(isScrolling.current);
|
||||
isScrolling.current = setTimeout(() => {
|
||||
scrollTop.current = currentScrollTop;
|
||||
//while scrolling update the widget name position
|
||||
updateSelectedWidgetPositions();
|
||||
isScrolling.current = 0;
|
||||
if (
|
||||
(props.parentRef?.current?.scrollHeight || 0) >
|
||||
(props.parentRef?.current?.clientHeight || 0)
|
||||
)
|
||||
hasScroll.current = true;
|
||||
}, 100);
|
||||
};
|
||||
|
||||
//Add event listeners
|
||||
useEffect(() => {
|
||||
if (
|
||||
!props.containerRef?.current ||
|
||||
!props.parentRef?.current ||
|
||||
!wrapperRef?.current
|
||||
)
|
||||
return;
|
||||
|
||||
const container: HTMLDivElement = props.containerRef
|
||||
?.current as HTMLDivElement;
|
||||
const parent: HTMLDivElement = props.parentRef?.current as HTMLDivElement;
|
||||
|
||||
container.addEventListener("mousemove", handleMouseMove);
|
||||
parent.addEventListener("scroll", handleScroll);
|
||||
return () => {
|
||||
container.removeEventListener("mousemove", handleMouseMove);
|
||||
parent.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}, [
|
||||
props.containerRef?.current,
|
||||
props.parentRef?.current,
|
||||
wrapperRef?.current,
|
||||
widgetNamePositions.current,
|
||||
canvasPositions.current,
|
||||
]);
|
||||
|
||||
/**
|
||||
* This Method verifies if the mouse position coincides with any widget name drawn on canvas
|
||||
* and returns details regarding the widget
|
||||
* @param e Mouse event
|
||||
* @returns Mainly isMouseOver indicating if the mouse is on any one of the widget name
|
||||
* if true also returns data regarding the widget
|
||||
*/
|
||||
const getMouseOverDetails = (e: MouseEvent) => {
|
||||
const x = e.clientX - canvasPositions.current.left;
|
||||
const y = e.clientY - canvasPositions.current.top;
|
||||
const widgetNamePositionsArray = Object.values(widgetNamePositions.current);
|
||||
|
||||
//for selected and focused widget names check the widget name positions with respect to mouse positions
|
||||
for (const widgetNamePosition of widgetNamePositionsArray) {
|
||||
if (widgetNamePosition) {
|
||||
const { height, left, top, widgetNameData, width } = widgetNamePosition;
|
||||
if (x > left && x < left + width && y > top && y < top + height) {
|
||||
return { isMouseOver: true, cursor: "pointer", widgetNameData };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { isMouseOver: false };
|
||||
};
|
||||
|
||||
//Used when the position of selected or focused widget changes
|
||||
useEffect(() => {
|
||||
if (!selectedWidgetNameData && !focusedWidgetNameData) {
|
||||
resetCanvas();
|
||||
} else {
|
||||
updateSelectedWidgetPositions();
|
||||
}
|
||||
}, [selectedWidgetNameData, focusedWidgetNameData]);
|
||||
|
||||
/**
|
||||
* Resets canvas when there is nothing to be drawn on canvas
|
||||
*/
|
||||
const resetCanvas = () => {
|
||||
// Resets stored widget position names
|
||||
widgetNamePositions.current = { selected: undefined, focused: undefined };
|
||||
|
||||
// clears all drawings on canvas
|
||||
const stage = stageRef.current;
|
||||
if (!stage) return;
|
||||
const layer = stage.getLayers()[0];
|
||||
if (!layer) return;
|
||||
layer.destroyChildren();
|
||||
layer.draw();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
draggable
|
||||
id={WIDGET_NAME_CANVAS}
|
||||
onDragStart={handleDragStart}
|
||||
ref={wrapperRef}
|
||||
style={widgetNameWrapperStyle}
|
||||
>
|
||||
<Stage
|
||||
height={
|
||||
canvasPositions?.current.height || DEFAULT_WIDGET_NAME_CANVAS_HEIGHT
|
||||
}
|
||||
ref={stageRef}
|
||||
width={props.canvasWidth + WIDGET_NAME_CANVAS_PADDING}
|
||||
>
|
||||
<Layer />
|
||||
</Stage>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OverlayCanvasContainer;
|
||||
147
app/client/src/layoutSystems/common/WidgetNamesCanvas/utils.ts
Normal file
147
app/client/src/layoutSystems/common/WidgetNamesCanvas/utils.ts
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
import Konva from "konva";
|
||||
import type { WidgetPosition } from "layoutSystems/common/types";
|
||||
import type { WidgetNameData, WidgetNamePositionData } from "./WidgetNameTypes";
|
||||
import {
|
||||
warningSVGPath,
|
||||
WidgetNameState,
|
||||
WIDGET_NAME_FILL_COLORS,
|
||||
WIDGET_NAME_FONT_SIZE,
|
||||
WIDGET_NAME_HEIGHT,
|
||||
WIDGET_NAME_HORIZONTAL_PADDING,
|
||||
WIDGET_NAME_ICON_PADDING,
|
||||
WIDGET_NAME_TEXT_COLOR,
|
||||
WIDGET_NAME_VERTICAL_PADDING,
|
||||
} from "./WidgetNameConstants";
|
||||
|
||||
/**
|
||||
* used to get the Konva Group Element that is a group of all the elements
|
||||
* that are to be drawn as part of widget name on canvas
|
||||
* @param position Position of widget
|
||||
* @param widgetName widget name
|
||||
* @param widgetNameData widget name data contains more information regarding the widget that helps in determining the state of widget name
|
||||
* @param parentDOM DOM element the MainContainerWrapper component i.e, the parent of the canvas component
|
||||
* @param htmlCanvasDOM DOM element of the html canvas on which the widget name is drawn
|
||||
* @param scrollTop amount of pixels scrolled by canvas
|
||||
* @returns an object that contains
|
||||
* widgetName Group on Konva, position of widgetName on canvas and canvas offsets
|
||||
*/
|
||||
export const getWidgetNameComponent = (
|
||||
position: WidgetPosition,
|
||||
widgetName: string,
|
||||
widgetNameData: WidgetNameData,
|
||||
parentDOM: HTMLDivElement | null,
|
||||
htmlCanvasDOM: HTMLDivElement | undefined,
|
||||
scrollTop: number,
|
||||
) => {
|
||||
let showIcon = false;
|
||||
|
||||
const { nameState } = widgetNameData;
|
||||
|
||||
if (nameState === WidgetNameState.ERROR) {
|
||||
showIcon = true;
|
||||
}
|
||||
|
||||
//Defining Text Element
|
||||
const textEl = new Konva.Text({
|
||||
fill: WIDGET_NAME_TEXT_COLOR,
|
||||
fontFamily: "sans-serif",
|
||||
fontSize: WIDGET_NAME_FONT_SIZE,
|
||||
text: widgetName,
|
||||
x: showIcon
|
||||
? WIDGET_NAME_ICON_PADDING + WIDGET_NAME_HORIZONTAL_PADDING
|
||||
: WIDGET_NAME_HORIZONTAL_PADDING,
|
||||
y: WIDGET_NAME_VERTICAL_PADDING,
|
||||
});
|
||||
|
||||
const textWidth: number = textEl.width();
|
||||
const componentWidth: number =
|
||||
textWidth +
|
||||
WIDGET_NAME_HORIZONTAL_PADDING * 2 +
|
||||
(showIcon ? WIDGET_NAME_ICON_PADDING : 0);
|
||||
|
||||
const {
|
||||
canvasLeftOffset,
|
||||
canvasTopOffset,
|
||||
left: widgetLeft,
|
||||
top: widgetTop,
|
||||
} = getPositionsForBoundary(parentDOM, htmlCanvasDOM, position, scrollTop);
|
||||
const left: number = widgetLeft + position.width - componentWidth + 0.5;
|
||||
const top: number = widgetTop - WIDGET_NAME_HEIGHT;
|
||||
|
||||
//Store the widget name positions for future use
|
||||
const widgetNamePosition: WidgetNamePositionData = {
|
||||
left: left,
|
||||
top: top,
|
||||
width: componentWidth,
|
||||
height: WIDGET_NAME_HEIGHT,
|
||||
widgetNameData: widgetNameData,
|
||||
};
|
||||
|
||||
//rectangle encompassing the widget name
|
||||
const rectEl = new Konva.Rect({
|
||||
cornerRadius: [4, 4, 0, 0],
|
||||
fill: WIDGET_NAME_FILL_COLORS[nameState],
|
||||
height: WIDGET_NAME_HEIGHT,
|
||||
width: componentWidth,
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
//Icon in widget name componenet in case of error
|
||||
const iconEl = new Konva.Path({
|
||||
x: WIDGET_NAME_HORIZONTAL_PADDING,
|
||||
y: WIDGET_NAME_VERTICAL_PADDING,
|
||||
data: warningSVGPath,
|
||||
fill: WIDGET_NAME_TEXT_COLOR,
|
||||
scaleX: 0.7,
|
||||
scaleY: 0.7,
|
||||
});
|
||||
|
||||
//Group Containing all the elements of that particular widget name
|
||||
const groupEl = new Konva.Group({
|
||||
height: WIDGET_NAME_HEIGHT,
|
||||
width: componentWidth,
|
||||
x: left,
|
||||
y: top,
|
||||
});
|
||||
|
||||
groupEl.add(rectEl);
|
||||
groupEl.add(textEl);
|
||||
showIcon && groupEl.add(iconEl);
|
||||
|
||||
return {
|
||||
widgetNameComponent: groupEl,
|
||||
widgetNamePosition,
|
||||
canvasLeftOffset,
|
||||
canvasTopOffset,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to calculate the positions of the widget with respect to the HTML Canvas that is rendered by Konva
|
||||
* @param parentDOM DOM element the MainContainerWrapper component i.e, the parent of the canvas component
|
||||
* @param htmlCanvasDOM DOM element of the html canvas on which the widget name is drawn
|
||||
* @param position position of widget with respect to client window in pixels
|
||||
* @param scrollTop amount of pixels scrolled by canvas
|
||||
* @returns mainly the left and top of widget with respect to the html canvas
|
||||
* and also the canvas offset
|
||||
*/
|
||||
const getPositionsForBoundary = (
|
||||
parentDOM: HTMLDivElement | null,
|
||||
htmlCanvasDOM: HTMLDivElement | undefined,
|
||||
position: WidgetPosition,
|
||||
scrollTop: number,
|
||||
) => {
|
||||
const { left: parentLeft = 0, top: parentTop = 0 } =
|
||||
parentDOM?.getBoundingClientRect() || {};
|
||||
const { left: canvasLeft = 0, top: canvasTop = 0 } =
|
||||
htmlCanvasDOM?.getBoundingClientRect() || {};
|
||||
|
||||
const canvasLeftOffset = parentLeft - canvasLeft;
|
||||
const canvasTopOffset = parentTop - canvasTop;
|
||||
|
||||
const left: number = position.left + canvasLeftOffset;
|
||||
const top: number = position.top + canvasTopOffset - scrollTop;
|
||||
|
||||
return { left, top, canvasLeftOffset, canvasTopOffset };
|
||||
};
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { canDrag } from "./DraggableComponent";
|
||||
|
||||
describe("DraggableComponent", () => {
|
||||
it("it tests draggable canDrag helper function", () => {
|
||||
expect(
|
||||
canDrag(false, false, { dragDisabled: false }, false, false, false),
|
||||
).toBe(true);
|
||||
expect(
|
||||
canDrag(true, false, { dragDisabled: false }, false, false, false),
|
||||
).toBe(false);
|
||||
expect(
|
||||
canDrag(false, true, { dragDisabled: false }, false, false, false),
|
||||
).toBe(false);
|
||||
expect(
|
||||
canDrag(false, false, { dragDisabled: true }, false, false, false),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -5,10 +5,6 @@ import type { CSSProperties, DragEventHandler, ReactNode } from "react";
|
|||
import React, { useMemo, useRef } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useSelector } from "react-redux";
|
||||
import {
|
||||
previewModeSelector,
|
||||
snipingModeSelector,
|
||||
} from "selectors/editorSelectors";
|
||||
import {
|
||||
isCurrentWidgetFocused,
|
||||
isWidgetSelected,
|
||||
|
|
@ -19,7 +15,7 @@ import {
|
|||
useShowTableFilterPane,
|
||||
useWidgetDragResize,
|
||||
} from "utils/hooks/dragResizeHooks";
|
||||
import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettingsPaneSelectors";
|
||||
import { getShouldAllowDrag } from "selectors/widgetDragSelectors";
|
||||
|
||||
const DraggableWrapper = styled.div`
|
||||
display: block;
|
||||
|
|
@ -43,6 +39,7 @@ type DraggableComponentProps = {
|
|||
parentRowSpace: number;
|
||||
parentColumnSpace: number;
|
||||
children: ReactNode;
|
||||
dragDisabled?: boolean;
|
||||
};
|
||||
|
||||
// Widget Boundaries which is shown to indicate the boundaries of the widget
|
||||
|
|
@ -59,42 +56,11 @@ const WidgetBoundaries = styled.div`
|
|||
left: 0;
|
||||
`;
|
||||
|
||||
/**
|
||||
* can drag helper function to know if drag and drop should apply
|
||||
*
|
||||
* @param isResizingOrDragging
|
||||
* @param isDraggingDisabled
|
||||
* @param props
|
||||
* @param isSnipingMode
|
||||
* @param isPreviewMode
|
||||
* @returns
|
||||
*/
|
||||
export const canDrag = (
|
||||
isResizingOrDragging: boolean,
|
||||
isDraggingDisabled: boolean,
|
||||
props: any,
|
||||
isSnipingMode: boolean,
|
||||
isPreviewMode: boolean,
|
||||
isAppSettingsPaneWithNavigationTabOpen: boolean,
|
||||
) => {
|
||||
return (
|
||||
!isResizingOrDragging &&
|
||||
!isDraggingDisabled &&
|
||||
!props?.dragDisabled &&
|
||||
!isSnipingMode &&
|
||||
!isPreviewMode &&
|
||||
!isAppSettingsPaneWithNavigationTabOpen
|
||||
);
|
||||
};
|
||||
|
||||
function DraggableComponent(props: DraggableComponentProps) {
|
||||
// Dispatch hook handy to set a widget as focused/selected
|
||||
const { focusWidget, selectWidget } = useWidgetSelection();
|
||||
const isSnipingMode = useSelector(snipingModeSelector);
|
||||
const isPreviewMode = useSelector(previewModeSelector);
|
||||
const isAppSettingsPaneWithNavigationTabOpen = useSelector(
|
||||
getIsAppSettingsPaneWithNavigationTabOpen,
|
||||
);
|
||||
|
||||
const shouldAllowDrag = useSelector(getShouldAllowDrag);
|
||||
// Dispatch hook handy to set any `DraggableComponent` as dragging/ not dragging
|
||||
// The value is boolean
|
||||
const { setDraggingState } = useWidgetDragResize();
|
||||
|
|
@ -120,13 +86,6 @@ function DraggableComponent(props: DraggableComponentProps) {
|
|||
state.ui.widgetDragResize?.dragDetails?.draggedOn === props.parentId,
|
||||
);
|
||||
|
||||
// This state tells us to disable dragging,
|
||||
// This is usually true when widgets themselves implement drag/drop
|
||||
// This flag resolves conflicting drag/drop triggers.
|
||||
const isDraggingDisabled: boolean = useSelector(
|
||||
(state: AppState) => state.ui.widgetDragResize.isDraggingDisabled,
|
||||
);
|
||||
|
||||
// True when any widget is dragging or resizing, including this one
|
||||
const isResizingOrDragging = !!isResizing || !!isDragging;
|
||||
const isCurrentWidgetDragging = isDragging && isSelected;
|
||||
|
|
@ -159,14 +118,7 @@ function DraggableComponent(props: DraggableComponentProps) {
|
|||
.join("")
|
||||
.toLowerCase()}`;
|
||||
|
||||
const allowDrag = canDrag(
|
||||
isResizingOrDragging,
|
||||
isDraggingDisabled,
|
||||
props,
|
||||
isSnipingMode,
|
||||
isPreviewMode,
|
||||
isAppSettingsPaneWithNavigationTabOpen,
|
||||
);
|
||||
const allowDrag = !props?.dragDisabled && shouldAllowDrag;
|
||||
const className = `${classNameForTesting}`;
|
||||
const draggableRef = useRef<HTMLDivElement>(null);
|
||||
const onDragStart: DragEventHandler = (e) => {
|
||||
|
|
|
|||
124
app/client/src/layoutSystems/common/selectors.ts
Normal file
124
app/client/src/layoutSystems/common/selectors.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import type { AppState } from "@appsmith/reducers";
|
||||
import { createSelector } from "reselect";
|
||||
import { getFocusedWidget, getSelectedWidgets } from "selectors/ui";
|
||||
import { getDataTree } from "selectors/dataTreeSelectors";
|
||||
import { getWidgets } from "sagas/selectors";
|
||||
import { getShouldShowWidgetName } from "@appsmith/selectors/entitiesSelector";
|
||||
import { WidgetNameState } from "./WidgetNamesCanvas/WidgetNameConstants";
|
||||
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
|
||||
import { EVAL_ERROR_PATH } from "utils/DynamicBindingUtils";
|
||||
import get from "lodash/get";
|
||||
import { getErrorCount } from "layoutSystems/common/widgetName/utils";
|
||||
import type { WidgetPositions } from "./types";
|
||||
import type { WidgetProps } from "widgets/BaseWidget";
|
||||
import type { WidgetNameData } from "./WidgetNamesCanvas/WidgetNameTypes";
|
||||
import type { DataTree } from "@appsmith/entities/DataTree/types";
|
||||
|
||||
export const getWidgetPositions = (state: AppState) =>
|
||||
state.entities.widgetPositions;
|
||||
|
||||
/**
|
||||
* method to get the widget data required to draw widget name component on canvas
|
||||
* @param widget widget whose widget name will be drawn on canvas
|
||||
* @param dataTree contains evaluated widget information that is used to check of the widget has any errors
|
||||
* @param positions positions of all the widgets in pixels
|
||||
* @param isFocused boolean to indicate if the widget is focused
|
||||
* @returns WidgetNameData object which contains information regarding the widget to draw it's widget name on canvas
|
||||
*/
|
||||
const getWidgetNameState = (
|
||||
widget: WidgetProps,
|
||||
dataTree: DataTree,
|
||||
positions: WidgetPositions,
|
||||
isFocused = false,
|
||||
): WidgetNameData => {
|
||||
let nameState = isFocused
|
||||
? WidgetNameState.FOCUSED
|
||||
: WidgetNameState.SELECTED;
|
||||
|
||||
const widgetName = widget.widgetName;
|
||||
|
||||
const widgetEntity = dataTree[widgetName];
|
||||
|
||||
const parentId = widget.parentId || MAIN_CONTAINER_WIDGET_ID;
|
||||
|
||||
if (widgetEntity) {
|
||||
const errorObj = get(widgetEntity, EVAL_ERROR_PATH, {});
|
||||
const errorCount = getErrorCount(errorObj);
|
||||
|
||||
if (errorCount > 0) {
|
||||
nameState = WidgetNameState.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
const widgetNameData = {
|
||||
id: widget.widgetId,
|
||||
position: positions[widget.widgetId],
|
||||
widgetName: widgetName,
|
||||
parentId,
|
||||
dragDisabled: widget.dragDisabled,
|
||||
nameState,
|
||||
};
|
||||
|
||||
return widgetNameData;
|
||||
};
|
||||
|
||||
/**
|
||||
* selector to get information regarding the selected widget to draw it's widget name on canvas
|
||||
*/
|
||||
export const getSelectedWidgetNameData = createSelector(
|
||||
getWidgetPositions,
|
||||
getSelectedWidgets,
|
||||
getWidgets,
|
||||
getDataTree,
|
||||
getShouldShowWidgetName,
|
||||
(
|
||||
positions,
|
||||
selectedWidgets,
|
||||
widgets,
|
||||
dataTree,
|
||||
shouldShowWidgetName,
|
||||
): WidgetNameData | undefined => {
|
||||
if (
|
||||
!selectedWidgets ||
|
||||
selectedWidgets.length !== 1 ||
|
||||
!shouldShowWidgetName
|
||||
)
|
||||
return;
|
||||
|
||||
const selectedWidgetId = selectedWidgets[0];
|
||||
|
||||
const selectedWidget = widgets[selectedWidgetId];
|
||||
|
||||
if (!selectedWidget) return;
|
||||
|
||||
return getWidgetNameState(selectedWidget, dataTree, positions);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* selector to get information regarding the focused widget to draw it's widget name on canvas
|
||||
*/
|
||||
export const getFocusedWidgetNameData = createSelector(
|
||||
getWidgetPositions,
|
||||
getFocusedWidget,
|
||||
getSelectedWidgets,
|
||||
getWidgets,
|
||||
getDataTree,
|
||||
getShouldShowWidgetName,
|
||||
(
|
||||
positions,
|
||||
focusedWidgetId,
|
||||
selectedWidgets,
|
||||
widgets,
|
||||
dataTree,
|
||||
shouldShowWidgetName,
|
||||
): WidgetNameData | undefined => {
|
||||
if (!focusedWidgetId || !widgets || !shouldShowWidgetName) return;
|
||||
|
||||
const focusedWidget = widgets[focusedWidgetId];
|
||||
|
||||
if (!focusedWidget || selectedWidgets.indexOf(focusedWidgetId) > -1) return;
|
||||
|
||||
return getWidgetNameState(focusedWidget, dataTree, positions, true);
|
||||
},
|
||||
);
|
||||
13
app/client/src/layoutSystems/common/types.ts
Normal file
13
app/client/src/layoutSystems/common/types.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
Hols the position of a widget in pixels from the top left of the MainContainer
|
||||
*/
|
||||
export type WidgetPosition = {
|
||||
left: number;
|
||||
top: number;
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
|
||||
export interface WidgetPositions {
|
||||
[widgetId: string]: WidgetPosition;
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
import { debounce } from "lodash";
|
||||
import type { RefObject } from "react";
|
||||
import { ANVIL_LAYER, ANVIL_WIDGET, LAYOUT } from "./utils";
|
||||
import store from "store";
|
||||
import { readWidgetPositions } from "layoutSystems/anvil/integrations/actions";
|
||||
|
||||
/**
|
||||
* This Class's main function is to batch all the registered widgets, Flex layers and layout components
|
||||
* and dispatch an action to process all the affected widgets to determine all the widgets' positions
|
||||
*
|
||||
* This inturn acts as an observer to find out update widgets positions whenever the widget position changes
|
||||
*/
|
||||
class WidgetPositionsObserver {
|
||||
// Objects to store registered elements
|
||||
private registeredWidgets: {
|
||||
[widgetDOMId: string]: { ref: RefObject<HTMLDivElement>; id: string };
|
||||
} = {};
|
||||
private registeredLayers: {
|
||||
[layerId: string]: {
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
canvasId: string;
|
||||
layerIndex: number;
|
||||
};
|
||||
} = {};
|
||||
private registeredLayouts: {
|
||||
[layoutId: string]: {
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
layoutId: string;
|
||||
canvasId: string;
|
||||
};
|
||||
} = {};
|
||||
|
||||
//Queues to process the registered elements that changed
|
||||
private widgetsProcessQueue: {
|
||||
[widgetId: string]: boolean;
|
||||
} = {};
|
||||
private layersProcessQueue: { [canvasId: string]: number } = {};
|
||||
private layoutsProcessQueue: { [key: string]: boolean } = {};
|
||||
|
||||
private debouncedProcessBatch = debounce(this.processWidgetBatch, 200);
|
||||
|
||||
// All the registered elements are registered with this Resize observer
|
||||
// When any of the elements changes size this observer triggers it
|
||||
// Add the elements are added to queue to further batch and process
|
||||
private resizeObserver = new ResizeObserver(
|
||||
(entries: ResizeObserverEntry[]) => {
|
||||
for (const entry of entries) {
|
||||
if (entry?.target?.id) {
|
||||
const DOMId = entry?.target?.id;
|
||||
if (DOMId.indexOf(ANVIL_WIDGET) > -1) {
|
||||
this.addWidgetToProcess(DOMId);
|
||||
} else if (DOMId.indexOf(ANVIL_LAYER) > -1) {
|
||||
this.addLayerToProcess(DOMId);
|
||||
} else if (DOMId.indexOf(LAYOUT) > -1) {
|
||||
this.addLayoutToProcess(DOMId);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
//Method to register widgets for resize observer changes
|
||||
public observeWidget(
|
||||
widgetId: string,
|
||||
widgetDOMId: string,
|
||||
ref: RefObject<HTMLDivElement>,
|
||||
) {
|
||||
if (ref.current) {
|
||||
this.registeredWidgets[widgetDOMId] = { ref, id: widgetId };
|
||||
this.resizeObserver.observe(ref.current);
|
||||
this.addWidgetToProcess(widgetDOMId);
|
||||
}
|
||||
}
|
||||
|
||||
//Method to de register widgets for resize observer changes
|
||||
public unObserveWidget(widgetDOMId: string) {
|
||||
const element = this.registeredWidgets[widgetDOMId]?.ref?.current;
|
||||
if (element) {
|
||||
this.resizeObserver.unobserve(element);
|
||||
}
|
||||
|
||||
delete this.registeredWidgets[widgetDOMId];
|
||||
}
|
||||
|
||||
//Method to register layers for resize observer changes
|
||||
public observeLayer(
|
||||
layerId: string,
|
||||
canvasId: string,
|
||||
layerIndex: number,
|
||||
ref: RefObject<HTMLDivElement>,
|
||||
) {
|
||||
if (ref?.current) {
|
||||
this.registeredLayers[layerId] = { ref, canvasId, layerIndex };
|
||||
this.resizeObserver.observe(ref.current);
|
||||
}
|
||||
}
|
||||
|
||||
//Method to de register layers for resize observer changes
|
||||
public unObserveLayer(layerId: string) {
|
||||
const element = this.registeredLayers[layerId]?.ref?.current;
|
||||
if (element) {
|
||||
this.resizeObserver.unobserve(element);
|
||||
}
|
||||
|
||||
delete this.registeredLayers[layerId];
|
||||
}
|
||||
|
||||
//Method to register layouts for resize observer changes
|
||||
public observeLayout(
|
||||
layoutId: string,
|
||||
ref: RefObject<HTMLDivElement>,
|
||||
canvasId: string,
|
||||
id: string,
|
||||
) {
|
||||
if (ref?.current) {
|
||||
this.registeredLayouts[layoutId] = { ref, canvasId, layoutId: id };
|
||||
this.resizeObserver.observe(ref.current);
|
||||
}
|
||||
}
|
||||
|
||||
//Method to de register layouts for resize observer changes
|
||||
public unObserveLayout(layoutId: string) {
|
||||
const element = this.registeredLayouts[layoutId]?.ref?.current;
|
||||
if (element) {
|
||||
this.resizeObserver.unobserve(element);
|
||||
}
|
||||
|
||||
delete this.registeredLayouts[layoutId];
|
||||
}
|
||||
|
||||
//This method is triggered from the resize observer to add widgets to queue
|
||||
private addWidgetToProcess(widgetDOMId: string) {
|
||||
if (this.registeredWidgets[widgetDOMId]) {
|
||||
const widgetId = this.registeredWidgets[widgetDOMId].id;
|
||||
this.widgetsProcessQueue[widgetId] = true;
|
||||
this.debouncedProcessBatch();
|
||||
}
|
||||
}
|
||||
|
||||
//This method is triggered from the resize observer to add layer to queue
|
||||
private addLayerToProcess(LayerId: string) {
|
||||
if (this.registeredLayers[LayerId]) {
|
||||
const { canvasId, layerIndex } = this.registeredLayers[LayerId];
|
||||
|
||||
//If the layer in canvas already exist
|
||||
//and if the current layer is further higher than the previous one
|
||||
//add this layer to queue
|
||||
if (
|
||||
this.layersProcessQueue[canvasId] === undefined ||
|
||||
this.layersProcessQueue[canvasId] > layerIndex
|
||||
) {
|
||||
this.layersProcessQueue[canvasId] = layerIndex;
|
||||
}
|
||||
this.debouncedProcessBatch();
|
||||
}
|
||||
}
|
||||
|
||||
//This method is triggered from the resize observer to add layout to queue
|
||||
private addLayoutToProcess(layoutId: string) {
|
||||
if (this.registeredLayouts[layoutId]) {
|
||||
const id = this.registeredLayouts[layoutId].layoutId;
|
||||
this.layoutsProcessQueue[id] = true;
|
||||
this.debouncedProcessBatch();
|
||||
}
|
||||
}
|
||||
|
||||
//Clear all process queues
|
||||
private clearProcessQueue() {
|
||||
this.widgetsProcessQueue = {};
|
||||
this.layersProcessQueue = {};
|
||||
this.layoutsProcessQueue = {};
|
||||
}
|
||||
|
||||
//Dispatch all the changed elements to saga for further processing to update widget positions
|
||||
private processWidgetBatch() {
|
||||
store.dispatch(
|
||||
readWidgetPositions(
|
||||
{ ...this.widgetsProcessQueue },
|
||||
{ ...this.layersProcessQueue },
|
||||
{ ...this.layoutsProcessQueue },
|
||||
),
|
||||
);
|
||||
this.clearProcessQueue();
|
||||
}
|
||||
}
|
||||
|
||||
export const widgetPositionsObserver = new WidgetPositionsObserver();
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
export const ANVIL_LAYER = "anvil_layer";
|
||||
export const ANVIL_WIDGET = "anvil_widget";
|
||||
export const LAYOUT = "layout";
|
||||
|
||||
/**
|
||||
* Method to return Id of widget with widgetId
|
||||
* @param widgetId
|
||||
* @returns
|
||||
*/
|
||||
export const getAnvilWidgetId = (widgetId: string) => {
|
||||
return ANVIL_WIDGET + "_" + widgetId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Method to return Id of layer in canvasId of index layerIndex
|
||||
* @param canvasId
|
||||
* @param layerIndex
|
||||
* @returns
|
||||
*/
|
||||
export const getAnvilLayerId = (canvasId: string, layerIndex: number) => {
|
||||
return ANVIL_LAYER + "_" + canvasId + "_" + layerIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Method to return Id of layout with layoutId
|
||||
* @param layoutId
|
||||
* @returns
|
||||
*/
|
||||
export const getLayoutId = (layoutId: string) => {
|
||||
return LAYOUT + "_" + layoutId;
|
||||
};
|
||||
|
|
@ -15,6 +15,7 @@ import {
|
|||
useTheme,
|
||||
} from "@design-system/theming";
|
||||
import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettingsPaneSelectors";
|
||||
import { CANVAS_ART_BOARD } from "constants/componentClassNameConstants";
|
||||
import { renderAppsmithCanvas } from "layoutSystems/CanvasFactory";
|
||||
import type { WidgetProps } from "widgets/BaseWidget";
|
||||
|
||||
|
|
@ -83,8 +84,8 @@ const Canvas = (props: CanvasProps) => {
|
|||
className={`relative t--canvas-artboard ${paddingBottomClass} transition-all duration-400 ${marginHorizontalClass} ${getViewportClassName(
|
||||
canvasWidth,
|
||||
)}`}
|
||||
data-testid="t--canvas-artboard"
|
||||
id="art-board"
|
||||
data-testid={"t--canvas-artboard"}
|
||||
id={CANVAS_ART_BOARD}
|
||||
ref={focusRef}
|
||||
width={canvasWidth}
|
||||
>
|
||||
|
|
|
|||
37
app/client/src/reducers/entityReducers/index.ts
Normal file
37
app/client/src/reducers/entityReducers/index.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { combineReducers } from "redux";
|
||||
import appReducer from "./appReducer";
|
||||
import canvasWidgetsReducer from "./canvasWidgetsReducer";
|
||||
import canvasWidgetsStructureReducer from "./canvasWidgetsStructureReducer";
|
||||
import metaWidgetsReducer from "./metaWidgetsReducer";
|
||||
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 autoHeightLayoutTreeReducer from "./autoHeightReducers/autoHeightLayoutTreeReducer";
|
||||
import canvasLevelsReducer from "./autoHeightReducers/canvasLevelsReducer";
|
||||
import actionsReducer from "@appsmith/reducers/entityReducers/actionsReducer";
|
||||
|
||||
/* Reducers which are integrated into the core system when registering a pluggable module
|
||||
or done so by a module that is designed to be eventually pluggable */
|
||||
import widgetPositionsReducer from "layoutSystems/anvil/integrations/reducers/widgetPositionsReducer";
|
||||
|
||||
const entityReducer = combineReducers({
|
||||
canvasWidgets: canvasWidgetsReducer,
|
||||
canvasWidgetsStructure: canvasWidgetsStructureReducer,
|
||||
metaWidgets: metaWidgetsReducer,
|
||||
actions: actionsReducer,
|
||||
datasources: datasourceReducer,
|
||||
pageList: pageListReducer,
|
||||
jsExecutions: jsExecutionsReducer,
|
||||
plugins: pluginsReducer,
|
||||
meta: metaReducer,
|
||||
app: appReducer,
|
||||
jsActions: jsActionsReducer,
|
||||
autoHeightLayoutTree: autoHeightLayoutTreeReducer,
|
||||
canvasLevels: canvasLevelsReducer,
|
||||
widgetPositions: widgetPositionsReducer,
|
||||
});
|
||||
|
||||
export default entityReducer;
|
||||
42
app/client/src/selectors/widgetDragSelectors.ts
Normal file
42
app/client/src/selectors/widgetDragSelectors.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import type { AppState } from "@appsmith/reducers";
|
||||
import { createSelector } from "reselect";
|
||||
import { getIsAppSettingsPaneWithNavigationTabOpen } from "./appSettingsPaneSelectors";
|
||||
import { previewModeSelector, snipingModeSelector } from "./editorSelectors";
|
||||
|
||||
export const getIsDragging = (state: AppState) =>
|
||||
state.ui.widgetDragResize.isDragging;
|
||||
|
||||
export const getIsResizing = (state: AppState) =>
|
||||
state.ui.widgetDragResize.isResizing;
|
||||
|
||||
export const getIsDraggingDisabledInEditor = (state: AppState) =>
|
||||
state.ui.widgetDragResize.isDraggingDisabled;
|
||||
|
||||
/**
|
||||
* getShouldAllowDrag is a Selector that indicates if the widget could be dragged on canvas based on different states
|
||||
*/
|
||||
export const getShouldAllowDrag = createSelector(
|
||||
getIsResizing,
|
||||
getIsDragging,
|
||||
getIsDraggingDisabledInEditor,
|
||||
previewModeSelector,
|
||||
snipingModeSelector,
|
||||
getIsAppSettingsPaneWithNavigationTabOpen,
|
||||
(
|
||||
isResizing,
|
||||
isDragging,
|
||||
isDraggingDisabled,
|
||||
isPreviewMode,
|
||||
isSnipingMode,
|
||||
isAppSettingsPaneWithNavigationTabOpen,
|
||||
) => {
|
||||
return (
|
||||
!isResizing &&
|
||||
!isDragging &&
|
||||
!isDraggingDisabled &&
|
||||
!isSnipingMode &&
|
||||
!isPreviewMode &&
|
||||
!isAppSettingsPaneWithNavigationTabOpen
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -47,6 +47,7 @@ import type { ThemeProp } from "WidgetProvider/constants";
|
|||
import { isAirgapped } from "@appsmith/utils/airgapHelpers";
|
||||
import { importSvg } from "design-system-old";
|
||||
import { getVideoConstraints } from "../../utils";
|
||||
import { CANVAS_ART_BOARD } from "constants/componentClassNameConstants";
|
||||
|
||||
const CameraOfflineIcon = importSvg(
|
||||
() => import("assets/icons/widget/camera/camera-offline.svg"),
|
||||
|
|
@ -806,7 +807,9 @@ function DevicePopover(props: DevicePopoverProps) {
|
|||
content={<DeviceMenu items={items} onItemClick={onItemClick} />}
|
||||
disabled={disabledMenu}
|
||||
minimal
|
||||
portalContainer={document.getElementById("art-board") || undefined}
|
||||
portalContainer={
|
||||
document.getElementById(CANVAS_ART_BOARD) || undefined
|
||||
}
|
||||
>
|
||||
<Button
|
||||
disabled={disabledMenu}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Classes } from "@blueprintjs/core";
|
|||
import { countryToFlag } from "./utilities";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { lightenColor } from "widgets/WidgetUtils";
|
||||
import { CANVAS_ART_BOARD } from "constants/componentClassNameConstants";
|
||||
|
||||
const StyledDropdown = styled(Dropdown)`
|
||||
/*
|
||||
|
|
@ -256,7 +257,7 @@ export default function CurrencyTypeDropdown(props: CurrencyDropdownProps) {
|
|||
optionWidth="360px"
|
||||
options={props.options}
|
||||
portalClassName={`country-type-filter-dropdown-${props.widgetId}`}
|
||||
portalContainer={document.getElementById("art-board") || undefined}
|
||||
portalContainer={document.getElementById(CANVAS_ART_BOARD) || undefined}
|
||||
searchAutoFocus
|
||||
searchPlaceholder="Search by currency or country"
|
||||
selected={selectedOption}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import LabelWithTooltip, {
|
|||
|
||||
const DATEPICKER_POPUP_CLASSNAME = "datepickerwidget-popup";
|
||||
import { required } from "utils/validation/common";
|
||||
import { CANVAS_ART_BOARD } from "constants/componentClassNameConstants";
|
||||
|
||||
function hasFulfilledRequiredCondition(
|
||||
isRequired: boolean | undefined,
|
||||
|
|
@ -381,7 +382,7 @@ class DatePickerComponent extends React.Component<
|
|||
placeholder={"Select Date"}
|
||||
popoverProps={{
|
||||
portalContainer:
|
||||
document.getElementById("art-board") || undefined,
|
||||
document.getElementById(CANVAS_ART_BOARD) || undefined,
|
||||
usePortal: !this.props.withoutPortal,
|
||||
canEscapeKeyClose: true,
|
||||
portalClassName: `${DATEPICKER_POPUP_CLASSNAME}-${this.props.widgetId}`,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { ISDCodeOptions } from "constants/ISDCodes_v2";
|
|||
import { Colors } from "constants/Colors";
|
||||
import { Classes } from "@blueprintjs/core";
|
||||
import { lightenColor } from "widgets/WidgetUtils";
|
||||
import { CANVAS_ART_BOARD } from "constants/componentClassNameConstants";
|
||||
|
||||
type DropdownTriggerIconWrapperProp = {
|
||||
allowDialCodeChange: boolean;
|
||||
|
|
@ -280,7 +281,7 @@ export default function ISDCodeDropdown(props: ISDCodeDropdownProps) {
|
|||
optionWidth="360px"
|
||||
options={props.options}
|
||||
portalClassName={`country-type-filter-dropdown-${props.widgetId}`}
|
||||
portalContainer={document.getElementById("art-board") || undefined}
|
||||
portalContainer={document.getElementById(CANVAS_ART_BOARD) || undefined}
|
||||
searchAutoFocus
|
||||
searchPlaceholder="Search by ISD code or country"
|
||||
selected={props.selected}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import type { LabelPosition } from "components/constants";
|
|||
import SelectButton from "./SelectButton";
|
||||
import { labelMargin } from "../../WidgetUtils";
|
||||
import LabelWithTooltip from "widgets/components/LabelWithTooltip";
|
||||
import { CANVAS_ART_BOARD } from "constants/componentClassNameConstants";
|
||||
|
||||
const DEBOUNCE_TIMEOUT = 800;
|
||||
const ITEM_SIZE = 40;
|
||||
|
|
@ -383,7 +384,7 @@ class SelectComponent extends React.Component<
|
|||
onQueryChange={this.onQueryChange}
|
||||
popoverProps={{
|
||||
portalContainer:
|
||||
document.getElementById("art-board") || undefined,
|
||||
document.getElementById(CANVAS_ART_BOARD) || undefined,
|
||||
boundary: "window",
|
||||
isOpen: this.state.isOpen,
|
||||
minimal: true,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { isColumnTypeEditable } from "widgets/TableWidgetV2/widget/utilities";
|
|||
import { Popover2 } from "@blueprintjs/popover2";
|
||||
import { MenuDivider } from "@design-system/widgets-old";
|
||||
import { importRemixIcon, importSvg } from "@design-system/widgets-old";
|
||||
import { CANVAS_ART_BOARD } from "constants/componentClassNameConstants";
|
||||
|
||||
const Check = importRemixIcon(() => import("remixicon-react/CheckFillIcon"));
|
||||
const ArrowDownIcon = importRemixIcon(
|
||||
|
|
@ -326,7 +327,9 @@ const HeaderCellComponent = (props: HeaderProps) => {
|
|||
onInteraction={setIsMenuOpen}
|
||||
placement="bottom-end"
|
||||
portalClassName={`${HEADER_MENU_PORTAL_CLASS}-${props.widgetId}`}
|
||||
portalContainer={document.getElementById("art-board") || undefined}
|
||||
portalContainer={
|
||||
document.getElementById(CANVAS_ART_BOARD) || undefined
|
||||
}
|
||||
>
|
||||
<ArrowDownIcon className="w-5 h-5" color="var(--wds-color-icon)" />
|
||||
</Popover2>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import type { WidgetProps } from "widgets/BaseWidget";
|
|||
import { selectWidgetInitAction } from "actions/widgetSelectionActions";
|
||||
import { SelectionRequestType } from "sagas/WidgetSelectUtils";
|
||||
import { importSvg } from "design-system-old";
|
||||
import { CANVAS_ART_BOARD } from "constants/componentClassNameConstants";
|
||||
|
||||
const DragHandleIcon = importSvg(
|
||||
() => import("assets/icons/ads/app-icons/draghandler.svg"),
|
||||
|
|
@ -106,7 +107,9 @@ class TableFilterPane extends Component<Props> {
|
|||
onPositionChange={this.handlePositionUpdate}
|
||||
parentElement={boundaryParent}
|
||||
placement="top"
|
||||
portalContainer={document.getElementById("art-board") || undefined}
|
||||
portalContainer={
|
||||
document.getElementById(CANVAS_ART_BOARD) || undefined
|
||||
}
|
||||
position={get(this.props, "metaProps.position") as PositionPropsInt}
|
||||
renderDragBlock={
|
||||
<DragBlock>
|
||||
|
|
|
|||
|
|
@ -8594,6 +8594,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-reconciler@npm:~0.26.2":
|
||||
version: 0.26.7
|
||||
resolution: "@types/react-reconciler@npm:0.26.7"
|
||||
dependencies:
|
||||
"@types/react": "*"
|
||||
checksum: 4122d2b08580f775d0aeae9bd10b68248f894096ed14c0ebbc143ef712e21b159e89d0c628bd95dd3329947fc1ee94a0cb1d2d32b32b1d5d225e70030e91e58f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-redux@npm:^7.0.1, @types/react-redux@npm:^7.1.16":
|
||||
version: 7.1.18
|
||||
resolution: "@types/react-redux@npm:7.1.18"
|
||||
|
|
@ -10369,6 +10378,7 @@ __metadata:
|
|||
jshint: ^2.13.4
|
||||
json5: ^2.2.3
|
||||
klona: ^2.0.5
|
||||
konva: 8.0.1
|
||||
libphonenumber-js: ^1.9.44
|
||||
linkedom: ^0.14.20
|
||||
lint-staged: ^13.2.0
|
||||
|
|
@ -10430,6 +10440,7 @@ __metadata:
|
|||
react-instantsearch-dom: ^6.4.0
|
||||
react-is: ^16.12.0
|
||||
react-json-view: ^1.21.3
|
||||
react-konva: 17.0.2-6
|
||||
react-masonry-css: ^1.0.16
|
||||
react-media-recorder: ^1.6.1
|
||||
react-modal: ^3.15.1
|
||||
|
|
@ -20800,6 +20811,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"konva@npm:8.0.1":
|
||||
version: 8.0.1
|
||||
resolution: "konva@npm:8.0.1"
|
||||
checksum: 989702028faed7981780d74bf4fa2f682627e726832745b4258af93bf0ef5bde766db0f5e5cd47a01839796698b01dbb4c9d6e8592c03321ba38a24e0b6dae3d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"labeled-stream-splicer@npm:^2.0.0":
|
||||
version: 2.0.2
|
||||
resolution: "labeled-stream-splicer@npm:2.0.2"
|
||||
|
|
@ -26225,6 +26243,21 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-konva@npm:17.0.2-6":
|
||||
version: 17.0.2-6
|
||||
resolution: "react-konva@npm:17.0.2-6"
|
||||
dependencies:
|
||||
"@types/react-reconciler": ~0.26.2
|
||||
react-reconciler: ~0.26.2
|
||||
scheduler: ^0.20.2
|
||||
peerDependencies:
|
||||
konva: ^8.0.1 || ^7.2.5
|
||||
react: ">=16.8.0"
|
||||
react-dom: ">=16.8.0"
|
||||
checksum: 5e868f6941090243c998f2817fbc9f031f60c83e236dc3f6328c904114582cfe281a08df9b5146b3339f6e1ccf30f3707d03de85fd534fb42fbe0ee6c6531b0c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-lifecycles-compat@npm:^3.0.0, react-lifecycles-compat@npm:^3.0.4":
|
||||
version: 3.0.4
|
||||
resolution: "react-lifecycles-compat@npm:3.0.4"
|
||||
|
|
@ -26370,6 +26403,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-reconciler@npm:~0.26.2":
|
||||
version: 0.26.2
|
||||
resolution: "react-reconciler@npm:0.26.2"
|
||||
dependencies:
|
||||
loose-envify: ^1.1.0
|
||||
object-assign: ^4.1.1
|
||||
scheduler: ^0.20.2
|
||||
peerDependencies:
|
||||
react: ^17.0.2
|
||||
checksum: 2ebceace56f547f51eaf142becefef9cca980eae4f42d90ee5a966f54a375f5082d78b71b00c40bbd9bca69e0e0f698c7d4e81cc7373437caa19831fddc1d01b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-redux@npm:^7.1.1, react-redux@npm:^7.2.4":
|
||||
version: 7.2.4
|
||||
resolution: "react-redux@npm:7.2.4"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user