chore: Update Entity Tree implementation for better composition (#39488)
This commit is contained in:
parent
213ebf963b
commit
3f2972e27c
|
|
@ -9,6 +9,7 @@ import type {
|
|||
} from "./EntityListTree.types";
|
||||
import { ExplorerContainer } from "../ExplorerContainer";
|
||||
import { Flex, Icon } from "../../..";
|
||||
import { EntityItem } from "../EntityItem";
|
||||
import { noop } from "lodash";
|
||||
|
||||
const meta: Meta<typeof EntityListTree> = {
|
||||
|
|
@ -18,7 +19,6 @@ const meta: Meta<typeof EntityListTree> = {
|
|||
|
||||
export default meta;
|
||||
|
||||
const onClick = noop;
|
||||
const nameEditorConfig = {
|
||||
canEdit: true,
|
||||
isEditing: false,
|
||||
|
|
@ -28,60 +28,50 @@ const nameEditorConfig = {
|
|||
validateName: () => null,
|
||||
};
|
||||
|
||||
const names = {
|
||||
"1": "Parent 1",
|
||||
"1.1": "Child 1.1",
|
||||
"1.1.1": "Child 1.1.1",
|
||||
"1.1.2": "Child 1.1.2",
|
||||
"1.2": "Child 1.2",
|
||||
"2": "Parent 2",
|
||||
};
|
||||
|
||||
const Tree: EntityListTreeProps["items"] = [
|
||||
{
|
||||
startIcon: <Icon name="apps-line" />,
|
||||
id: "1",
|
||||
title: "Parent 1",
|
||||
isExpanded: true,
|
||||
onClick,
|
||||
nameEditorConfig,
|
||||
isSelected: false,
|
||||
children: [
|
||||
{
|
||||
startIcon: <Icon name="apps-line" />,
|
||||
id: "1.1",
|
||||
title: "Child 1",
|
||||
isExpanded: false,
|
||||
isSelected: true,
|
||||
onClick,
|
||||
nameEditorConfig,
|
||||
children: [
|
||||
{
|
||||
startIcon: <Icon name="apps-line" />,
|
||||
id: "1.1.1",
|
||||
title: "Grandchild 1",
|
||||
isExpanded: false,
|
||||
onClick,
|
||||
nameEditorConfig,
|
||||
isSelected: false,
|
||||
},
|
||||
{
|
||||
startIcon: <Icon name="apps-line" />,
|
||||
id: "1.1.2",
|
||||
isDisabled: true,
|
||||
title: "Grandchild 2",
|
||||
isExpanded: false,
|
||||
onClick,
|
||||
nameEditorConfig,
|
||||
isSelected: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
startIcon: <Icon name="apps-line" />,
|
||||
id: "1.2",
|
||||
title: "Child 2",
|
||||
isExpanded: false,
|
||||
onClick,
|
||||
nameEditorConfig,
|
||||
isSelected: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
startIcon: <Icon name="apps-line" />,
|
||||
id: "2",
|
||||
title: "Parent 2",
|
||||
isExpanded: false,
|
||||
onClick,
|
||||
nameEditorConfig,
|
||||
isSelected: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -97,28 +87,9 @@ const treeUpdate = (
|
|||
});
|
||||
};
|
||||
|
||||
const Template = (props: { outsideSelection: string }) => {
|
||||
const [expanded, setExpanded] = React.useState<Record<string, boolean>>({});
|
||||
const [selected, setSelected] = React.useState<string | null>(
|
||||
props.outsideSelection,
|
||||
);
|
||||
const EntityItemComponent = (props: { item: EntityListTreeItem }) => {
|
||||
const { item } = props;
|
||||
const [editing, setEditing] = React.useState<string | null>(null);
|
||||
|
||||
useEffect(
|
||||
function handleSyncOfSelection() {
|
||||
setSelected(props.outsideSelection);
|
||||
},
|
||||
[props.outsideSelection],
|
||||
);
|
||||
|
||||
const onExpandClick = (id: string) => {
|
||||
setExpanded((prev) => ({ ...prev, [id]: !Boolean(prev[id]) }));
|
||||
};
|
||||
|
||||
const onItemSelect = (id: string) => {
|
||||
setSelected(id);
|
||||
};
|
||||
|
||||
const onItemEdit = (id: string) => {
|
||||
setEditing(id);
|
||||
};
|
||||
|
|
@ -127,27 +98,56 @@ const Template = (props: { outsideSelection: string }) => {
|
|||
setEditing(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<EntityItem
|
||||
{...item}
|
||||
nameEditorConfig={{
|
||||
...nameEditorConfig,
|
||||
isEditing: item.id === editing,
|
||||
onEditComplete: completeEdit,
|
||||
onNameSave: noop,
|
||||
validateName: () => null,
|
||||
}}
|
||||
onClick={noop}
|
||||
onDoubleClick={() => onItemEdit(item.id)}
|
||||
startIcon={<Icon name="apps-line" />}
|
||||
title={names[item.id as keyof typeof names] || item.id}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Template = (props: { selectedItem: string }) => {
|
||||
const [expanded, setExpanded] = React.useState<Record<string, boolean>>({});
|
||||
const [selected, setSelected] = React.useState<string | null>(
|
||||
props.selectedItem,
|
||||
);
|
||||
|
||||
useEffect(
|
||||
function handleSyncOfSelection() {
|
||||
setSelected(props.selectedItem);
|
||||
},
|
||||
[props.selectedItem],
|
||||
);
|
||||
|
||||
const onExpandClick = (id: string) => {
|
||||
setExpanded((prev) => ({ ...prev, [id]: !Boolean(prev[id]) }));
|
||||
};
|
||||
|
||||
const updatedTree = treeUpdate(Tree, (item) => ({
|
||||
...item,
|
||||
isExpanded: Boolean(expanded[item.id]),
|
||||
isSelected: item.id === selected,
|
||||
onClick: () => onItemSelect(item.id),
|
||||
onDoubleClick: () => onItemEdit(item.id),
|
||||
nameEditorConfig: {
|
||||
canEdit: true,
|
||||
isEditing: item.id === editing,
|
||||
isLoading: false,
|
||||
onEditComplete: completeEdit,
|
||||
onNameSave: noop,
|
||||
validateName: () => null,
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<Flex bg="white" overflow="hidden" width="400px">
|
||||
<ExplorerContainer borderRight="STANDARD" height="500px" width="255px">
|
||||
<Flex flexDirection="column" gap="spaces-2" p="spaces-3">
|
||||
<EntityListTree items={updatedTree} onItemExpand={onExpandClick} />
|
||||
<EntityListTree
|
||||
ItemComponent={EntityItemComponent}
|
||||
items={updatedTree}
|
||||
onItemExpand={onExpandClick}
|
||||
/>
|
||||
</Flex>
|
||||
</ExplorerContainer>
|
||||
</Flex>
|
||||
|
|
@ -157,5 +157,5 @@ const Template = (props: { outsideSelection: string }) => {
|
|||
export const Basic = Template.bind({}) as StoryObj;
|
||||
|
||||
Basic.args = {
|
||||
outsideSelection: "1",
|
||||
selectedItem: "1",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,39 +1,41 @@
|
|||
import React from "react";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { EntityListTree } from "./EntityListTree";
|
||||
import type { EntityListTreeProps } from "./EntityListTree.types";
|
||||
import type {
|
||||
EntityListTreeItem,
|
||||
EntityListTreeProps,
|
||||
} from "./EntityListTree.types";
|
||||
|
||||
const mockOnItemExpand = jest.fn();
|
||||
const mockNameEditorConfig = {
|
||||
canEdit: true,
|
||||
isEditing: false,
|
||||
isLoading: false,
|
||||
onEditComplete: jest.fn(),
|
||||
onNameSave: jest.fn(),
|
||||
validateName: jest.fn(),
|
||||
|
||||
const name = {
|
||||
"1": "Parent 1",
|
||||
"1.1": "Child 1.1",
|
||||
"1.1.1": "Child 1.1.1",
|
||||
"1.1.2": "Child 1.1.2",
|
||||
"1.2": "Child 1.2",
|
||||
"2": "No Children Parent",
|
||||
"1-1": "Child",
|
||||
};
|
||||
|
||||
const mockOnClick = jest.fn();
|
||||
const ItemComponent = ({ item }: { item: EntityListTreeItem }) => {
|
||||
return <div>{name[item.id as keyof typeof name] || item.id}</div>;
|
||||
};
|
||||
|
||||
const defaultProps: EntityListTreeProps = {
|
||||
ItemComponent,
|
||||
items: [
|
||||
{
|
||||
id: "1",
|
||||
title: "Parent",
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
isDisabled: false,
|
||||
nameEditorConfig: mockNameEditorConfig,
|
||||
onClick: mockOnClick,
|
||||
children: [
|
||||
{
|
||||
id: "1-1",
|
||||
title: "Child",
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
isDisabled: false,
|
||||
nameEditorConfig: mockNameEditorConfig,
|
||||
onClick: mockOnClick,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
|
|
@ -50,7 +52,7 @@ describe("EntityListTree", () => {
|
|||
|
||||
it("calls onItemExpand when expand icon is clicked", () => {
|
||||
render(<EntityListTree {...defaultProps} />);
|
||||
const expandIcon = screen.getByTestId("entity-item-expand-icon");
|
||||
const expandIcon = screen.getByTestId("t--entity-item-expand-icon");
|
||||
|
||||
fireEvent.click(expandIcon);
|
||||
expect(mockOnItemExpand).toHaveBeenCalledWith("1");
|
||||
|
|
@ -62,19 +64,16 @@ describe("EntityListTree", () => {
|
|||
items: [
|
||||
{
|
||||
id: "2",
|
||||
title: "No Children Parent",
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
isDisabled: false,
|
||||
children: [],
|
||||
nameEditorConfig: mockNameEditorConfig,
|
||||
onClick: mockOnClick,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
render(<EntityListTree {...props} />);
|
||||
const expandIcon = screen.queryByTestId("entity-item-expand-icon");
|
||||
const expandIcon = screen.queryByTestId("t--entity-item-expand-icon");
|
||||
|
||||
expect(
|
||||
screen.getByRole("treeitem", { name: "No Children Parent" }),
|
||||
|
|
@ -88,21 +87,15 @@ describe("EntityListTree", () => {
|
|||
items: [
|
||||
{
|
||||
id: "1",
|
||||
title: "Parent",
|
||||
isExpanded: true,
|
||||
isSelected: false,
|
||||
isDisabled: false,
|
||||
nameEditorConfig: mockNameEditorConfig,
|
||||
onClick: mockOnClick,
|
||||
children: [
|
||||
{
|
||||
id: "1-1",
|
||||
title: "Child",
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
isDisabled: false,
|
||||
nameEditorConfig: mockNameEditorConfig,
|
||||
onClick: mockOnClick,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React, { useCallback } from "react";
|
|||
import type { EntityListTreeProps } from "./EntityListTree.types";
|
||||
import { Flex } from "../../../Flex";
|
||||
import { Icon } from "../../../Icon";
|
||||
import { EntityItem } from "../EntityItem";
|
||||
import {
|
||||
CollapseSpacer,
|
||||
PaddingOverrider,
|
||||
|
|
@ -11,7 +10,7 @@ import {
|
|||
} from "./EntityListTree.styles";
|
||||
|
||||
export function EntityListTree(props: EntityListTreeProps) {
|
||||
const { onItemExpand } = props;
|
||||
const { ItemComponent, onItemExpand } = props;
|
||||
|
||||
const handleOnExpandClick = useCallback(
|
||||
(event: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
|
||||
|
|
@ -51,7 +50,7 @@ export function EntityListTree(props: EntityListTreeProps) {
|
|||
{item.children && item.children.length ? (
|
||||
<CollapseWrapper
|
||||
data-itemid={item.id}
|
||||
data-testid="entity-item-expand-icon"
|
||||
data-testid="t--entity-item-expand-icon"
|
||||
onClick={handleOnExpandClick}
|
||||
>
|
||||
<Icon
|
||||
|
|
@ -65,11 +64,12 @@ export function EntityListTree(props: EntityListTreeProps) {
|
|||
<CollapseSpacer />
|
||||
)}
|
||||
<PaddingOverrider>
|
||||
<EntityItem {...item} />
|
||||
<ItemComponent item={item} />
|
||||
</PaddingOverrider>
|
||||
</EntityItemWrapper>
|
||||
{item.children && item.isExpanded ? (
|
||||
<EntityListTree
|
||||
ItemComponent={ItemComponent}
|
||||
depth={childrenDepth}
|
||||
items={item.children}
|
||||
onItemExpand={onItemExpand}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import type { EntityItemProps } from "../EntityItem/EntityItem.types";
|
||||
|
||||
export interface EntityListTreeItem extends EntityItemProps {
|
||||
export interface EntityListTreeItem {
|
||||
children?: EntityListTreeItem[];
|
||||
isExpanded: boolean;
|
||||
isSelected: boolean;
|
||||
isDisabled?: boolean;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface EntityListTreeProps {
|
||||
depth?: number;
|
||||
items: EntityListTreeItem[];
|
||||
ItemComponent: React.ComponentType<{ item: EntityListTreeItem }>;
|
||||
onItemExpand: (id: string) => void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ import { getUsedActionNames } from "selectors/actionSelectors";
|
|||
import { isNameValid } from "utils/helpers";
|
||||
|
||||
interface UseValidateEntityNameProps {
|
||||
entityName?: string;
|
||||
entityName: string;
|
||||
entityId?: string;
|
||||
nameErrorMessage?: (name: string) => string;
|
||||
}
|
||||
|
||||
|
|
@ -18,18 +19,22 @@ interface UseValidateEntityNameProps {
|
|||
* Provides a unified way to validate entity names.
|
||||
*/
|
||||
export function useValidateEntityName(props: UseValidateEntityNameProps) {
|
||||
const { entityName, nameErrorMessage = ACTION_NAME_CONFLICT_ERROR } = props;
|
||||
const {
|
||||
entityId = "",
|
||||
entityName,
|
||||
nameErrorMessage = ACTION_NAME_CONFLICT_ERROR,
|
||||
} = props;
|
||||
|
||||
const usedEntityNames = useSelector(
|
||||
(state: AppState) => getUsedActionNames(state, ""),
|
||||
(state: AppState) => getUsedActionNames(state, entityId),
|
||||
shallowEqual,
|
||||
);
|
||||
|
||||
return useCallback(
|
||||
(name: string, oldName: string | undefined = entityName): string | null => {
|
||||
(name: string): string | null => {
|
||||
if (!name || name.trim().length === 0) {
|
||||
return createMessage(ACTION_INVALID_NAME_ERROR);
|
||||
} else if (name !== oldName && !isNameValid(name, usedEntityNames)) {
|
||||
} else if (name !== entityName && !isNameValid(name, usedEntityNames)) {
|
||||
return createMessage(nameErrorMessage, name);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export const JSEntityItem = ({ item }: { item: EntityItemProps }) => {
|
|||
onEditComplete: exitEditMode,
|
||||
onNameSave: (newName: string) =>
|
||||
dispatch(saveJSObjectNameBasedOnIdeType(jsAction.id, newName, ideType)),
|
||||
validateName: (newName: string) => validateName(newName, item.title),
|
||||
validateName: (newName: string) => validateName(newName),
|
||||
};
|
||||
}, [
|
||||
canManageJSAction,
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export const QueryEntityItem = ({ item }: { item: EntityItemProps }) => {
|
|||
onEditComplete: exitEditMode,
|
||||
onNameSave: (newName: string) =>
|
||||
dispatch(saveActionNameBasedOnIdeType(action.id, newName, ideType)),
|
||||
validateName: (newName: string) => validateName(newName, item.title),
|
||||
validateName: (newName: string) => validateName(newName),
|
||||
};
|
||||
}, [
|
||||
canManageAction,
|
||||
|
|
|
|||
|
|
@ -1,73 +1,29 @@
|
|||
import React, { useCallback } from "react";
|
||||
import React from "react";
|
||||
import { EntityListTree } from "@appsmith/ads";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSelector } from "react-redux";
|
||||
import { selectWidgetsForCurrentPage } from "ee/selectors/entitiesSelector";
|
||||
import { getSelectedWidgets } from "selectors/ui";
|
||||
import { getPagePermissions } from "selectors/editorSelectors";
|
||||
import { getHasManagePagePermission } from "ee/utils/BusinessFeatures/permissionPageHelpers";
|
||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||
import { useValidateEntityName } from "IDE";
|
||||
import { updateWidgetName } from "actions/propertyPaneActions";
|
||||
import { WidgetContextMenu } from "./WidgetContextMenu";
|
||||
import { useSwitchToWidget } from "./hooks/useSwitchToWidget";
|
||||
import { WidgetTypeIcon } from "./WidgetTypeIcon";
|
||||
import { useWidgetTreeState } from "./hooks/useWidgetTreeExpandedState";
|
||||
import { enhanceItemsTree } from "./utils/enhanceTree";
|
||||
import { useNameEditorState } from "IDE/hooks/useNameEditorState";
|
||||
import { WidgetTreeItem } from "./WidgetTreeItem";
|
||||
|
||||
export const UIEntityListTree = () => {
|
||||
const widgets = useSelector(selectWidgetsForCurrentPage);
|
||||
const selectedWidgets = useSelector(getSelectedWidgets);
|
||||
|
||||
const switchToWidget = useSwitchToWidget();
|
||||
|
||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||
const pagePermissions = useSelector(getPagePermissions);
|
||||
const canManagePages = getHasManagePagePermission(
|
||||
isFeatureEnabled,
|
||||
pagePermissions,
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleNameSave = useCallback(
|
||||
(id: string, newName: string) => {
|
||||
dispatch(updateWidgetName(id, newName));
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const { editingEntity, enterEditMode, exitEditMode, updatingEntity } =
|
||||
useNameEditorState();
|
||||
|
||||
const validateName = useValidateEntityName({});
|
||||
|
||||
const { expandedWidgets, handleExpand } = useWidgetTreeState();
|
||||
|
||||
const items = enhanceItemsTree(widgets?.children || [], (widget) => ({
|
||||
id: widget.widgetId,
|
||||
title: widget.widgetName,
|
||||
startIcon: <WidgetTypeIcon type={widget.type} />,
|
||||
isSelected: selectedWidgets.includes(widget.widgetId),
|
||||
isExpanded: expandedWidgets.includes(widget.widgetId),
|
||||
onClick: (e) => switchToWidget(e, widget),
|
||||
onDoubleClick: () => enterEditMode(widget.widgetId),
|
||||
rightControl: (
|
||||
<WidgetContextMenu
|
||||
canManagePages={canManagePages}
|
||||
widgetId={widget.widgetId}
|
||||
/>
|
||||
),
|
||||
rightControlVisibility: "hover",
|
||||
nameEditorConfig: {
|
||||
canEdit: canManagePages,
|
||||
isLoading: updatingEntity === widget.widgetId,
|
||||
isEditing: editingEntity === widget.widgetId,
|
||||
onNameSave: (newName) => handleNameSave(widget.widgetId, newName),
|
||||
onEditComplete: exitEditMode,
|
||||
validateName: (newName) => validateName(newName, widget.widgetName),
|
||||
},
|
||||
}));
|
||||
|
||||
return <EntityListTree items={items} onItemExpand={handleExpand} />;
|
||||
return (
|
||||
<EntityListTree
|
||||
ItemComponent={WidgetTreeItem}
|
||||
items={items}
|
||||
onItemExpand={handleExpand}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
import React, { useCallback, useMemo } from "react";
|
||||
import { type EntityListTreeItem, EntityItem } from "@appsmith/ads";
|
||||
import { WidgetContextMenu } from "./WidgetContextMenu";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { getWidgetByID } from "sagas/selectors";
|
||||
import { updateWidgetName } from "actions/propertyPaneActions";
|
||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||
import { getHasManagePagePermission } from "ee/utils/BusinessFeatures/permissionPageHelpers";
|
||||
import { useValidateEntityName } from "IDE";
|
||||
import { useNameEditorState } from "IDE/hooks/useNameEditorState";
|
||||
import { getPagePermissions } from "selectors/editorSelectors";
|
||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
import { useSwitchToWidget } from "./hooks/useSwitchToWidget";
|
||||
import { WidgetTypeIcon } from "./WidgetTypeIcon";
|
||||
|
||||
export const WidgetTreeItem = ({ item }: { item: EntityListTreeItem }) => {
|
||||
const widget = useSelector(getWidgetByID(item.id));
|
||||
const switchToWidget = useSwitchToWidget();
|
||||
|
||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||
const pagePermissions = useSelector(getPagePermissions);
|
||||
const canManagePages = getHasManagePagePermission(
|
||||
isFeatureEnabled,
|
||||
pagePermissions,
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleNameSave = useCallback(
|
||||
(id: string, newName: string) => {
|
||||
dispatch(updateWidgetName(id, newName));
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const { editingEntity, enterEditMode, exitEditMode, updatingEntity } =
|
||||
useNameEditorState();
|
||||
|
||||
const validateName = useValidateEntityName({
|
||||
entityName: widget.widgetName,
|
||||
entityId: widget.widgetId,
|
||||
});
|
||||
|
||||
const isLoading = updatingEntity === widget.widgetId;
|
||||
const isEditing = editingEntity === widget.widgetId;
|
||||
const onNameSave = useCallback(
|
||||
(newName: string) => handleNameSave(widget.widgetId, newName),
|
||||
[handleNameSave, widget.widgetId],
|
||||
);
|
||||
|
||||
const nameEditorConfig = useMemo(
|
||||
() => ({
|
||||
canEdit: canManagePages,
|
||||
isLoading,
|
||||
isEditing,
|
||||
onNameSave,
|
||||
onEditComplete: exitEditMode,
|
||||
validateName,
|
||||
}),
|
||||
[
|
||||
canManagePages,
|
||||
exitEditMode,
|
||||
isEditing,
|
||||
isLoading,
|
||||
onNameSave,
|
||||
validateName,
|
||||
],
|
||||
);
|
||||
|
||||
const startIcon = useMemo(
|
||||
() => <WidgetTypeIcon type={widget.type} />,
|
||||
[widget.type],
|
||||
);
|
||||
|
||||
const onClick = useCallback(
|
||||
(e: React.MouseEvent) => switchToWidget(e, widget),
|
||||
[switchToWidget, widget],
|
||||
);
|
||||
|
||||
const onDoubleClick = useCallback(
|
||||
() => enterEditMode(widget.widgetId),
|
||||
[enterEditMode, widget.widgetId],
|
||||
);
|
||||
|
||||
const rightControl = useMemo(
|
||||
() => (
|
||||
<WidgetContextMenu
|
||||
canManagePages={canManagePages}
|
||||
widgetId={widget.widgetId}
|
||||
/>
|
||||
),
|
||||
[canManagePages, widget.widgetId],
|
||||
);
|
||||
|
||||
return (
|
||||
<EntityItem
|
||||
id={item.id}
|
||||
isSelected={item.isSelected}
|
||||
nameEditorConfig={nameEditorConfig}
|
||||
onClick={onClick}
|
||||
onDoubleClick={onDoubleClick}
|
||||
rightControl={rightControl}
|
||||
rightControlVisibility="hover"
|
||||
startIcon={startIcon}
|
||||
title={widget.widgetName}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { useCallback, type MouseEvent } from "react";
|
||||
import type { CanvasStructure } from "reducers/uiReducers/pageCanvasStructureReducer";
|
||||
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
|
||||
import { builderURL } from "ee/RouteBuilder";
|
||||
import { NavigationMethod } from "utils/history";
|
||||
|
|
@ -7,6 +6,7 @@ import { useNavigateToWidget } from "pages/Editor/Explorer/Widgets/useNavigateTo
|
|||
import { useSelector } from "react-redux";
|
||||
import { getCurrentBasePageId } from "selectors/editorSelectors";
|
||||
import { getSelectedWidgets } from "selectors/ui";
|
||||
import type { WidgetType } from "constants/WidgetConstants";
|
||||
|
||||
export function useSwitchToWidget() {
|
||||
const { navigateToWidget } = useNavigateToWidget();
|
||||
|
|
@ -14,7 +14,10 @@ export function useSwitchToWidget() {
|
|||
const selectedWidgets = useSelector(getSelectedWidgets);
|
||||
|
||||
return useCallback(
|
||||
(e: MouseEvent, widget: CanvasStructure) => {
|
||||
(
|
||||
e: MouseEvent,
|
||||
widget: { widgetId: string; widgetName: string; type: WidgetType },
|
||||
) => {
|
||||
const isMultiSelect = e.metaKey || e.ctrlKey;
|
||||
const isShiftSelect = e.shiftKey;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user