2023-04-20 05:14:37 +00:00
|
|
|
import {
|
|
|
|
|
FlexLayerAlignment,
|
|
|
|
|
MOBILE_ROW_GAP,
|
|
|
|
|
ROW_GAP,
|
2023-10-02 19:41:05 +00:00
|
|
|
} from "layoutSystems/common/utils/constants";
|
2023-03-04 07:25:54 +00:00
|
|
|
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 {
|
2023-03-04 07:25:54 +00:00
|
|
|
CanvasWidgetsReduxState,
|
|
|
|
|
FlattenedWidgetProps,
|
|
|
|
|
} from "reducers/entityReducers/canvasWidgetsReducer";
|
|
|
|
|
import {
|
|
|
|
|
getLeftColumn,
|
|
|
|
|
getRightColumn,
|
|
|
|
|
getTopRow,
|
|
|
|
|
getWidgetHeight,
|
|
|
|
|
getWidgetWidth,
|
|
|
|
|
} from "./flexWidgetUtils";
|
2023-07-10 04:32:42 +00:00
|
|
|
import { getAlignmentSizeInfo, getWrappedAlignmentInfo } from "./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 {
|
2023-04-07 13:51:35 +00:00
|
|
|
AlignmentChildren,
|
|
|
|
|
AlignmentInfo,
|
2023-10-02 19:41:05 +00:00
|
|
|
} from "../../autolayout/utils/types";
|
2023-07-10 04:32:42 +00:00
|
|
|
import { getTotalRowsOfAllChildren } from "./heightUpdateUtils";
|
2023-09-11 15:55:11 +00:00
|
|
|
import { DEFAULT_HIGHLIGHT_SIZE } from "../common/flexCanvas/FlexBoxComponent";
|
2023-10-02 19:41:05 +00:00
|
|
|
import type { FlexLayer, LayerChild } from "./types";
|
|
|
|
|
import type { DropZone, HighlightInfo } from "layoutSystems/common/utils/types";
|
2023-03-04 07:25:54 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param allWidgets : CanvasWidgetsReduxState
|
|
|
|
|
* @param canvasId : string
|
|
|
|
|
* @param draggedWidgets : string[]
|
|
|
|
|
* @returns widgets: CanvasWidgetsReduxState
|
|
|
|
|
*
|
|
|
|
|
* This function is used to derive highlights from flex layers and stored in widget dsl.
|
|
|
|
|
*/
|
|
|
|
|
export function deriveHighlightsFromLayers(
|
|
|
|
|
allWidgets: CanvasWidgetsReduxState,
|
|
|
|
|
canvasId: string,
|
|
|
|
|
snapColumnSpace: number,
|
|
|
|
|
draggedWidgets: string[] = [],
|
|
|
|
|
hasFillWidget = false,
|
|
|
|
|
isMobile = false,
|
|
|
|
|
): HighlightInfo[] {
|
|
|
|
|
const widgets = { ...allWidgets };
|
|
|
|
|
const canvas = widgets[canvasId];
|
|
|
|
|
if (!canvas) return [];
|
|
|
|
|
|
|
|
|
|
const layers: FlexLayer[] = canvas.flexLayers || [];
|
|
|
|
|
let highlights: HighlightInfo[] = [];
|
|
|
|
|
let childCount = 0;
|
|
|
|
|
let layerIndex = 0;
|
|
|
|
|
|
2023-04-20 05:14:37 +00:00
|
|
|
const rowGap = isMobile ? MOBILE_ROW_GAP : ROW_GAP;
|
|
|
|
|
|
2023-03-04 07:25:54 +00:00
|
|
|
let offsetTop = FLEXBOX_PADDING; // used to calculate distance of a highlight from parents's top.
|
|
|
|
|
for (const layer of layers) {
|
2023-04-07 13:51:35 +00:00
|
|
|
/**
|
|
|
|
|
* Discard widgets that are detached from layout (Modals).
|
|
|
|
|
*/
|
|
|
|
|
const updatedLayer: FlexLayer = {
|
|
|
|
|
children: layer?.children?.filter(
|
|
|
|
|
(child: LayerChild) =>
|
|
|
|
|
widgets[child.id] && !widgets[child.id].detachFromLayout,
|
|
|
|
|
),
|
|
|
|
|
};
|
2023-03-04 07:25:54 +00:00
|
|
|
/**
|
|
|
|
|
* If the layer is empty, after discounting the dragged widgets,
|
|
|
|
|
* then don't process it for vertical highlights.
|
|
|
|
|
*/
|
|
|
|
|
const isEmpty: boolean =
|
2023-04-07 13:51:35 +00:00
|
|
|
updatedLayer?.children?.filter(
|
2023-03-04 07:25:54 +00:00
|
|
|
(child: LayerChild) => draggedWidgets.indexOf(child.id) === -1,
|
|
|
|
|
).length === 0;
|
|
|
|
|
const childrenRows = getTotalRowsOfAllChildren(
|
|
|
|
|
widgets,
|
2023-04-07 13:51:35 +00:00
|
|
|
updatedLayer.children?.map((child) => child.id) || [],
|
2023-03-04 07:25:54 +00:00
|
|
|
isMobile,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const payload: VerticalHighlightsPayload = generateVerticalHighlights({
|
|
|
|
|
widgets,
|
2023-04-07 13:51:35 +00:00
|
|
|
layer: updatedLayer,
|
2023-03-04 07:25:54 +00:00
|
|
|
childCount,
|
|
|
|
|
layerIndex,
|
|
|
|
|
offsetTop,
|
|
|
|
|
canvasId,
|
|
|
|
|
columnSpace: snapColumnSpace,
|
|
|
|
|
draggedWidgets,
|
|
|
|
|
isMobile,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!isEmpty) {
|
|
|
|
|
/**
|
|
|
|
|
* Add a layer of horizontal highlights before each flex layer
|
|
|
|
|
* to account for new vertical drop positions.
|
|
|
|
|
*/
|
|
|
|
|
highlights.push(
|
|
|
|
|
...generateHorizontalHighlights(
|
|
|
|
|
childCount,
|
|
|
|
|
layerIndex,
|
|
|
|
|
offsetTop,
|
|
|
|
|
snapColumnSpace * GridDefaults.DEFAULT_GRID_COLUMNS,
|
|
|
|
|
canvasId,
|
|
|
|
|
hasFillWidget,
|
|
|
|
|
childrenRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
|
|
|
|
getPreviousOffsetTop(highlights),
|
2023-04-20 05:14:37 +00:00
|
|
|
isMobile,
|
2023-03-04 07:25:54 +00:00
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
highlights.push(...payload.highlights);
|
|
|
|
|
layerIndex += 1;
|
|
|
|
|
} else highlights = updateHorizontalDropZone(highlights);
|
2023-04-20 05:14:37 +00:00
|
|
|
offsetTop +=
|
|
|
|
|
childrenRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT + rowGap || 0;
|
2023-03-04 07:25:54 +00:00
|
|
|
childCount += payload.childCount;
|
|
|
|
|
}
|
|
|
|
|
// Add a layer of horizontal highlights for the empty space at the bottom of a stack.
|
|
|
|
|
highlights.push(
|
|
|
|
|
...generateHorizontalHighlights(
|
|
|
|
|
childCount,
|
|
|
|
|
layerIndex,
|
|
|
|
|
offsetTop,
|
|
|
|
|
snapColumnSpace * GridDefaults.DEFAULT_GRID_COLUMNS,
|
|
|
|
|
canvasId,
|
|
|
|
|
hasFillWidget,
|
|
|
|
|
-1,
|
|
|
|
|
getPreviousOffsetTop(highlights),
|
2023-04-20 05:14:37 +00:00
|
|
|
isMobile,
|
2023-03-04 07:25:54 +00:00
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
return highlights;
|
|
|
|
|
}
|
|
|
|
|
export interface VerticalHighlightsPayload {
|
|
|
|
|
childCount: number;
|
|
|
|
|
highlights: HighlightInfo[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Derive highlight information for all widgets within a layer.
|
|
|
|
|
* - Breakdown each layer into component alignments.
|
|
|
|
|
* - generate highlight information for each alignment.
|
|
|
|
|
*/
|
|
|
|
|
export function generateVerticalHighlights(data: {
|
|
|
|
|
widgets: CanvasWidgetsReduxState;
|
|
|
|
|
layer: FlexLayer;
|
|
|
|
|
childCount: number;
|
|
|
|
|
layerIndex: number;
|
|
|
|
|
offsetTop: number;
|
|
|
|
|
canvasId: string;
|
|
|
|
|
columnSpace: number;
|
|
|
|
|
draggedWidgets: string[];
|
|
|
|
|
isMobile: boolean;
|
|
|
|
|
}): VerticalHighlightsPayload {
|
|
|
|
|
const {
|
|
|
|
|
canvasId,
|
|
|
|
|
childCount,
|
|
|
|
|
columnSpace,
|
|
|
|
|
draggedWidgets,
|
|
|
|
|
isMobile,
|
|
|
|
|
layer,
|
|
|
|
|
layerIndex,
|
|
|
|
|
offsetTop,
|
|
|
|
|
widgets,
|
|
|
|
|
} = data;
|
|
|
|
|
const { children } = layer;
|
|
|
|
|
|
|
|
|
|
let count = 0;
|
|
|
|
|
const startChildren = [],
|
|
|
|
|
centerChildren = [],
|
|
|
|
|
endChildren = [];
|
|
|
|
|
let startColumns = 0,
|
|
|
|
|
centerColumns = 0,
|
|
|
|
|
endColumns = 0;
|
|
|
|
|
let maxHeight = 0;
|
2023-04-07 13:51:35 +00:00
|
|
|
const rowSpace = GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
2023-03-04 07:25:54 +00:00
|
|
|
for (const child of children) {
|
|
|
|
|
const widget = widgets[child.id];
|
|
|
|
|
if (!widget) continue;
|
|
|
|
|
count += 1;
|
|
|
|
|
if (draggedWidgets.indexOf(child.id) > -1) continue;
|
2023-04-07 13:51:35 +00:00
|
|
|
const columns: number = getWidgetWidth(widget, isMobile);
|
|
|
|
|
const rows: number = getWidgetHeight(widget, isMobile);
|
|
|
|
|
maxHeight = Math.max(maxHeight, rows * rowSpace);
|
2023-03-04 07:25:54 +00:00
|
|
|
if (child.align === FlexLayerAlignment.End) {
|
2023-04-07 13:51:35 +00:00
|
|
|
endChildren.push({ widget, columns, rows });
|
|
|
|
|
endColumns += columns;
|
2023-03-04 07:25:54 +00:00
|
|
|
} else if (child.align === FlexLayerAlignment.Center) {
|
2023-04-07 13:51:35 +00:00
|
|
|
centerChildren.push({ widget, columns, rows });
|
|
|
|
|
centerColumns += columns;
|
2023-03-04 07:25:54 +00:00
|
|
|
} else {
|
2023-04-07 13:51:35 +00:00
|
|
|
startChildren.push({ widget, columns, rows });
|
|
|
|
|
startColumns += columns;
|
2023-03-04 07:25:54 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const alignmentInfo: AlignmentInfo[] = [
|
|
|
|
|
{
|
|
|
|
|
alignment: FlexLayerAlignment.Start,
|
|
|
|
|
children: startChildren,
|
|
|
|
|
columns: startColumns,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
alignment: FlexLayerAlignment.Center,
|
|
|
|
|
children: centerChildren,
|
|
|
|
|
columns: centerColumns,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
alignment: FlexLayerAlignment.End,
|
|
|
|
|
children: endChildren,
|
|
|
|
|
columns: endColumns,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const wrappingInfo: AlignmentInfo[][] = isMobile
|
|
|
|
|
? getWrappedAlignmentInfo(alignmentInfo)
|
|
|
|
|
: [alignmentInfo];
|
|
|
|
|
|
|
|
|
|
let highlights: HighlightInfo[] = [];
|
|
|
|
|
for (const each of wrappingInfo) {
|
|
|
|
|
if (!each.length) continue;
|
|
|
|
|
/**
|
|
|
|
|
* On mobile viewport,
|
|
|
|
|
* if the row is wrapped, i.e. it contains less than all three alignments
|
|
|
|
|
* and if total columns required by these alignments are zero;
|
|
|
|
|
* then don't add a highlight for them as they will be squashed.
|
|
|
|
|
*/
|
|
|
|
|
if (
|
|
|
|
|
isMobile &&
|
|
|
|
|
each.length < 3 &&
|
|
|
|
|
each.reduce((a, b) => a + b.columns, 0) === 0
|
|
|
|
|
)
|
|
|
|
|
continue;
|
|
|
|
|
let index = 0;
|
|
|
|
|
for (const item of each) {
|
|
|
|
|
let avoidInitialHighlight = false;
|
|
|
|
|
let startPosition: number | undefined;
|
|
|
|
|
if (item.alignment === FlexLayerAlignment.Center) {
|
|
|
|
|
const { centerSize } = getAlignmentSizeInfo(each);
|
|
|
|
|
avoidInitialHighlight =
|
|
|
|
|
((startColumns > 25 || endColumns > 25) && centerColumns === 0) ||
|
|
|
|
|
centerSize === 0;
|
|
|
|
|
if (each.length === 2)
|
|
|
|
|
startPosition =
|
|
|
|
|
index === 0
|
|
|
|
|
? centerSize / 2
|
|
|
|
|
: GridDefaults.DEFAULT_GRID_COLUMNS - centerSize / 2;
|
|
|
|
|
}
|
|
|
|
|
highlights.push(
|
|
|
|
|
...generateHighlightsForAlignment({
|
2023-04-07 13:51:35 +00:00
|
|
|
arr: item.children.map((each: AlignmentChildren) => each.widget),
|
2023-03-04 07:25:54 +00:00
|
|
|
childCount:
|
|
|
|
|
item.alignment === FlexLayerAlignment.Start
|
|
|
|
|
? childCount
|
|
|
|
|
: item.alignment === FlexLayerAlignment.Center
|
|
|
|
|
? childCount + startChildren.length
|
|
|
|
|
: childCount + startChildren.length + centerChildren.length,
|
|
|
|
|
layerIndex,
|
|
|
|
|
alignment: item.alignment,
|
|
|
|
|
maxHeight,
|
|
|
|
|
offsetTop,
|
|
|
|
|
canvasId,
|
|
|
|
|
parentColumnSpace: columnSpace,
|
|
|
|
|
isMobile,
|
|
|
|
|
avoidInitialHighlight,
|
|
|
|
|
startPosition,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
index += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
highlights = updateVerticalHighlightDropZone(highlights, columnSpace * 64);
|
|
|
|
|
return { highlights, childCount: count };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generate highlight information for a single alignment within a layer.
|
|
|
|
|
* - For each widget in the alignment
|
|
|
|
|
* - generate a highlight to mark the the start position of the widget.
|
|
|
|
|
* - Add another highlight at the end position of the last widget in the alignment.
|
|
|
|
|
* - If the alignment has no children, then add an initial highlight to mark the start of the alignment.
|
|
|
|
|
*/
|
|
|
|
|
export function generateHighlightsForAlignment(data: {
|
|
|
|
|
arr: FlattenedWidgetProps[];
|
|
|
|
|
childCount: number;
|
|
|
|
|
layerIndex: number;
|
|
|
|
|
alignment: FlexLayerAlignment;
|
|
|
|
|
maxHeight: number;
|
|
|
|
|
offsetTop: number;
|
|
|
|
|
canvasId: string;
|
|
|
|
|
parentColumnSpace: number;
|
|
|
|
|
avoidInitialHighlight?: boolean;
|
|
|
|
|
isMobile: boolean;
|
|
|
|
|
startPosition: number | undefined;
|
|
|
|
|
}): HighlightInfo[] {
|
|
|
|
|
const {
|
|
|
|
|
alignment,
|
|
|
|
|
arr,
|
|
|
|
|
avoidInitialHighlight,
|
|
|
|
|
canvasId,
|
|
|
|
|
childCount,
|
|
|
|
|
isMobile,
|
|
|
|
|
layerIndex,
|
|
|
|
|
maxHeight,
|
|
|
|
|
offsetTop,
|
|
|
|
|
parentColumnSpace,
|
|
|
|
|
startPosition,
|
|
|
|
|
} = data;
|
|
|
|
|
const res: HighlightInfo[] = [];
|
|
|
|
|
let count = 0;
|
2023-04-07 13:51:35 +00:00
|
|
|
const rowSpace: number = GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
2023-03-04 07:25:54 +00:00
|
|
|
for (const child of arr) {
|
|
|
|
|
const left = getLeftColumn(child, isMobile);
|
|
|
|
|
res.push({
|
|
|
|
|
isNewLayer: false,
|
|
|
|
|
index: count + childCount,
|
|
|
|
|
layerIndex,
|
|
|
|
|
rowIndex: count,
|
|
|
|
|
alignment,
|
|
|
|
|
posX: left * parentColumnSpace + FLEXBOX_PADDING / 2,
|
2023-04-07 13:51:35 +00:00
|
|
|
posY: getTopRow(child, isMobile) * rowSpace + FLEXBOX_PADDING,
|
2023-03-04 07:25:54 +00:00
|
|
|
width: DEFAULT_HIGHLIGHT_SIZE,
|
|
|
|
|
height: isMobile
|
2023-04-07 13:51:35 +00:00
|
|
|
? getWidgetHeight(child, isMobile) * rowSpace
|
2023-03-04 07:25:54 +00:00
|
|
|
: maxHeight,
|
|
|
|
|
isVertical: true,
|
|
|
|
|
canvasId,
|
|
|
|
|
dropZone: {},
|
|
|
|
|
});
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!avoidInitialHighlight) {
|
|
|
|
|
const lastChild: FlattenedWidgetProps | null =
|
|
|
|
|
arr && arr.length ? arr[arr.length - 1] : null;
|
|
|
|
|
res.push({
|
|
|
|
|
isNewLayer: false,
|
|
|
|
|
index: count + childCount,
|
|
|
|
|
layerIndex,
|
|
|
|
|
rowIndex: count,
|
|
|
|
|
alignment,
|
|
|
|
|
posX: getPositionForInitialHighlight(
|
|
|
|
|
res,
|
|
|
|
|
alignment,
|
2023-04-07 13:51:35 +00:00
|
|
|
lastChild !== null
|
2023-03-04 07:25:54 +00:00
|
|
|
? getRightColumn(lastChild, isMobile) * parentColumnSpace +
|
|
|
|
|
FLEXBOX_PADDING / 2
|
|
|
|
|
: 0,
|
|
|
|
|
canvasId,
|
|
|
|
|
parentColumnSpace,
|
|
|
|
|
startPosition,
|
|
|
|
|
),
|
|
|
|
|
posY:
|
|
|
|
|
lastChild === null
|
|
|
|
|
? offsetTop
|
2023-04-07 13:51:35 +00:00
|
|
|
: getTopRow(lastChild, isMobile) * rowSpace + FLEXBOX_PADDING,
|
2023-03-04 07:25:54 +00:00
|
|
|
width: DEFAULT_HIGHLIGHT_SIZE,
|
|
|
|
|
height:
|
|
|
|
|
isMobile && lastChild !== null
|
2023-04-07 13:51:35 +00:00
|
|
|
? getWidgetHeight(lastChild, isMobile) * rowSpace
|
2023-03-04 07:25:54 +00:00
|
|
|
: maxHeight,
|
|
|
|
|
isVertical: true,
|
|
|
|
|
canvasId,
|
|
|
|
|
dropZone: {},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the position of the initial / final highlight for an alignment.
|
|
|
|
|
* @param highlights | HighlightInfo[] : highlights for the current alignment
|
|
|
|
|
* @param alignment | FlexLayerAlignment : alignment of the current highlights
|
|
|
|
|
* @param posX | number : end position of the last widget in the current alignment. (rightColumn * columnSpace)
|
|
|
|
|
* @param containerWidth | number : width of the container
|
|
|
|
|
* @param canvasId | string : id of the canvas
|
|
|
|
|
* @returns number
|
|
|
|
|
*/
|
|
|
|
|
function getPositionForInitialHighlight(
|
|
|
|
|
highlights: HighlightInfo[],
|
|
|
|
|
alignment: FlexLayerAlignment,
|
|
|
|
|
posX: number,
|
|
|
|
|
canvasId: string,
|
|
|
|
|
columnSpace: number,
|
|
|
|
|
startPosition: number | undefined,
|
|
|
|
|
): number {
|
2023-04-26 17:39:11 +00:00
|
|
|
const containerWidth = GridDefaults.DEFAULT_GRID_COLUMNS * columnSpace;
|
|
|
|
|
const endPosition = containerWidth + FLEXBOX_PADDING / 2;
|
2023-03-04 07:25:54 +00:00
|
|
|
if (alignment === FlexLayerAlignment.End) {
|
|
|
|
|
return endPosition;
|
|
|
|
|
} else if (alignment === FlexLayerAlignment.Center) {
|
|
|
|
|
if (!highlights.length)
|
|
|
|
|
return startPosition !== undefined ? startPosition : containerWidth / 2;
|
|
|
|
|
return Math.min(posX, endPosition);
|
|
|
|
|
} else {
|
|
|
|
|
if (!highlights.length) return 2;
|
|
|
|
|
return Math.min(posX, endPosition);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a layer of horizontal alignments to denote new vertical drop zones.
|
|
|
|
|
* - if the layer has a fill widget,
|
|
|
|
|
* - Start alignment spans the entire container width.
|
|
|
|
|
* - else each layer takes up a third of the container width and are placed side to side.
|
|
|
|
|
* @param childIndex | number : child count of children placed in preceding layers.
|
|
|
|
|
* @param layerIndex | number
|
|
|
|
|
* @param offsetTop | number
|
|
|
|
|
* @param containerWidth | number
|
|
|
|
|
* @param canvasId |
|
|
|
|
|
* @param hasFillWidget | boolean : whether the layer has a fill widget or not.
|
|
|
|
|
* @returns HighlightInfo[]
|
|
|
|
|
*/
|
|
|
|
|
function generateHorizontalHighlights(
|
|
|
|
|
childIndex: number,
|
|
|
|
|
layerIndex: number,
|
|
|
|
|
offsetTop: number,
|
|
|
|
|
containerWidth: number,
|
|
|
|
|
canvasId: string,
|
|
|
|
|
hasFillWidget: boolean,
|
|
|
|
|
rowHeight: number,
|
|
|
|
|
previousOffset: number,
|
2023-04-20 05:14:37 +00:00
|
|
|
isMobile: boolean,
|
2023-03-04 07:25:54 +00:00
|
|
|
): HighlightInfo[] {
|
|
|
|
|
const width = containerWidth / 3;
|
|
|
|
|
const arr: HighlightInfo[] = [];
|
|
|
|
|
const dropZone: DropZone = {
|
|
|
|
|
top: previousOffset === -1 ? offsetTop : (offsetTop - previousOffset) * 0.5,
|
|
|
|
|
bottom: rowHeight === -1 ? 10000 : rowHeight * 0.5,
|
|
|
|
|
};
|
2023-04-20 05:14:37 +00:00
|
|
|
const rowGap = isMobile ? MOBILE_ROW_GAP : ROW_GAP;
|
2023-04-26 17:39:11 +00:00
|
|
|
offsetTop = previousOffset === -1 ? offsetTop : offsetTop - rowGap;
|
2023-03-04 07:25:54 +00:00
|
|
|
[
|
|
|
|
|
FlexLayerAlignment.Start,
|
|
|
|
|
FlexLayerAlignment.Center,
|
|
|
|
|
FlexLayerAlignment.End,
|
|
|
|
|
].forEach((alignment, index) => {
|
|
|
|
|
arr.push({
|
|
|
|
|
isNewLayer: true,
|
|
|
|
|
index: childIndex,
|
|
|
|
|
layerIndex,
|
|
|
|
|
rowIndex: 0,
|
|
|
|
|
alignment,
|
|
|
|
|
posX: hasFillWidget
|
|
|
|
|
? alignment === FlexLayerAlignment.Start
|
|
|
|
|
? canvasId === MAIN_CONTAINER_WIDGET_ID
|
|
|
|
|
? FLEXBOX_PADDING
|
|
|
|
|
: FLEXBOX_PADDING * 1.5
|
|
|
|
|
: containerWidth
|
|
|
|
|
: width * index + FLEXBOX_PADDING,
|
|
|
|
|
posY: offsetTop,
|
|
|
|
|
width: hasFillWidget
|
|
|
|
|
? alignment === FlexLayerAlignment.Start
|
|
|
|
|
? containerWidth -
|
|
|
|
|
(canvasId === MAIN_CONTAINER_WIDGET_ID ? 0 : FLEXBOX_PADDING)
|
|
|
|
|
: 0
|
|
|
|
|
: width,
|
|
|
|
|
height: DEFAULT_HIGHLIGHT_SIZE,
|
|
|
|
|
isVertical: false,
|
|
|
|
|
canvasId,
|
|
|
|
|
dropZone,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return arr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Calculate drop zones for vertical highlights.
|
|
|
|
|
* Drop zone of vertical highlights span 35% of the distance between two consecutive highlights.
|
|
|
|
|
* @param highlights | HighlightInfo[] : array of highlights to be updated.
|
|
|
|
|
* @param canvasWidth | number : width of the canvas.
|
|
|
|
|
* @returns HighlightInfo[] : updated highlights.
|
|
|
|
|
*/
|
|
|
|
|
function updateVerticalHighlightDropZone(
|
|
|
|
|
highlights: HighlightInfo[],
|
|
|
|
|
canvasWidth: number,
|
|
|
|
|
): HighlightInfo[] {
|
|
|
|
|
const zoneSize = 0.35;
|
|
|
|
|
for (const [index, highlight] of highlights.entries()) {
|
|
|
|
|
const nextHighlight: HighlightInfo | undefined = highlights[index + 1];
|
|
|
|
|
const previousHighlight: HighlightInfo | undefined = highlights[index - 1];
|
2023-04-26 17:39:11 +00:00
|
|
|
const leftZone = Math.max(
|
|
|
|
|
previousHighlight
|
|
|
|
|
? (highlight.posX -
|
|
|
|
|
(highlight.posY < previousHighlight.posY + previousHighlight.height
|
|
|
|
|
? previousHighlight.posX
|
|
|
|
|
: 0)) *
|
|
|
|
|
zoneSize
|
|
|
|
|
: highlight.posX + DEFAULT_HIGHLIGHT_SIZE,
|
|
|
|
|
DEFAULT_HIGHLIGHT_SIZE,
|
|
|
|
|
);
|
|
|
|
|
const rightZone = Math.max(
|
|
|
|
|
nextHighlight
|
|
|
|
|
? ((highlight.posY + highlight.height > nextHighlight.posY
|
|
|
|
|
? nextHighlight.posX
|
|
|
|
|
: canvasWidth) -
|
|
|
|
|
highlight.posX) *
|
|
|
|
|
zoneSize
|
|
|
|
|
: canvasWidth - highlight.posX,
|
|
|
|
|
DEFAULT_HIGHLIGHT_SIZE,
|
|
|
|
|
);
|
2023-03-04 07:25:54 +00:00
|
|
|
highlights[index] = {
|
|
|
|
|
...highlight,
|
|
|
|
|
dropZone: {
|
|
|
|
|
left: leftZone,
|
|
|
|
|
right: rightZone,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return highlights;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update drop zones for horizontal highlights of the last row.
|
|
|
|
|
* Normally, bottom drop zone of a horizontal highlights spans 50% of the row height.
|
|
|
|
|
* However, if the next row of horizontal highlights is omitted on account of the dragged widgets,
|
|
|
|
|
* then update the previous row's bottom drop zone to span 100% of the row height.
|
|
|
|
|
* @param highlights | HighlightInfo[] : array of highlights to be updated.
|
|
|
|
|
* @returns HighlightInfo[] : updated highlights.
|
|
|
|
|
*/
|
|
|
|
|
function updateHorizontalDropZone(
|
|
|
|
|
highlights: HighlightInfo[],
|
|
|
|
|
): HighlightInfo[] {
|
|
|
|
|
let index = highlights.length - 1;
|
|
|
|
|
while (index >= 0 && highlights[index].isVertical) {
|
|
|
|
|
index -= 1;
|
|
|
|
|
}
|
|
|
|
|
if (index < 0) return highlights;
|
|
|
|
|
const dropZone = {
|
|
|
|
|
top: highlights[index].dropZone.top,
|
|
|
|
|
bottom: (highlights[index]?.dropZone?.bottom || 5) * 2,
|
|
|
|
|
};
|
|
|
|
|
const updatedHighlights: HighlightInfo[] = [
|
|
|
|
|
...highlights.slice(0, index - 2),
|
|
|
|
|
{
|
|
|
|
|
...highlights[index - 2],
|
|
|
|
|
dropZone,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
...highlights[index - 1],
|
|
|
|
|
dropZone,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
...highlights[index],
|
|
|
|
|
dropZone,
|
|
|
|
|
},
|
|
|
|
|
...highlights.slice(index + 1),
|
|
|
|
|
];
|
|
|
|
|
return updatedHighlights;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getPreviousOffsetTop(highlights: HighlightInfo[]): number {
|
|
|
|
|
if (!highlights.length) return -1;
|
|
|
|
|
let index = highlights.length - 1;
|
|
|
|
|
while (highlights[index].isVertical) {
|
|
|
|
|
index--;
|
|
|
|
|
}
|
|
|
|
|
return highlights[index].posY + highlights[index].height;
|
|
|
|
|
}
|