2024-08-06 14:52:22 +00:00
|
|
|
import { widgetURL } from "ee/RouteBuilder";
|
|
|
|
|
import type { ReduxAction } from "ee/constants/ReduxActionConstants";
|
2021-08-24 11:38:20 +00:00
|
|
|
import {
|
|
|
|
|
ReduxActionErrorTypes,
|
|
|
|
|
ReduxActionTypes,
|
2024-08-06 14:52:22 +00:00
|
|
|
} from "ee/constants/ReduxActionConstants";
|
|
|
|
|
import { getAppMode, getCanvasWidgets } from "ee/selectors/entitiesSelector";
|
2024-01-17 07:13:51 +00:00
|
|
|
import { showModal } from "actions/widgetActions";
|
2023-04-10 07:25:14 +00:00
|
|
|
import type {
|
|
|
|
|
SetSelectedWidgetsPayload,
|
|
|
|
|
WidgetSelectionRequestPayload,
|
|
|
|
|
} from "actions/widgetSelectionActions";
|
|
|
|
|
import {
|
|
|
|
|
setEntityExplorerAncestry,
|
|
|
|
|
setSelectedWidgetAncestry,
|
|
|
|
|
setSelectedWidgets,
|
|
|
|
|
} from "actions/widgetSelectionActions";
|
2024-01-17 07:13:51 +00:00
|
|
|
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
|
2023-11-17 07:16:18 +00:00
|
|
|
import { APP_MODE } from "entities/App";
|
2024-01-17 07:13:51 +00:00
|
|
|
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
|
|
|
|
|
import { all, call, put, select, take, takeLatest } from "redux-saga/effects";
|
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 { SetSelectionResult } from "sagas/WidgetSelectUtils";
|
2022-12-08 12:16:41 +00:00
|
|
|
import {
|
2023-01-28 02:17:06 +00:00
|
|
|
assertParentId,
|
2023-11-17 07:16:18 +00:00
|
|
|
getWidgetAncestry,
|
2023-01-28 02:17:06 +00:00
|
|
|
isInvalidSelectionRequest,
|
|
|
|
|
pushPopWidgetSelection,
|
|
|
|
|
selectAllWidgetsInCanvasSaga,
|
2024-03-13 06:23:49 +00:00
|
|
|
SelectionRequestType,
|
2023-01-28 02:17:06 +00:00
|
|
|
selectMultipleWidgets,
|
|
|
|
|
selectOneWidget,
|
|
|
|
|
shiftSelectWidgets,
|
|
|
|
|
unselectWidget,
|
|
|
|
|
} from "sagas/WidgetSelectUtils";
|
2023-11-17 07:16:18 +00:00
|
|
|
import {
|
2024-07-31 02:54:51 +00:00
|
|
|
getCurrentBasePageId,
|
2023-11-17 07:16:18 +00:00
|
|
|
getIsEditorInitialized,
|
|
|
|
|
getIsFetchingPage,
|
|
|
|
|
snipingModeSelector,
|
|
|
|
|
} from "selectors/editorSelectors";
|
2024-03-13 06:23:49 +00:00
|
|
|
import {
|
|
|
|
|
getLastSelectedWidget,
|
|
|
|
|
getSelectedWidgets,
|
|
|
|
|
getWidgetSelectionBlock,
|
|
|
|
|
} from "selectors/ui";
|
2023-01-28 02:17:06 +00:00
|
|
|
import { areArraysEqual } from "utils/AppsmithUtils";
|
2023-11-17 07:16:18 +00:00
|
|
|
import { quickScrollToWidget } from "utils/helpers";
|
|
|
|
|
import history, { NavigationMethod } from "utils/history";
|
|
|
|
|
import {
|
|
|
|
|
getWidgetIdsByType,
|
|
|
|
|
getWidgetImmediateChildren,
|
2024-01-26 04:00:57 +00:00
|
|
|
getWidgetMetaProps,
|
2023-11-17 07:16:18 +00:00
|
|
|
getWidgets,
|
|
|
|
|
} from "./selectors";
|
2024-01-26 04:00:57 +00:00
|
|
|
import { getModalWidgetType } from "selectors/widgetSelectors";
|
2024-02-27 04:41:55 +00:00
|
|
|
import { getWidgetSelectorByWidgetId } from "selectors/layoutSystemSelectors";
|
2024-08-06 14:52:22 +00:00
|
|
|
import { getAppViewerPageIdFromPath } from "ee/pages/Editor/Explorer/helpers";
|
|
|
|
|
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
|
2024-04-12 17:24:04 +00:00
|
|
|
import { getIsAnvilLayout } from "layoutSystems/anvil/integrations/selectors";
|
2021-06-17 13:26:54 +00:00
|
|
|
|
2023-03-04 07:25:54 +00:00
|
|
|
// The following is computed to be used in the entity explorer
|
|
|
|
|
// Every time a widget is selected, we need to expand widget entities
|
|
|
|
|
// in the entity explorer so that the selected widget is visible
|
2023-01-28 02:17:06 +00:00
|
|
|
function* selectWidgetSaga(action: ReduxAction<WidgetSelectionRequestPayload>) {
|
|
|
|
|
try {
|
2023-02-21 13:38:16 +00:00
|
|
|
const {
|
2024-07-31 02:54:51 +00:00
|
|
|
basePageId,
|
2023-02-21 13:38:16 +00:00
|
|
|
invokedBy,
|
2023-10-11 07:14:38 +00:00
|
|
|
payload = [],
|
|
|
|
|
selectionRequestType,
|
2023-02-21 13:38:16 +00:00
|
|
|
} = action.payload;
|
2024-03-13 05:17:45 +00:00
|
|
|
/**
|
|
|
|
|
* Apart from the normal selection request by a user on canvas, there are other ways which can trigger selection
|
|
|
|
|
* e.g. when a modal closes in the editor -> we select the main container.
|
|
|
|
|
* One way modal closes is because user navigates to home page using the appsmith icon. In this case, we don't want the selection process to trigger.
|
|
|
|
|
* This also safeguards against the case where the selection process is triggered by a non-canvas click where user moves out of editor.
|
|
|
|
|
* */
|
2021-06-17 13:26:54 +00:00
|
|
|
|
2024-03-13 05:17:45 +00:00
|
|
|
const isOnEditorURL = !!getAppViewerPageIdFromPath(
|
|
|
|
|
window.location.pathname,
|
|
|
|
|
);
|
|
|
|
|
if (payload.some(isInvalidSelectionRequest) || !isOnEditorURL) {
|
2023-01-28 02:17:06 +00:00
|
|
|
// Throw error
|
|
|
|
|
return;
|
2021-08-24 11:38:20 +00:00
|
|
|
}
|
|
|
|
|
|
2023-01-28 02:17:06 +00:00
|
|
|
let newSelection: SetSelectionResult;
|
2021-08-24 11:38:20 +00:00
|
|
|
|
2023-01-28 02:17:06 +00:00
|
|
|
const allWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
|
|
|
|
|
const selectedWidgets: string[] = yield select(getSelectedWidgets);
|
|
|
|
|
const lastSelectedWidget: string = yield select(getLastSelectedWidget);
|
2021-08-24 11:38:20 +00:00
|
|
|
|
2023-01-28 02:17:06 +00:00
|
|
|
// It is possible that the payload is empty.
|
|
|
|
|
// These properties can be used for a finding sibling widgets for certain types of selections
|
|
|
|
|
const widgetId = payload[0];
|
|
|
|
|
const parentId: string | undefined =
|
|
|
|
|
widgetId in allWidgets ? allWidgets[widgetId].parentId : undefined;
|
|
|
|
|
|
2023-02-21 04:13:25 +00:00
|
|
|
if (
|
|
|
|
|
widgetId &&
|
|
|
|
|
!allWidgets[widgetId] &&
|
|
|
|
|
selectionRequestType === SelectionRequestType.One
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-28 02:17:06 +00:00
|
|
|
switch (selectionRequestType) {
|
|
|
|
|
case SelectionRequestType.Empty: {
|
2024-01-12 05:23:47 +00:00
|
|
|
newSelection = [MAIN_CONTAINER_WIDGET_ID];
|
2023-02-21 13:38:16 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case SelectionRequestType.UnsafeSelect: {
|
|
|
|
|
newSelection = payload;
|
2023-01-28 02:17:06 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2024-03-15 09:27:06 +00:00
|
|
|
case SelectionRequestType.One:
|
|
|
|
|
case SelectionRequestType.Create: {
|
2023-01-28 02:17:06 +00:00
|
|
|
assertParentId(parentId);
|
|
|
|
|
newSelection = selectOneWidget(payload);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case SelectionRequestType.Multiple: {
|
|
|
|
|
newSelection = selectMultipleWidgets(payload, allWidgets);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case SelectionRequestType.ShiftSelect: {
|
|
|
|
|
assertParentId(parentId);
|
|
|
|
|
const siblingWidgets: string[] = yield select(
|
|
|
|
|
getWidgetImmediateChildren,
|
|
|
|
|
parentId,
|
|
|
|
|
);
|
|
|
|
|
newSelection = shiftSelectWidgets(
|
|
|
|
|
payload,
|
|
|
|
|
siblingWidgets,
|
|
|
|
|
selectedWidgets,
|
|
|
|
|
lastSelectedWidget,
|
2021-08-24 11:38:20 +00:00
|
|
|
);
|
2023-01-28 02:17:06 +00:00
|
|
|
break;
|
2021-08-24 11:38:20 +00:00
|
|
|
}
|
2023-01-28 02:17:06 +00:00
|
|
|
case SelectionRequestType.PushPop: {
|
|
|
|
|
assertParentId(parentId);
|
|
|
|
|
const siblingWidgets: string[] = yield select(
|
|
|
|
|
getWidgetImmediateChildren,
|
|
|
|
|
parentId,
|
|
|
|
|
);
|
|
|
|
|
newSelection = pushPopWidgetSelection(
|
|
|
|
|
payload,
|
|
|
|
|
selectedWidgets,
|
|
|
|
|
siblingWidgets,
|
2021-08-24 11:38:20 +00:00
|
|
|
);
|
2023-01-28 02:17:06 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case SelectionRequestType.Unselect: {
|
|
|
|
|
newSelection = unselectWidget(payload, selectedWidgets);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case SelectionRequestType.All: {
|
|
|
|
|
newSelection = yield call(selectAllWidgetsInCanvasSaga);
|
2021-08-24 11:38:20 +00:00
|
|
|
}
|
2021-06-17 13:26:54 +00:00
|
|
|
}
|
|
|
|
|
|
2023-01-28 02:17:06 +00:00
|
|
|
if (!newSelection) return;
|
2021-06-17 13:26:54 +00:00
|
|
|
|
2023-01-28 02:17:06 +00:00
|
|
|
// When append selections happen, we want to ensure they all exist under the same parent
|
|
|
|
|
// Selections across parents is not possible.
|
|
|
|
|
if (
|
|
|
|
|
[SelectionRequestType.PushPop, SelectionRequestType.ShiftSelect].includes(
|
|
|
|
|
selectionRequestType,
|
|
|
|
|
) &&
|
2023-02-21 13:38:16 +00:00
|
|
|
newSelection[0] in allWidgets
|
2023-01-28 02:17:06 +00:00
|
|
|
) {
|
2023-02-21 13:38:16 +00:00
|
|
|
const selectionWidgetId = newSelection[0];
|
2023-01-28 02:17:06 +00:00
|
|
|
const parentId = allWidgets[selectionWidgetId].parentId;
|
|
|
|
|
if (parentId) {
|
|
|
|
|
const selectionSiblingWidgets: string[] = yield select(
|
|
|
|
|
getWidgetImmediateChildren,
|
|
|
|
|
parentId,
|
|
|
|
|
);
|
2023-02-21 13:38:16 +00:00
|
|
|
newSelection = newSelection.filter((each) =>
|
2023-01-28 02:17:06 +00:00
|
|
|
selectionSiblingWidgets.includes(each),
|
|
|
|
|
);
|
2021-08-24 11:38:20 +00:00
|
|
|
}
|
2021-06-17 13:26:54 +00:00
|
|
|
}
|
2023-02-21 13:38:16 +00:00
|
|
|
|
|
|
|
|
if (areArraysEqual([...newSelection], [...selectedWidgets])) {
|
2023-03-23 05:43:07 +00:00
|
|
|
yield put(setSelectedWidgets(newSelection));
|
2023-02-21 13:38:16 +00:00
|
|
|
return;
|
2021-08-24 11:38:20 +00:00
|
|
|
}
|
2024-03-15 09:27:06 +00:00
|
|
|
yield call(
|
|
|
|
|
appendSelectedWidgetToUrlSaga,
|
|
|
|
|
newSelection,
|
|
|
|
|
selectionRequestType,
|
2024-07-31 02:54:51 +00:00
|
|
|
basePageId,
|
2024-03-15 09:27:06 +00:00
|
|
|
invokedBy,
|
|
|
|
|
);
|
2021-08-24 11:38:20 +00:00
|
|
|
} catch (error) {
|
|
|
|
|
yield put({
|
|
|
|
|
type: ReduxActionErrorTypes.WIDGET_SELECTION_ERROR,
|
|
|
|
|
payload: {
|
2023-01-28 02:17:06 +00:00
|
|
|
action: ReduxActionTypes.SELECT_WIDGET_INIT,
|
2021-08-24 11:38:20 +00:00
|
|
|
error,
|
|
|
|
|
},
|
|
|
|
|
});
|
2021-06-28 07:11:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-17 15:16:38 +00:00
|
|
|
/**
|
|
|
|
|
* Append Selected widgetId as hash to the url path
|
2023-02-21 13:38:16 +00:00
|
|
|
* @param selectedWidgets
|
2024-03-15 09:27:06 +00:00
|
|
|
* @param type
|
2024-07-31 02:54:51 +00:00
|
|
|
* @param basePageId
|
2023-02-21 13:38:16 +00:00
|
|
|
* @param invokedBy
|
2022-10-17 15:16:38 +00:00
|
|
|
*/
|
|
|
|
|
function* appendSelectedWidgetToUrlSaga(
|
2023-02-21 13:38:16 +00:00
|
|
|
selectedWidgets: string[],
|
2024-03-15 09:27:06 +00:00
|
|
|
type: SelectionRequestType,
|
2024-07-31 02:54:51 +00:00
|
|
|
basePageId?: string,
|
2023-02-21 13:38:16 +00:00
|
|
|
invokedBy?: NavigationMethod,
|
2022-10-17 15:16:38 +00:00
|
|
|
) {
|
2023-02-21 13:38:16 +00:00
|
|
|
const isSnipingMode: boolean = yield select(snipingModeSelector);
|
2024-03-13 06:23:49 +00:00
|
|
|
const isWidgetSelectionBlocked: boolean = yield select(
|
|
|
|
|
getWidgetSelectionBlock,
|
|
|
|
|
);
|
2023-02-21 13:38:16 +00:00
|
|
|
const appMode: APP_MODE = yield select(getAppMode);
|
|
|
|
|
const viewMode = appMode === APP_MODE.PUBLISHED;
|
2023-03-01 04:18:58 +00:00
|
|
|
if (isSnipingMode || viewMode) return;
|
2024-03-13 06:23:49 +00:00
|
|
|
|
2023-02-21 13:38:16 +00:00
|
|
|
const { pathname } = window.location;
|
2024-07-31 02:54:51 +00:00
|
|
|
const currentBasePageId: string = yield select(getCurrentBasePageId);
|
2023-02-21 13:38:16 +00:00
|
|
|
const currentURL = pathname;
|
|
|
|
|
const newUrl = selectedWidgets.length
|
|
|
|
|
? widgetURL({
|
2024-07-31 02:54:51 +00:00
|
|
|
basePageId: basePageId ?? currentBasePageId,
|
2023-02-21 13:38:16 +00:00
|
|
|
persistExistingParams: true,
|
2024-03-15 09:27:06 +00:00
|
|
|
add: type === SelectionRequestType.Create,
|
2023-02-21 13:38:16 +00:00
|
|
|
selectedWidgets,
|
|
|
|
|
})
|
2024-01-12 05:23:47 +00:00
|
|
|
: widgetURL({
|
2024-07-31 02:54:51 +00:00
|
|
|
basePageId: basePageId ?? currentBasePageId,
|
2023-02-21 13:38:16 +00:00
|
|
|
persistExistingParams: true,
|
2024-01-12 05:23:47 +00:00
|
|
|
selectedWidgets: [MAIN_CONTAINER_WIDGET_ID],
|
2023-02-21 13:38:16 +00:00
|
|
|
});
|
2024-03-13 06:23:49 +00:00
|
|
|
if (invokedBy === NavigationMethod.CanvasClick && isWidgetSelectionBlocked) {
|
|
|
|
|
AnalyticsUtil.logEvent("CODE_MODE_WIDGET_SELECTION");
|
|
|
|
|
}
|
2023-02-21 13:38:16 +00:00
|
|
|
if (currentURL !== newUrl) {
|
|
|
|
|
history.push(newUrl, { invokedBy });
|
2022-10-17 15:16:38 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-31 15:41:28 +00:00
|
|
|
// TODO: Fix this the next time the file is edited
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
2023-03-23 05:43:07 +00:00
|
|
|
function* waitForInitialization(saga: any, action: ReduxAction<unknown>) {
|
|
|
|
|
const isEditorInitialized: boolean = yield select(getIsEditorInitialized);
|
|
|
|
|
const appMode: APP_MODE = yield select(getAppMode);
|
2023-10-10 10:10:53 +00:00
|
|
|
const isViewMode = appMode === APP_MODE.PUBLISHED;
|
|
|
|
|
|
|
|
|
|
// Wait until the editor is initialised, and ensure we're not in the view mode
|
|
|
|
|
if (!isEditorInitialized && !isViewMode) {
|
2023-03-23 05:43:07 +00:00
|
|
|
yield take(ReduxActionTypes.INITIALIZE_EDITOR_SUCCESS);
|
|
|
|
|
}
|
2023-10-10 10:10:53 +00:00
|
|
|
|
|
|
|
|
// Wait until we're done fetching the page
|
|
|
|
|
// This is so that we can reliably assume that the Editor and the Canvas have loaded
|
|
|
|
|
const isPageFetching: boolean = yield select(getIsFetchingPage);
|
2023-03-23 05:43:07 +00:00
|
|
|
if (isPageFetching) {
|
|
|
|
|
yield take(ReduxActionTypes.FETCH_PAGE_SUCCESS);
|
2021-08-16 09:24:42 +00:00
|
|
|
}
|
2023-10-10 10:10:53 +00:00
|
|
|
|
|
|
|
|
// Continue yielding
|
2023-03-23 05:43:07 +00:00
|
|
|
yield call(saga, action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function* handleWidgetSelectionSaga(
|
2023-04-10 07:25:14 +00:00
|
|
|
action: ReduxAction<SetSelectedWidgetsPayload>,
|
2023-03-23 05:43:07 +00:00
|
|
|
) {
|
|
|
|
|
yield call(focusOnWidgetSaga, action);
|
|
|
|
|
yield call(openOrCloseModalSaga, action);
|
2023-04-10 07:25:14 +00:00
|
|
|
yield call(setWidgetAncestry, action);
|
2021-08-16 09:24:42 +00:00
|
|
|
}
|
|
|
|
|
|
2023-01-28 02:17:06 +00:00
|
|
|
function* openOrCloseModalSaga(action: ReduxAction<{ widgetIds: string[] }>) {
|
fix: Anvil Widgets not accessible when widget has no content. (#30780)
> Pull Request Template
>
> Use this template to quickly create a well written pull request.
Delete all quotes before creating the pull request.
>
## Description
In this PR, we are fixing a few issues and also destructuring
AnvilFlexComponent for edit and view.
Issues Fixed
- Widgets without any content like a text widget without text are not
hoverable
- Modal once opened does not close when other widgets on the canvas are
selected via the enitty explorer.
Anvil Flex Component was common component inspired from
PositionedContainer of Fixed layout. It had all features of Edit and
View together in one place. This mean viewer was unnecessarily
interpreting more code.
Now AnvilFlexComponent has been broken into AnvilFlexComponent and
AnvilEditorFlexComponent.
AnvilEditorFlexComponent is a wrapper around AnvilFlexComponent with
abilities needed for Edit Mode.
Another issue addressed in the PR is removal of DraggableComponennt,
which was just making dragging possible and providing a few styles like
fading the widget when it is being dragged.
With this PR all the above mentioned functions will be taken care of by
AnvilEditorFlexComponent.
#### PR fixes following issue(s)
Fixes #30734
> if no issue exists, please create an issue and ask the maintainers
about this first
>
>
#### Media
> A video or a GIF is preferred. when using Loom, don’t embed because it
looks like it’s a GIF. instead, just link to the video
>
>
#### Type of change
> Please delete options that are not relevant.
- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- Chore (housekeeping or task changes that don't impact user perception)
- This change requires a documentation update
>
>
>
## Testing
>
#### How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Also
list any relevant details for your test configuration.
> Delete anything that is not relevant
- [ ] Manual
- [ ] JUnit
- [ ] Jest
- [ ] Cypress
>
>
#### 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
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Summary by CodeRabbit
- **New Features**
- Enhanced feature flag management with additional flags for better
control over application features.
- Introduced a new editor component for Anvil layout system, improving
layout and behavior management in edit mode.
- Added a custom hook for managing hover states on Anvil widgets,
enhancing user interaction.
- **Refactor**
- Updated AnvilFlexComponent to use `forwardRef` for better ref
management and optimized widget configuration and rendering logic.
- Modified selector logic to simplify the retrieval of layout system
type, enhancing code maintainability.
- Adjusted test methodologies to improve reliability and accuracy.
- **Bug Fixes**
- Corrected assertions in Cypress end-to-end tests to accurately locate
and interact with widgets in the Anvil canvas, ensuring test
reliability.
- **Chores**
- Updated common locators and assertion methods in Cypress support files
for consistency and clarity in test scripts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-02-05 12:30:50 +00:00
|
|
|
const widgetsToSelect = action.payload.widgetIds;
|
|
|
|
|
if (widgetsToSelect.length !== 1) return;
|
|
|
|
|
if (
|
|
|
|
|
widgetsToSelect.length === 1 &&
|
|
|
|
|
widgetsToSelect[0] === MAIN_CONTAINER_WIDGET_ID
|
|
|
|
|
) {
|
|
|
|
|
// for cases where a widget inside modal is deleted and main canvas gets selected post that.
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-08-16 09:24:42 +00:00
|
|
|
|
2023-10-10 10:10:53 +00:00
|
|
|
// Let's assume that the payload widgetId is a modal widget and we need to open the modal as it is selected
|
|
|
|
|
let modalWidgetToOpen: string = action.payload.widgetIds[0];
|
2022-12-08 12:16:41 +00:00
|
|
|
|
2024-01-26 04:00:57 +00:00
|
|
|
const modalWidgetType: string = yield select(getModalWidgetType);
|
|
|
|
|
|
2023-10-10 10:10:53 +00:00
|
|
|
// Get all modal widget ids
|
2022-12-08 12:16:41 +00:00
|
|
|
const modalWidgetIds: string[] = yield select(
|
|
|
|
|
getWidgetIdsByType,
|
2024-01-26 04:00:57 +00:00
|
|
|
modalWidgetType,
|
2022-12-08 12:16:41 +00:00
|
|
|
);
|
|
|
|
|
|
2023-10-10 10:10:53 +00:00
|
|
|
// Get all widgets
|
|
|
|
|
const allWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
|
|
|
|
|
// Get the ancestry of the selected widget
|
|
|
|
|
const widgetAncestry = getWidgetAncestry(modalWidgetToOpen, allWidgets);
|
|
|
|
|
|
|
|
|
|
// If the selected widget is a modal, we want to open the modal
|
|
|
|
|
const widgetIsModal =
|
|
|
|
|
// Check if the widget is a modal widget
|
|
|
|
|
modalWidgetIds.includes(modalWidgetToOpen);
|
|
|
|
|
|
|
|
|
|
// Let's assume that this is not a child of a modal widget
|
|
|
|
|
let widgetIsChildOfModal = false;
|
|
|
|
|
|
|
|
|
|
if (!widgetIsModal) {
|
|
|
|
|
// Check if the widget is a child of a modal widget
|
|
|
|
|
const indexOfParentModalWidget: number = widgetAncestry.findIndex((id) =>
|
|
|
|
|
modalWidgetIds.includes(id),
|
|
|
|
|
);
|
|
|
|
|
// If we found a modal widget in the ancestry, we want to open that modal
|
|
|
|
|
if (indexOfParentModalWidget > -1) {
|
|
|
|
|
// Set the flag to true, so that we can open the modal
|
|
|
|
|
widgetIsChildOfModal = true;
|
|
|
|
|
modalWidgetToOpen = widgetAncestry[indexOfParentModalWidget];
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-12 17:24:04 +00:00
|
|
|
const isAnvilLayout: boolean = yield select(getIsAnvilLayout);
|
|
|
|
|
if (isAnvilLayout) {
|
2024-01-26 04:00:57 +00:00
|
|
|
// If widget is modal and modal is already open, skip opening it
|
|
|
|
|
const modalProps = allWidgets[modalWidgetToOpen];
|
|
|
|
|
const metaProps: Record<string, unknown> = yield select(
|
|
|
|
|
getWidgetMetaProps,
|
|
|
|
|
modalProps,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
(widgetIsModal || widgetIsChildOfModal) &&
|
|
|
|
|
metaProps?.isVisible === true
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-12-08 12:16:41 +00:00
|
|
|
|
2023-10-10 10:10:53 +00:00
|
|
|
if (widgetIsModal || widgetIsChildOfModal) {
|
|
|
|
|
yield put(showModal(modalWidgetToOpen));
|
2022-12-08 12:16:41 +00:00
|
|
|
}
|
fix: Anvil Widgets not accessible when widget has no content. (#30780)
> Pull Request Template
>
> Use this template to quickly create a well written pull request.
Delete all quotes before creating the pull request.
>
## Description
In this PR, we are fixing a few issues and also destructuring
AnvilFlexComponent for edit and view.
Issues Fixed
- Widgets without any content like a text widget without text are not
hoverable
- Modal once opened does not close when other widgets on the canvas are
selected via the enitty explorer.
Anvil Flex Component was common component inspired from
PositionedContainer of Fixed layout. It had all features of Edit and
View together in one place. This mean viewer was unnecessarily
interpreting more code.
Now AnvilFlexComponent has been broken into AnvilFlexComponent and
AnvilEditorFlexComponent.
AnvilEditorFlexComponent is a wrapper around AnvilFlexComponent with
abilities needed for Edit Mode.
Another issue addressed in the PR is removal of DraggableComponennt,
which was just making dragging possible and providing a few styles like
fading the widget when it is being dragged.
With this PR all the above mentioned functions will be taken care of by
AnvilEditorFlexComponent.
#### PR fixes following issue(s)
Fixes #30734
> if no issue exists, please create an issue and ask the maintainers
about this first
>
>
#### Media
> A video or a GIF is preferred. when using Loom, don’t embed because it
looks like it’s a GIF. instead, just link to the video
>
>
#### Type of change
> Please delete options that are not relevant.
- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- Chore (housekeeping or task changes that don't impact user perception)
- This change requires a documentation update
>
>
>
## Testing
>
#### How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Also
list any relevant details for your test configuration.
> Delete anything that is not relevant
- [ ] Manual
- [ ] JUnit
- [ ] Jest
- [ ] Cypress
>
>
#### 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
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Summary by CodeRabbit
- **New Features**
- Enhanced feature flag management with additional flags for better
control over application features.
- Introduced a new editor component for Anvil layout system, improving
layout and behavior management in edit mode.
- Added a custom hook for managing hover states on Anvil widgets,
enhancing user interaction.
- **Refactor**
- Updated AnvilFlexComponent to use `forwardRef` for better ref
management and optimized widget configuration and rendering logic.
- Modified selector logic to simplify the retrieval of layout system
type, enhancing code maintainability.
- Adjusted test methodologies to improve reliability and accuracy.
- **Bug Fixes**
- Corrected assertions in Cypress end-to-end tests to accurately locate
and interact with widgets in the Anvil canvas, ensuring test
reliability.
- **Chores**
- Updated common locators and assertion methods in Cypress support files
for consistency and clarity in test scripts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-02-05 12:30:50 +00:00
|
|
|
if (!widgetIsModal && !widgetIsChildOfModal) {
|
|
|
|
|
yield put({
|
|
|
|
|
type: ReduxActionTypes.CLOSE_MODAL,
|
|
|
|
|
payload: {},
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-12-08 12:16:41 +00:00
|
|
|
}
|
|
|
|
|
|
2023-02-21 13:38:16 +00:00
|
|
|
function* focusOnWidgetSaga(action: ReduxAction<{ widgetIds: string[] }>) {
|
|
|
|
|
if (action.payload.widgetIds.length > 1) return;
|
|
|
|
|
const widgetId = action.payload.widgetIds[0];
|
|
|
|
|
if (widgetId) {
|
2023-03-23 05:43:07 +00:00
|
|
|
const allWidgets: CanvasWidgetsReduxState = yield select(getCanvasWidgets);
|
2024-02-27 04:41:55 +00:00
|
|
|
const widgetIdSelector: string = yield select(
|
|
|
|
|
getWidgetSelectorByWidgetId,
|
|
|
|
|
widgetId,
|
|
|
|
|
);
|
|
|
|
|
quickScrollToWidget(widgetId, widgetIdSelector, allWidgets);
|
2023-02-21 13:38:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-10 07:25:14 +00:00
|
|
|
function* setWidgetAncestry(action: ReduxAction<SetSelectedWidgetsPayload>) {
|
|
|
|
|
const allWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
|
|
|
|
|
|
|
|
|
|
// When a widget selection is triggered via a canvas click,
|
|
|
|
|
// we do not want to set the widget ancestry. This is so
|
|
|
|
|
// that if the widget like a button causes a widget
|
|
|
|
|
// navigation, it would block the navigation
|
|
|
|
|
const dontSetSelectedAncestry =
|
|
|
|
|
action.payload.invokedBy === undefined ||
|
|
|
|
|
action.payload.invokedBy === NavigationMethod.CanvasClick;
|
|
|
|
|
|
|
|
|
|
const widgetAncestry = getWidgetAncestry(
|
|
|
|
|
action.payload.widgetIds[0],
|
|
|
|
|
allWidgets,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (dontSetSelectedAncestry) {
|
|
|
|
|
yield put(setSelectedWidgetAncestry([]));
|
|
|
|
|
} else {
|
|
|
|
|
yield put(setSelectedWidgetAncestry(widgetAncestry));
|
|
|
|
|
}
|
|
|
|
|
yield put(setEntityExplorerAncestry(widgetAncestry));
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-17 13:26:54 +00:00
|
|
|
export function* widgetSelectionSagas() {
|
|
|
|
|
yield all([
|
2023-01-28 02:17:06 +00:00
|
|
|
takeLatest(ReduxActionTypes.SELECT_WIDGET_INIT, selectWidgetSaga),
|
2021-06-17 13:26:54 +00:00
|
|
|
takeLatest(
|
2023-01-28 02:17:06 +00:00
|
|
|
ReduxActionTypes.SET_SELECTED_WIDGETS,
|
2023-03-23 05:43:07 +00:00
|
|
|
waitForInitialization,
|
|
|
|
|
handleWidgetSelectionSaga,
|
2022-12-08 12:16:41 +00:00
|
|
|
),
|
2021-06-17 13:26:54 +00:00
|
|
|
]);
|
|
|
|
|
}
|