PromucFlow_constructor/app/client/src/utils/autoLayout/AutoLayoutUtils.ts

351 lines
9.6 KiB
TypeScript
Raw Normal View History

chore: upgrade to prettier v2 + enforce import types (#21013)Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com> ## Description This PR upgrades Prettier to v2 + enforces TypeScript’s [`import type`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export) syntax where applicable. It’s submitted as a separate PR so we can merge it easily. As a part of this PR, we reformat the codebase heavily: - add `import type` everywhere where it’s required, and - re-format the code to account for Prettier 2’s breaking changes: https://prettier.io/blog/2020/03/21/2.0.0.html#breaking-changes This PR is submitted against `release` to make sure all new code by team members will adhere to new formatting standards, and we’ll have fewer conflicts when merging `bundle-optimizations` into `release`. (I’ll merge `release` back into `bundle-optimizations` once this PR is merged.) ### Why is this needed? This PR is needed because, for the Lodash optimization from https://github.com/appsmithorg/appsmith/commit/7cbb12af886621256224be0c93e6a465dd710ad3, we need to use `import type`. Otherwise, `babel-plugin-lodash` complains that `LoDashStatic` is not a lodash function. However, just using `import type` in the current codebase will give you this: <img width="962" alt="Screenshot 2023-03-08 at 17 45 59" src="https://user-images.githubusercontent.com/2953267/223775744-407afa0c-e8b9-44a1-90f9-b879348da57f.png"> That’s because Prettier 1 can’t parse `import type` at all. To parse it, we need to upgrade to Prettier 2. ### Why enforce `import type`? Apart from just enabling `import type` support, this PR enforces specifying `import type` everywhere it’s needed. (Developers will get immediate TypeScript and ESLint errors when they forget to do so.) I’m doing this because I believe `import type` improves DX and makes refactorings easier. Let’s say you had a few imports like below. Can you tell which of these imports will increase the bundle size? (Tip: it’s not all of them!) ```ts // app/client/src/workers/Linting/utils.ts import { Position } from "codemirror"; import { LintError as JSHintError, LintOptions } from "jshint"; import { get, isEmpty, isNumber, keys, last, set } from "lodash"; ``` It’s pretty hard, right? What about now? ```ts // app/client/src/workers/Linting/utils.ts import type { Position } from "codemirror"; import type { LintError as JSHintError, LintOptions } from "jshint"; import { get, isEmpty, isNumber, keys, last, set } from "lodash"; ``` Now, it’s clear that only `lodash` will be bundled. This helps developers to see which imports are problematic, but it _also_ helps with refactorings. Now, if you want to see where `codemirror` is bundled, you can just grep for `import \{.*\} from "codemirror"` – and you won’t get any type-only imports. This also helps (some) bundlers. Upon transpiling, TypeScript erases type-only imports completely. In some environment (not ours), this makes the bundle smaller, as the bundler doesn’t need to bundle type-only imports anymore. ## Type of change - Chore (housekeeping or task changes that don't impact user perception) ## How Has This Been Tested? This was tested to not break the build. ### 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 - [x] 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 - [x] 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: - [ ] Test plan has been approved by relevant developers - [ ] Test plan has been peer reviewed by QA - [ ] Cypress test cases have been added and approved by either SDET or manual QA - [ ] Organized project review call with relevant stakeholders after Round 1/2 of QA - [ ] Added Test Plan Approved label after reveiwing all Cypress test --------- Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
2023-03-16 11:41:47 +00:00
import type { FlexLayer, LayerChild } from "./autoLayoutTypes";
import {
FLEXBOX_PADDING,
GridDefaults,
MAIN_CONTAINER_WIDGET_ID,
} from "constants/WidgetConstants";
chore: upgrade to prettier v2 + enforce import types (#21013)Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com> ## Description This PR upgrades Prettier to v2 + enforces TypeScript’s [`import type`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export) syntax where applicable. It’s submitted as a separate PR so we can merge it easily. As a part of this PR, we reformat the codebase heavily: - add `import type` everywhere where it’s required, and - re-format the code to account for Prettier 2’s breaking changes: https://prettier.io/blog/2020/03/21/2.0.0.html#breaking-changes This PR is submitted against `release` to make sure all new code by team members will adhere to new formatting standards, and we’ll have fewer conflicts when merging `bundle-optimizations` into `release`. (I’ll merge `release` back into `bundle-optimizations` once this PR is merged.) ### Why is this needed? This PR is needed because, for the Lodash optimization from https://github.com/appsmithorg/appsmith/commit/7cbb12af886621256224be0c93e6a465dd710ad3, we need to use `import type`. Otherwise, `babel-plugin-lodash` complains that `LoDashStatic` is not a lodash function. However, just using `import type` in the current codebase will give you this: <img width="962" alt="Screenshot 2023-03-08 at 17 45 59" src="https://user-images.githubusercontent.com/2953267/223775744-407afa0c-e8b9-44a1-90f9-b879348da57f.png"> That’s because Prettier 1 can’t parse `import type` at all. To parse it, we need to upgrade to Prettier 2. ### Why enforce `import type`? Apart from just enabling `import type` support, this PR enforces specifying `import type` everywhere it’s needed. (Developers will get immediate TypeScript and ESLint errors when they forget to do so.) I’m doing this because I believe `import type` improves DX and makes refactorings easier. Let’s say you had a few imports like below. Can you tell which of these imports will increase the bundle size? (Tip: it’s not all of them!) ```ts // app/client/src/workers/Linting/utils.ts import { Position } from "codemirror"; import { LintError as JSHintError, LintOptions } from "jshint"; import { get, isEmpty, isNumber, keys, last, set } from "lodash"; ``` It’s pretty hard, right? What about now? ```ts // app/client/src/workers/Linting/utils.ts import type { Position } from "codemirror"; import type { LintError as JSHintError, LintOptions } from "jshint"; import { get, isEmpty, isNumber, keys, last, set } from "lodash"; ``` Now, it’s clear that only `lodash` will be bundled. This helps developers to see which imports are problematic, but it _also_ helps with refactorings. Now, if you want to see where `codemirror` is bundled, you can just grep for `import \{.*\} from "codemirror"` – and you won’t get any type-only imports. This also helps (some) bundlers. Upon transpiling, TypeScript erases type-only imports completely. In some environment (not ours), this makes the bundle smaller, as the bundler doesn’t need to bundle type-only imports anymore. ## Type of change - Chore (housekeeping or task changes that don't impact user perception) ## How Has This Been Tested? This was tested to not break the build. ### 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 - [x] 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 - [x] 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: - [ ] Test plan has been approved by relevant developers - [ ] Test plan has been peer reviewed by QA - [ ] Cypress test cases have been added and approved by either SDET or manual QA - [ ] Organized project review call with relevant stakeholders after Round 1/2 of QA - [ ] Added Test Plan Approved label after reveiwing all Cypress test --------- Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
2023-03-16 11:41:47 +00:00
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
import { AppPositioningTypes } from "reducers/entityReducers/pageListReducer";
import {
defaultAutoLayoutWidgets,
FlexLayerAlignment,
Positioning,
ResponsiveBehavior,
} from "utils/autoLayout/constants";
import { updateWidgetPositions } from "utils/autoLayout/positionUtils";
chore: upgrade to prettier v2 + enforce import types (#21013)Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com> ## Description This PR upgrades Prettier to v2 + enforces TypeScript’s [`import type`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export) syntax where applicable. It’s submitted as a separate PR so we can merge it easily. As a part of this PR, we reformat the codebase heavily: - add `import type` everywhere where it’s required, and - re-format the code to account for Prettier 2’s breaking changes: https://prettier.io/blog/2020/03/21/2.0.0.html#breaking-changes This PR is submitted against `release` to make sure all new code by team members will adhere to new formatting standards, and we’ll have fewer conflicts when merging `bundle-optimizations` into `release`. (I’ll merge `release` back into `bundle-optimizations` once this PR is merged.) ### Why is this needed? This PR is needed because, for the Lodash optimization from https://github.com/appsmithorg/appsmith/commit/7cbb12af886621256224be0c93e6a465dd710ad3, we need to use `import type`. Otherwise, `babel-plugin-lodash` complains that `LoDashStatic` is not a lodash function. However, just using `import type` in the current codebase will give you this: <img width="962" alt="Screenshot 2023-03-08 at 17 45 59" src="https://user-images.githubusercontent.com/2953267/223775744-407afa0c-e8b9-44a1-90f9-b879348da57f.png"> That’s because Prettier 1 can’t parse `import type` at all. To parse it, we need to upgrade to Prettier 2. ### Why enforce `import type`? Apart from just enabling `import type` support, this PR enforces specifying `import type` everywhere it’s needed. (Developers will get immediate TypeScript and ESLint errors when they forget to do so.) I’m doing this because I believe `import type` improves DX and makes refactorings easier. Let’s say you had a few imports like below. Can you tell which of these imports will increase the bundle size? (Tip: it’s not all of them!) ```ts // app/client/src/workers/Linting/utils.ts import { Position } from "codemirror"; import { LintError as JSHintError, LintOptions } from "jshint"; import { get, isEmpty, isNumber, keys, last, set } from "lodash"; ``` It’s pretty hard, right? What about now? ```ts // app/client/src/workers/Linting/utils.ts import type { Position } from "codemirror"; import type { LintError as JSHintError, LintOptions } from "jshint"; import { get, isEmpty, isNumber, keys, last, set } from "lodash"; ``` Now, it’s clear that only `lodash` will be bundled. This helps developers to see which imports are problematic, but it _also_ helps with refactorings. Now, if you want to see where `codemirror` is bundled, you can just grep for `import \{.*\} from "codemirror"` – and you won’t get any type-only imports. This also helps (some) bundlers. Upon transpiling, TypeScript erases type-only imports completely. In some environment (not ours), this makes the bundle smaller, as the bundler doesn’t need to bundle type-only imports anymore. ## Type of change - Chore (housekeeping or task changes that don't impact user perception) ## How Has This Been Tested? This was tested to not break the build. ### 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 - [x] 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 - [x] 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: - [ ] Test plan has been approved by relevant developers - [ ] Test plan has been peer reviewed by QA - [ ] Cypress test cases have been added and approved by either SDET or manual QA - [ ] Organized project review call with relevant stakeholders after Round 1/2 of QA - [ ] Added Test Plan Approved label after reveiwing all Cypress test --------- Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
2023-03-16 11:41:47 +00:00
import type { AlignmentColumnInfo } from "./autoLayoutTypes";
import { getWidgetWidth } from "./flexWidgetUtils";
export function updateFlexLayersOnDelete(
allWidgets: CanvasWidgetsReduxState,
widgetId: string,
parentId: string,
isMobile?: boolean,
): CanvasWidgetsReduxState {
const widgets = { ...allWidgets };
if (
widgets[MAIN_CONTAINER_WIDGET_ID].appPositioningType ===
AppPositioningTypes.FIXED ||
widgets[MAIN_CONTAINER_WIDGET_ID].positioning === Positioning.Fixed
)
return widgets;
let parent = widgets[parentId];
if (!parent) return widgets;
let flexLayers = [...(parent.flexLayers || [])];
if (!flexLayers.length) return widgets;
let layerIndex = -1; // Find the layer in which the deleted widget exists.
let index = 0;
let updatedChildren: LayerChild[] = [];
for (const layer of flexLayers) {
const children = layer.children || [];
if (!children.length) continue;
const childIndex = children.findIndex(
(each: LayerChild) => each.id === widgetId,
);
if (childIndex === -1) {
index += 1;
continue;
}
updatedChildren = children.filter(
(each: LayerChild) => each.id !== widgetId,
);
layerIndex = index;
break;
}
if (layerIndex === -1) return widgets;
flexLayers = [
...flexLayers.slice(0, layerIndex),
{
children: updatedChildren,
},
...flexLayers.slice(layerIndex + 1),
];
parent = {
...parent,
flexLayers: flexLayers.filter(
(layer: FlexLayer) => layer?.children?.length,
),
};
widgets[parentId] = parent;
return updateWidgetPositions(widgets, parentId, isMobile);
}
export function alterLayoutForMobile(
allWidgets: CanvasWidgetsReduxState,
parentId: string,
canvasWidth: number,
): CanvasWidgetsReduxState {
let widgets = { ...allWidgets };
const parent = widgets[parentId];
const children = parent.children;
if (!isStack(allWidgets, parent)) {
return widgets;
}
if (!children || !children.length) return widgets;
for (const child of children) {
const widget = { ...widgets[child] };
if (widget.responsiveBehavior === ResponsiveBehavior.Fill) {
widget.mobileRightColumn = GridDefaults.DEFAULT_GRID_COLUMNS;
widget.mobileLeftColumn = 0;
} else if (
widget.responsiveBehavior === ResponsiveBehavior.Hug &&
widget.minWidth
) {
const { minWidth, rightColumn } = widget;
const columnSpace =
(canvasWidth - FLEXBOX_PADDING * 2) / GridDefaults.DEFAULT_GRID_COLUMNS;
if (columnSpace * rightColumn < minWidth) {
widget.mobileLeftColumn = 0;
widget.mobileRightColumn = Math.min(
minWidth / columnSpace,
GridDefaults.DEFAULT_GRID_COLUMNS,
);
}
}
widget.mobileTopRow = widget.topRow;
widget.mobileBottomRow = widget.bottomRow;
widgets = alterLayoutForMobile(
widgets,
child,
(canvasWidth * (widget.mobileRightColumn || 1)) /
GridDefaults.DEFAULT_GRID_COLUMNS,
);
widgets[child] = widget;
widgets = updateWidgetPositions(widgets, child, true);
}
widgets = updateWidgetPositions(widgets, parentId, true);
return widgets;
}
export function alterLayoutForDesktop(
allWidgets: CanvasWidgetsReduxState,
parentId: string,
): CanvasWidgetsReduxState {
let widgets = { ...allWidgets };
const parent = widgets[parentId];
const children = parent.children;
if (!isStack(allWidgets, parent)) return widgets;
if (!children || !children.length) return widgets;
widgets = updateWidgetPositions(widgets, parentId, false);
for (const child of children) {
widgets = alterLayoutForDesktop(widgets, child);
}
return widgets;
}
/**
* START: COPY PASTE UTILS
*/
export function pasteWidgetInFlexLayers(
allWidgets: CanvasWidgetsReduxState,
parentId: string,
widget: any,
originalWidgetId: string,
isMobile: boolean,
): CanvasWidgetsReduxState {
let widgets = { ...allWidgets };
const parent = widgets[parentId];
let flexLayers: FlexLayer[] = parent.flexLayers || [];
/**
* If the new parent is not the same as the original parent,
* then add a new flex layer.
*/
if (
!widgets[originalWidgetId] ||
widgets[originalWidgetId].parentId !== parentId
) {
flexLayers = [
...flexLayers,
{
children: [
{
id: widget.widgetId,
align: FlexLayerAlignment.Start,
},
],
},
];
} else {
/**
* If the new parent is the same as the original parent,
* then update the flex layer.
*/
let rowIndex = -1,
alignment = FlexLayerAlignment.Start;
const flexLayerIndex = flexLayers.findIndex((layer: FlexLayer) => {
const temp = layer.children.findIndex(
(child: LayerChild) => child.id === originalWidgetId,
);
if (temp > -1) {
rowIndex = temp;
alignment = layer.children[temp].align;
}
return temp > -1;
});
if (flexLayerIndex > -1 && rowIndex > -1) {
let selectedLayer = flexLayers[flexLayerIndex];
selectedLayer = {
children: [
...selectedLayer.children.slice(0, rowIndex + 1),
{ id: widget.widgetId, align: alignment },
...selectedLayer.children.slice(rowIndex + 1),
],
};
flexLayers = [
...flexLayers.slice(0, flexLayerIndex),
selectedLayer,
...flexLayers.slice(flexLayerIndex + 1),
];
}
}
widgets = {
...widgets,
[parentId]: {
...parent,
flexLayers,
},
};
return updateWidgetPositions(widgets, parentId, isMobile);
}
/**
* Add nested children to flex layers of the new pasted canvas.
* The flexLayers get copied from the original canvas.
* This method matches the copied widgetId with the original widgetId
* and replaces them in position.
*/
export function addChildToPastedFlexLayers(
allWidgets: CanvasWidgetsReduxState,
widget: any,
widgetIdMap: Record<string, string>,
isMobile: boolean,
): CanvasWidgetsReduxState {
let widgets = { ...allWidgets };
const parent = widgets[widget.parentId];
const flexLayers = parent.flexLayers || [];
if (flexLayers.length > 0) {
let index = 0;
for (const layer of flexLayers) {
let children = layer.children;
let childIndex = 0;
for (const child of children) {
if (widgetIdMap[child.id] === widget.widgetId) {
children = [
...children.slice(0, childIndex),
{ id: widget.widgetId, align: child.align },
...children.slice(childIndex + 1),
];
}
childIndex += 1;
}
flexLayers[index] = {
children,
};
index += 1;
}
}
widgets = {
...widgets,
[parent.widgetId]: {
...parent,
flexLayers,
},
};
return updateWidgetPositions(widgets, parent.widgetId, isMobile);
}
export function isStack(
allWidgets: CanvasWidgetsReduxState,
widget: any,
): boolean {
let parent = widget.parentId ? allWidgets[widget.parentId] : undefined;
if (parent && parent.type === "CANVAS_WIDGET" && parent.parentId)
parent = allWidgets[parent.parentId];
return (
widget.positioning === Positioning.Vertical ||
((parent && defaultAutoLayoutWidgets.includes(parent.type)) ||
parent?.widgetId === MAIN_CONTAINER_WIDGET_ID
? allWidgets[MAIN_CONTAINER_WIDGET_ID].positioning ===
Positioning.Vertical
: false)
);
}
/**
* END: copy paste utils
*/
export function getLayerIndexOfWidget(
flexLayers: FlexLayer[],
widgetId: string,
): number {
if (!flexLayers) return -1;
return flexLayers.findIndex((layer: FlexLayer) => {
return (
layer.children.findIndex((child: LayerChild) => child.id === widgetId) !==
-1
);
});
}
export function getFillWidgetLengthForLayer(
layer: any,
allWidgets: any,
): number | undefined {
let fillLength = GridDefaults.DEFAULT_GRID_COLUMNS;
let hugLength = 0,
fillCount = 0;
for (const child of layer.children) {
const childWidget = allWidgets[child.id];
if (!childWidget) {
continue;
}
if (childWidget.responsiveBehavior !== ResponsiveBehavior.Fill) {
hugLength += childWidget.rightColumn - childWidget.leftColumn;
} else {
fillCount += 1;
}
}
fillLength = (fillLength - hugLength) / (fillCount || 1);
return fillLength;
}
export function getAlignmentColumnInfo(
widgets: CanvasWidgetsReduxState,
layer: FlexLayer,
isMobile: boolean,
): AlignmentColumnInfo {
if (!layer)
return {
[FlexLayerAlignment.Start]: 0,
[FlexLayerAlignment.Center]: 0,
[FlexLayerAlignment.End]: 0,
};
let start = 0,
end = 0,
center = 0;
for (const child of layer.children) {
const widget = widgets[child.id];
if (!widget) continue;
if (child.align === FlexLayerAlignment.End)
end += getWidgetWidth(widget, isMobile);
else if (child.align === FlexLayerAlignment.Center)
center += getWidgetWidth(widget, isMobile);
else start += getWidgetWidth(widget, isMobile);
}
return {
[FlexLayerAlignment.Start]: start,
[FlexLayerAlignment.Center]: center,
[FlexLayerAlignment.End]: end,
};
}