chore: Replace entities in entity explorer with new ADS templates (#38750)
## Description Replace entities in entity explorer with new ADS templates Fixes [#38286](https://github.com/appsmithorg/appsmith/issues/38286) [#39289](https://github.com/appsmithorg/appsmith/issues/38289) ## Automation /ok-to-test tags="@tag.All" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/12945261277> > Commit: bf508e2291441102ab2260f39118e269022650b3 > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12945261277&attempt=2" target="_blank">Cypress dashboard</a>. > Tags: `@tag.All` > Spec: > <hr>Fri, 24 Jan 2025 08:55:33 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit ## Release Notes - **New Features** - Added new context menu components for JavaScript and Query entities. - Introduced enhanced entity item rendering for JavaScript and Query collections. - Implemented more granular permission-based context menu actions. - Added new components: `Rename`, `Copy`, `Move`, `Delete`, `ShowBindings`, `Prettify`, and `EntityContextMenu`. - **Improvements** - Streamlined context menu functionality across editor interfaces. - Enhanced user permissions handling for entity actions. - Improved modularity of editor components. - Updated rendering logic for JavaScript and Query lists based on feature flags. - **Bug Fixes** - Refined component prop management. - Updated navigation and analytics event logging for entity interactions. - **Refactoring** - Simplified component structures. - Removed deprecated prop usage. - Consolidated import and export statements. These changes primarily focus on improving the user experience and developer flexibility within the application's editor interfaces. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
eb72ece5e8
commit
8f5aad96df
|
|
@ -2,6 +2,7 @@ import React, { useCallback } from "react";
|
||||||
import { MenuItem } from "@appsmith/ads";
|
import { MenuItem } from "@appsmith/ads";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { setRenameEntity } from "actions/ideActions";
|
import { setRenameEntity } from "actions/ideActions";
|
||||||
|
import { CONTEXT_RENAME, createMessage } from "ee/constants/messages";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
|
@ -24,7 +25,7 @@ export const RenameMenuItem = ({ disabled, entityId }: Props) => {
|
||||||
onSelect={setRename}
|
onSelect={setRename}
|
||||||
startIcon="input-cursor-move"
|
startIcon="input-cursor-move"
|
||||||
>
|
>
|
||||||
Rename
|
{createMessage(CONTEXT_RENAME)}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ExplorerJSCollectionEntity from "pages/Editor/Explorer/JSActions/JSActionEntity";
|
|
||||||
import { Flex } from "@appsmith/ads";
|
|
||||||
import type { EntityItem } from "ee/entities/IDE/constants";
|
import type { EntityItem } from "ee/entities/IDE/constants";
|
||||||
|
import { JSEntityItem } from "pages/Editor/IDE/EditorPane/JS/EntityItem/JSEntityItem";
|
||||||
|
|
||||||
export interface JSListItemProps {
|
export const JSEntity = (props: { item: EntityItem }) => {
|
||||||
item: EntityItem;
|
return <JSEntityItem {...props} />;
|
||||||
isActive: boolean;
|
|
||||||
parentEntityId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const JSListItem = (props: JSListItemProps) => {
|
|
||||||
const { isActive, item, parentEntityId } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex data-testid="t--ide-list-item" flexDirection={"column"}>
|
|
||||||
<ExplorerJSCollectionEntity
|
|
||||||
baseCollectionId={item.key}
|
|
||||||
isActive={isActive}
|
|
||||||
key={item.key}
|
|
||||||
parentEntityId={parentEntityId}
|
|
||||||
searchKeyword={""}
|
|
||||||
step={1}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from "react";
|
||||||
|
import ExplorerJSCollectionEntity from "pages/Editor/Explorer/JSActions/JSActionEntity";
|
||||||
|
import { Flex } from "@appsmith/ads";
|
||||||
|
import type { EntityItem } from "ee/entities/IDE/constants";
|
||||||
|
|
||||||
|
export interface JSListItemProps {
|
||||||
|
item: EntityItem;
|
||||||
|
isActive: boolean;
|
||||||
|
parentEntityId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JSListItem = (props: JSListItemProps) => {
|
||||||
|
const { isActive, item, parentEntityId } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex data-testid="t--ide-list-item" flexDirection={"column"}>
|
||||||
|
<ExplorerJSCollectionEntity
|
||||||
|
baseCollectionId={item.key}
|
||||||
|
isActive={isActive}
|
||||||
|
key={item.key}
|
||||||
|
parentEntityId={parentEntityId}
|
||||||
|
searchKeyword={""}
|
||||||
|
step={1}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from "react";
|
||||||
|
import { IDE_TYPE, type IDEType } from "ee/entities/IDE/constants";
|
||||||
|
import EntityContextMenu from "pages/Editor/IDE/EditorPane/components/EntityContextMenu";
|
||||||
|
import { AppJSContextMenuItems } from "pages/Editor/IDE/EditorPane/JS/EntityItem/AppJSContextMenuItems";
|
||||||
|
import type { JSCollection } from "entities/JSCollection";
|
||||||
|
|
||||||
|
export const getJSContextMenuByIdeType = (
|
||||||
|
ideType: IDEType,
|
||||||
|
jsAction: JSCollection,
|
||||||
|
) => {
|
||||||
|
switch (ideType) {
|
||||||
|
case IDE_TYPE.App: {
|
||||||
|
return (
|
||||||
|
<EntityContextMenu>
|
||||||
|
<AppJSContextMenuItems jsAction={jsAction} />
|
||||||
|
</EntityContextMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,25 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ExplorerActionEntity from "pages/Editor/Explorer/Actions/ActionEntity";
|
import { QueryEntityItem } from "pages/Editor/IDE/EditorPane/Query/EntityItem/QueryEntityItem";
|
||||||
import type { EntityItem } from "ee/entities/IDE/constants";
|
import type { EntityItem } from "ee/entities/IDE/constants";
|
||||||
|
|
||||||
export interface QueryListItemProps {
|
export const ActionEntityItem = (props: { item: EntityItem }) => {
|
||||||
item: EntityItem;
|
return <QueryEntityItem {...props} />;
|
||||||
isActive: boolean;
|
|
||||||
parentEntityId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const QueryListItem = (props: QueryListItemProps) => {
|
|
||||||
const { isActive, item, parentEntityId } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ExplorerActionEntity
|
|
||||||
baseId={item.key}
|
|
||||||
isActive={isActive}
|
|
||||||
key={item.key}
|
|
||||||
parentEntityId={parentEntityId}
|
|
||||||
searchKeyword={""}
|
|
||||||
step={1}
|
|
||||||
type={item.type}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from "react";
|
||||||
|
import ExplorerActionEntity from "pages/Editor/Explorer/Actions/ActionEntity";
|
||||||
|
import type { EntityItem } from "ee/entities/IDE/constants";
|
||||||
|
|
||||||
|
export interface QueryListItemProps {
|
||||||
|
item: EntityItem;
|
||||||
|
isActive: boolean;
|
||||||
|
parentEntityId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const QueryListItem = (props: QueryListItemProps) => {
|
||||||
|
const { isActive, item, parentEntityId } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExplorerActionEntity
|
||||||
|
baseId={item.key}
|
||||||
|
isActive={isActive}
|
||||||
|
key={item.key}
|
||||||
|
parentEntityId={parentEntityId}
|
||||||
|
searchKeyword={""}
|
||||||
|
step={1}
|
||||||
|
type={item.type}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from "react";
|
||||||
|
import { IDE_TYPE, type IDEType } from "ee/entities/IDE/constants";
|
||||||
|
import type { Action } from "entities/Action";
|
||||||
|
import { AppQueryContextMenuItems } from "pages/Editor/IDE/EditorPane/Query/EntityItem/AppQueryContextMenuItems";
|
||||||
|
import EntityContextMenu from "pages/Editor/IDE/EditorPane/components/EntityContextMenu";
|
||||||
|
|
||||||
|
export const getQueryContextMenuByIdeType = (
|
||||||
|
ideType: IDEType,
|
||||||
|
action: Action,
|
||||||
|
) => {
|
||||||
|
switch (ideType) {
|
||||||
|
case IDE_TYPE.App: {
|
||||||
|
return (
|
||||||
|
<EntityContextMenu>
|
||||||
|
<AppQueryContextMenuItems action={action} />
|
||||||
|
</EntityContextMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "ce/pages/Editor/IDE/EditorPane/JS/old/ListItem";
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "ce/pages/Editor/IDE/EditorPane/JS/utils/getJSContextMenuByIdeType";
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "ce/pages/Editor/IDE/EditorPane/Query/old/ListItem";
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "ce/pages/Editor/IDE/EditorPane/Query/utils/getQueryContextMenuByIdeType";
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { usePluginActionContext } from "PluginActionEditor";
|
|
||||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
|
@ -11,21 +10,27 @@ import {
|
||||||
import { MODULE_TYPE } from "ee/constants/ModuleConstants";
|
import { MODULE_TYPE } from "ee/constants/ModuleConstants";
|
||||||
import ConvertToModuleInstanceCTA from "ee/pages/Editor/EntityEditor/ConvertToModuleInstanceCTA";
|
import ConvertToModuleInstanceCTA from "ee/pages/Editor/EntityEditor/ConvertToModuleInstanceCTA";
|
||||||
import { PluginType } from "entities/Plugin";
|
import { PluginType } from "entities/Plugin";
|
||||||
|
import type { Action } from "entities/Action";
|
||||||
|
|
||||||
const ConvertToModuleCTA = () => {
|
interface Props {
|
||||||
const { action, plugin } = usePluginActionContext();
|
action: Action;
|
||||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
}
|
||||||
|
|
||||||
|
export const ConvertToModule = ({ action }: Props) => {
|
||||||
const pagePermissions = useSelector(getPagePermissions);
|
const pagePermissions = useSelector(getPagePermissions);
|
||||||
|
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||||
|
|
||||||
const isCreatePermitted = getHasCreateActionPermission(
|
const isCreatePermitted = getHasCreateActionPermission(
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
pagePermissions,
|
pagePermissions,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isDeletePermitted = getHasDeleteActionPermission(
|
const isDeletePermitted = getHasDeleteActionPermission(
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
action.userPermissions,
|
action.userPermissions,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (plugin.type === PluginType.INTERNAL) {
|
if (action.pluginType === PluginType.INTERNAL) {
|
||||||
// Workflow queries cannot be converted to modules
|
// Workflow queries cannot be converted to modules
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -39,5 +44,3 @@ const ConvertToModuleCTA = () => {
|
||||||
|
|
||||||
return <ConvertToModuleInstanceCTA {...convertToModuleProps} />;
|
return <ConvertToModuleInstanceCTA {...convertToModuleProps} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ConvertToModuleCTA;
|
|
||||||
|
|
@ -1,19 +1,20 @@
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import React from "react";
|
||||||
import { getPageList } from "ee/selectors/entitiesSelector";
|
|
||||||
import { usePluginActionContext } from "PluginActionEditor";
|
|
||||||
import React, { useCallback } from "react";
|
|
||||||
import { copyActionRequest } from "actions/pluginActionActions";
|
|
||||||
import { MenuSub, MenuSubContent, MenuSubTrigger } from "@appsmith/ads";
|
import { MenuSub, MenuSubContent, MenuSubTrigger } from "@appsmith/ads";
|
||||||
import { CONTEXT_COPY, createMessage } from "ee/constants/messages";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { getPageList } from "selectors/editorSelectors";
|
||||||
import { PageMenuItem } from "./PageMenuItem";
|
import { PageMenuItem } from "./PageMenuItem";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import type { Action } from "entities/Action";
|
||||||
|
import { copyActionRequest } from "actions/pluginActionActions";
|
||||||
|
import { CONTEXT_COPY, createMessage } from "ee/constants/messages";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
action: Action;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Copy = ({ disabled }: Props) => {
|
export const Copy = ({ action, disabled }: Props) => {
|
||||||
const menuPages = useSelector(getPageList);
|
const menuPages = useSelector(getPageList);
|
||||||
const { action } = usePluginActionContext();
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const copyActionToPage = useCallback(
|
const copyActionToPage = useCallback(
|
||||||
|
|
@ -30,13 +31,14 @@ export const Copy = ({ disabled }: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuSub>
|
<MenuSub>
|
||||||
<MenuSubTrigger disabled={disabled} startIcon="duplicate">
|
<MenuSubTrigger startIcon="duplicate">
|
||||||
{createMessage(CONTEXT_COPY)}
|
{createMessage(CONTEXT_COPY)}
|
||||||
</MenuSubTrigger>
|
</MenuSubTrigger>
|
||||||
<MenuSubContent>
|
<MenuSubContent style={{ maxHeight: "350px" }} width="220px">
|
||||||
{menuPages.map((page) => {
|
{menuPages.map((page) => {
|
||||||
return (
|
return (
|
||||||
<PageMenuItem
|
<PageMenuItem
|
||||||
|
disabled={disabled}
|
||||||
key={page.basePageId}
|
key={page.basePageId}
|
||||||
onSelect={copyActionToPage}
|
onSelect={copyActionToPage}
|
||||||
page={page}
|
page={page}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { useHandleDeleteClick } from "PluginActionEditor/hooks";
|
|
||||||
import React, { useCallback, useState } from "react";
|
import React, { useCallback, useState } from "react";
|
||||||
import {
|
import {
|
||||||
CONFIRM_CONTEXT_DELETE,
|
CONFIRM_CONTEXT_DELETE,
|
||||||
|
|
@ -6,19 +5,37 @@ import {
|
||||||
createMessage,
|
createMessage,
|
||||||
} from "ee/constants/messages";
|
} from "ee/constants/messages";
|
||||||
import { MenuItem } from "@appsmith/ads";
|
import { MenuItem } from "@appsmith/ads";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { deleteAction } from "actions/pluginActionActions";
|
||||||
|
import type { Action } from "entities/Action";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
action: Action;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Delete = ({ disabled }: Props) => {
|
export const Delete = ({ action, disabled }: Props) => {
|
||||||
const { handleDeleteClick } = useHandleDeleteClick();
|
const dispatch = useDispatch();
|
||||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
|
|
||||||
|
const handleDeleteClick = useCallback(
|
||||||
|
({ onSuccess }: { onSuccess?: () => void }) => {
|
||||||
|
dispatch(
|
||||||
|
deleteAction({
|
||||||
|
id: action?.id ?? "",
|
||||||
|
name: action?.name ?? "",
|
||||||
|
onSuccess,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[action.id, action.name, dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
(e?: Event) => {
|
(e?: Event) => {
|
||||||
e?.preventDefault();
|
e?.preventDefault();
|
||||||
confirmDelete ? handleDeleteClick({}) : setConfirmDelete(true);
|
confirmDelete ? handleDeleteClick({}) : setConfirmDelete(true);
|
||||||
|
e?.stopPropagation();
|
||||||
},
|
},
|
||||||
[confirmDelete, handleDeleteClick],
|
[confirmDelete, handleDeleteClick],
|
||||||
);
|
);
|
||||||
|
|
@ -29,7 +46,7 @@ export const Delete = ({ disabled }: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className="t--apiFormDeleteBtn error-menuitem"
|
className="t--apiFormDeleteBtn single-select error-menuitem"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
startIcon="trash"
|
startIcon="trash"
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { usePluginActionContext } from "PluginActionEditor";
|
|
||||||
import { getCurrentPageId } from "selectors/editorSelectors";
|
import { getCurrentPageId } from "selectors/editorSelectors";
|
||||||
import { getPageList } from "ee/selectors/entitiesSelector";
|
import { getPageList } from "ee/selectors/entitiesSelector";
|
||||||
import React, { useCallback, useMemo } from "react";
|
import React, { useCallback, useMemo } from "react";
|
||||||
|
|
@ -12,14 +11,15 @@ import {
|
||||||
} from "@appsmith/ads";
|
} from "@appsmith/ads";
|
||||||
import { CONTEXT_MOVE, createMessage } from "ee/constants/messages";
|
import { CONTEXT_MOVE, createMessage } from "ee/constants/messages";
|
||||||
import { PageMenuItem } from "./PageMenuItem";
|
import { PageMenuItem } from "./PageMenuItem";
|
||||||
|
import type { Action } from "entities/Action";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
action: Action;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Move = ({ disabled }: Props) => {
|
export const Move = ({ action, disabled }: Props) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { action } = usePluginActionContext();
|
|
||||||
|
|
||||||
const currentPageId = useSelector(getCurrentPageId);
|
const currentPageId = useSelector(getCurrentPageId);
|
||||||
const allPages = useSelector(getPageList);
|
const allPages = useSelector(getPageList);
|
||||||
|
|
@ -42,14 +42,15 @@ export const Move = ({ disabled }: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuSub>
|
<MenuSub>
|
||||||
<MenuSubTrigger disabled={disabled} startIcon="swap-horizontal">
|
<MenuSubTrigger startIcon="swap-horizontal">
|
||||||
{createMessage(CONTEXT_MOVE)}
|
{createMessage(CONTEXT_MOVE)}
|
||||||
</MenuSubTrigger>
|
</MenuSubTrigger>
|
||||||
<MenuSubContent>
|
<MenuSubContent style={{ maxHeight: "350px" }} width="220px">
|
||||||
{menuPages.length ? (
|
{menuPages.length ? (
|
||||||
menuPages.map((page) => {
|
menuPages.map((page) => {
|
||||||
return (
|
return (
|
||||||
<PageMenuItem
|
<PageMenuItem
|
||||||
|
disabled={disabled}
|
||||||
key={page.basePageId}
|
key={page.basePageId}
|
||||||
onSelect={moveActionToPage}
|
onSelect={moveActionToPage}
|
||||||
page={page}
|
page={page}
|
||||||
|
|
@ -5,10 +5,15 @@ import { MenuItem } from "@appsmith/ads";
|
||||||
export const PageMenuItem = (props: {
|
export const PageMenuItem = (props: {
|
||||||
page: Page;
|
page: Page;
|
||||||
onSelect: (id: string) => void;
|
onSelect: (id: string) => void;
|
||||||
|
disabled?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const handleOnSelect = useCallback(() => {
|
const handleOnSelect = useCallback(() => {
|
||||||
props.onSelect(props.page.pageId);
|
props.onSelect(props.page.pageId);
|
||||||
}, [props]);
|
}, [props]);
|
||||||
|
|
||||||
return <MenuItem onSelect={handleOnSelect}>{props.page.pageName}</MenuItem>;
|
return (
|
||||||
|
<MenuItem disabled={props.disabled} onSelect={handleOnSelect}>
|
||||||
|
{props.page.pageName}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { MenuItem } from "@appsmith/ads";
|
||||||
|
import type { Action } from "entities/Action";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { initExplorerEntityNameEdit } from "actions/explorerActions";
|
||||||
|
import { CONTEXT_RENAME, createMessage } from "ee/constants/messages";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
action: Action;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Rename = ({ action, disabled }: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const setRename = useCallback(() => {
|
||||||
|
// We add a delay to avoid having the focus stuck in the menu trigger
|
||||||
|
setTimeout(() => {
|
||||||
|
dispatch(initExplorerEntityNameEdit(action.id));
|
||||||
|
}, 100);
|
||||||
|
}, [dispatch, action.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
disabled={disabled}
|
||||||
|
onSelect={setRename}
|
||||||
|
startIcon="input-cursor-move"
|
||||||
|
>
|
||||||
|
{createMessage(CONTEXT_RENAME)}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { CONTEXT_SHOW_BINDING, createMessage } from "ee/constants/messages";
|
||||||
|
import { MenuItem } from "@appsmith/ads";
|
||||||
|
import type { Action } from "entities/Action";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
|
||||||
|
import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
action: Action;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShowBindings = ({ action, disabled }: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleSelect = useCallback(() => {
|
||||||
|
dispatch({
|
||||||
|
type: ReduxActionTypes.SET_ENTITY_INFO,
|
||||||
|
payload: {
|
||||||
|
entityId: action.id,
|
||||||
|
entityName: action.name,
|
||||||
|
entityType: ENTITY_TYPE.ACTION,
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [action.id, action.name]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
disabled={disabled}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
startIcon="binding-new"
|
||||||
|
>
|
||||||
|
{createMessage(CONTEXT_SHOW_BINDING)}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export { Copy } from "./Copy";
|
||||||
|
export { Move } from "./Move";
|
||||||
|
export { Delete } from "./Delete";
|
||||||
|
export { Rename } from "./Rename";
|
||||||
|
export { ShowBindings } from "./ShowBindings";
|
||||||
|
export { ConvertToModule } from "./ConvertToModule";
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
export { default as ConvertToModuleCTA } from "./ConvertToModuleCTA";
|
|
||||||
export { default as ConvertToModuleDisabler } from "./ConvertToModuleDisabler";
|
export { default as ConvertToModuleDisabler } from "./ConvertToModuleDisabler";
|
||||||
export { default as ConvertToModuleCallout } from "./ConvertToModuleCallout";
|
export { default as ConvertToModuleCallout } from "./ConvertToModuleCallout";
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { MenuItem } from "@appsmith/ads";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Rename = ({ disabled }: Props) => {
|
|
||||||
return (
|
|
||||||
<MenuItem disabled={disabled} startIcon="input-cursor-move">
|
|
||||||
Rename
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -10,10 +10,7 @@ import {
|
||||||
usePluginActionContext,
|
usePluginActionContext,
|
||||||
DocsMenuItem as Docs,
|
DocsMenuItem as Docs,
|
||||||
} from "PluginActionEditor";
|
} from "PluginActionEditor";
|
||||||
import { ConvertToModuleCTA } from "../ConvertToModule";
|
import { ConvertToModule, Copy, Delete, Move } from "../ContextMenuItems";
|
||||||
import { Move } from "./Move";
|
|
||||||
import { Copy } from "./Copy";
|
|
||||||
import { Delete } from "./Delete";
|
|
||||||
import { RenameMenuItem } from "IDE";
|
import { RenameMenuItem } from "IDE";
|
||||||
|
|
||||||
export const ToolbarMenu = () => {
|
export const ToolbarMenu = () => {
|
||||||
|
|
@ -32,12 +29,12 @@ export const ToolbarMenu = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RenameMenuItem disabled={!isChangePermitted} entityId={action.id} />
|
<RenameMenuItem disabled={!isChangePermitted} entityId={action.id} />
|
||||||
<ConvertToModuleCTA />
|
<ConvertToModule action={action} />
|
||||||
<Copy disabled={!isChangePermitted} />
|
<Copy action={action} disabled={!isChangePermitted} />
|
||||||
<Move disabled={!isChangePermitted} />
|
<Move action={action} disabled={!isChangePermitted} />
|
||||||
<Docs />
|
<Docs />
|
||||||
<MenuSeparator />
|
<MenuSeparator />
|
||||||
<Delete disabled={!isDeletePermitted} />
|
<Delete action={action} disabled={!isDeletePermitted} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ function Files() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[files, activeActionBaseId, parentEntityId, parentEntityType],
|
[files, activeActionBaseId, parentEntityId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||||
|
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||||
|
import {
|
||||||
|
getHasDeleteActionPermission,
|
||||||
|
getHasManageActionPermission,
|
||||||
|
} from "ee/utils/BusinessFeatures/permissionPageHelpers";
|
||||||
|
import type { JSCollection } from "entities/JSCollection";
|
||||||
|
import {
|
||||||
|
Copy,
|
||||||
|
Delete,
|
||||||
|
Move,
|
||||||
|
Rename,
|
||||||
|
ShowBindings,
|
||||||
|
} from "pages/Editor/JSEditor/ContextMenuItems";
|
||||||
|
import { MenuSeparator } from "@appsmith/ads";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
jsAction: JSCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppJSContextMenuItems(props: Props) {
|
||||||
|
const { jsAction } = props;
|
||||||
|
const jsActionPermissions = jsAction.userPermissions || [];
|
||||||
|
|
||||||
|
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||||
|
|
||||||
|
const canDeleteJSAction = getHasDeleteActionPermission(
|
||||||
|
isFeatureEnabled,
|
||||||
|
jsActionPermissions,
|
||||||
|
);
|
||||||
|
|
||||||
|
const canManageJSAction = getHasManageActionPermission(
|
||||||
|
isFeatureEnabled,
|
||||||
|
jsActionPermissions,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Rename disabled={!canManageJSAction} jsAction={jsAction} />
|
||||||
|
<ShowBindings jsAction={jsAction} />
|
||||||
|
<Copy disabled={!canManageJSAction} jsAction={jsAction} />
|
||||||
|
<Move disabled={!canManageJSAction} jsAction={jsAction} />
|
||||||
|
<MenuSeparator />
|
||||||
|
<Delete disabled={!canDeleteJSAction} jsAction={jsAction} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
import React, { useCallback, useMemo } from "react";
|
||||||
|
import { EntityItem } from "@appsmith/ads";
|
||||||
|
import type { EntityItem as EntityItemProps } from "ee/entities/IDE/constants";
|
||||||
|
import type { AppState } from "ee/reducers";
|
||||||
|
import { getJsCollectionByBaseId } from "ee/selectors/entitiesSelector";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||||
|
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||||
|
import { getHasManageActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers";
|
||||||
|
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
|
||||||
|
import history, { NavigationMethod } from "utils/history";
|
||||||
|
import { saveJSObjectNameBasedOnIdeType } from "ee/actions/helpers";
|
||||||
|
import { useNameEditorState } from "pages/Editor/IDE/EditorPane/hooks/useNameEditorState";
|
||||||
|
import { useValidateEntityName } from "IDE";
|
||||||
|
import { useLocation } from "react-router";
|
||||||
|
import { getIDETypeByUrl } from "ee/entities/IDE/utils";
|
||||||
|
import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks";
|
||||||
|
import { useParentEntityInfo } from "ee/IDE/hooks/useParentEntityInfo";
|
||||||
|
import type { JSCollection } from "entities/JSCollection";
|
||||||
|
import { jsCollectionIdURL } from "ee/RouteBuilder";
|
||||||
|
import { JsFileIconV2 } from "pages/Editor/Explorer/ExplorerIcons";
|
||||||
|
import { getJSContextMenuByIdeType } from "ee/pages/Editor/IDE/EditorPane/JS/utils/getJSContextMenuByIdeType";
|
||||||
|
|
||||||
|
export const JSEntityItem = ({ item }: { item: EntityItemProps }) => {
|
||||||
|
const jsAction = useSelector((state: AppState) =>
|
||||||
|
getJsCollectionByBaseId(state, item.key),
|
||||||
|
) as JSCollection;
|
||||||
|
const location = useLocation();
|
||||||
|
const ideType = getIDETypeByUrl(location.pathname);
|
||||||
|
const activeActionBaseId = useActiveActionBaseId();
|
||||||
|
const { parentEntityId } = useParentEntityInfo(ideType);
|
||||||
|
|
||||||
|
const { editingEntity, enterEditMode, exitEditMode, updatingEntity } =
|
||||||
|
useNameEditorState();
|
||||||
|
|
||||||
|
const validateName = useValidateEntityName({
|
||||||
|
entityName: item.title,
|
||||||
|
});
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const contextMenu = getJSContextMenuByIdeType(ideType, jsAction);
|
||||||
|
|
||||||
|
const jsActionPermissions = jsAction.userPermissions || [];
|
||||||
|
|
||||||
|
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||||
|
|
||||||
|
const canManageJSAction = getHasManageActionPermission(
|
||||||
|
isFeatureEnabled,
|
||||||
|
jsActionPermissions,
|
||||||
|
);
|
||||||
|
|
||||||
|
const navigateToUrl = jsCollectionIdURL({
|
||||||
|
baseParentEntityId: parentEntityId,
|
||||||
|
baseCollectionId: jsAction.baseId,
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const navigateToJSCollection = useCallback(() => {
|
||||||
|
if (jsAction.baseId) {
|
||||||
|
AnalyticsUtil.logEvent("ENTITY_EXPLORER_CLICK", {
|
||||||
|
type: "JSOBJECT",
|
||||||
|
fromUrl: location.pathname,
|
||||||
|
toUrl: navigateToUrl,
|
||||||
|
name: jsAction.name,
|
||||||
|
});
|
||||||
|
history.push(navigateToUrl, {
|
||||||
|
invokedBy: NavigationMethod.EntityExplorer,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [parentEntityId, jsAction.baseId, jsAction.name, location.pathname]);
|
||||||
|
|
||||||
|
const nameEditorConfig = useMemo(() => {
|
||||||
|
return {
|
||||||
|
canEdit: canManageJSAction && !Boolean(jsAction.isMainJSCollection),
|
||||||
|
isEditing: editingEntity === jsAction.id,
|
||||||
|
isLoading: updatingEntity === jsAction.id,
|
||||||
|
onEditComplete: exitEditMode,
|
||||||
|
onNameSave: (newName: string) =>
|
||||||
|
dispatch(saveJSObjectNameBasedOnIdeType(jsAction.id, newName, ideType)),
|
||||||
|
validateName: (newName: string) => validateName(newName, item.title),
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
canManageJSAction,
|
||||||
|
editingEntity,
|
||||||
|
exitEditMode,
|
||||||
|
ideType,
|
||||||
|
item.title,
|
||||||
|
jsAction.id,
|
||||||
|
jsAction.isMainJSCollection,
|
||||||
|
dispatch,
|
||||||
|
updatingEntity,
|
||||||
|
validateName,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EntityItem
|
||||||
|
className="t--jsaction"
|
||||||
|
id={jsAction.id}
|
||||||
|
isSelected={activeActionBaseId === jsAction.id}
|
||||||
|
key={jsAction.id}
|
||||||
|
nameEditorConfig={nameEditorConfig}
|
||||||
|
onClick={navigateToJSCollection}
|
||||||
|
onDoubleClick={() => enterEditMode(jsAction.id)}
|
||||||
|
rightControl={contextMenu}
|
||||||
|
rightControlVisibility="hover"
|
||||||
|
startIcon={JsFileIconV2(16, 16)}
|
||||||
|
title={item.title}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,25 +1,31 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { Flex, Text, SearchAndAdd, NoSearchResults } from "@appsmith/ads";
|
import {
|
||||||
|
Flex,
|
||||||
|
Text,
|
||||||
|
SearchAndAdd,
|
||||||
|
NoSearchResults,
|
||||||
|
EntityGroupsList,
|
||||||
|
} from "@appsmith/ads";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { selectJSSegmentEditorList } from "ee/selectors/appIDESelectors";
|
import { selectJSSegmentEditorList } from "ee/selectors/appIDESelectors";
|
||||||
import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks";
|
import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks";
|
||||||
import {
|
|
||||||
getCurrentApplicationId,
|
|
||||||
getCurrentPageId,
|
|
||||||
getPagePermissions,
|
|
||||||
} from "selectors/editorSelectors";
|
|
||||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||||
import { getHasCreateActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers";
|
|
||||||
import { ActionParentEntityType } from "ee/entities/Engine/actionHelpers";
|
import { ActionParentEntityType } from "ee/entities/Engine/actionHelpers";
|
||||||
import { FilesContextProvider } from "pages/Editor/Explorer/Files/FilesContextProvider";
|
import { FilesContextProvider } from "pages/Editor/Explorer/Files/FilesContextProvider";
|
||||||
import { useJSAdd } from "ee/pages/Editor/IDE/EditorPane/JS/hooks";
|
import { useJSAdd } from "ee/pages/Editor/IDE/EditorPane/JS/hooks";
|
||||||
import { JSListItem } from "ee/pages/Editor/IDE/EditorPane/JS/ListItem";
|
import { JSListItem } from "ee/pages/Editor/IDE/EditorPane/JS/old/ListItem";
|
||||||
import { BlankState } from "./BlankState";
|
import { BlankState } from "./BlankState";
|
||||||
import { EDITOR_PANE_TEXTS, createMessage } from "ee/constants/messages";
|
import { EDITOR_PANE_TEXTS, createMessage } from "ee/constants/messages";
|
||||||
import { filterEntityGroupsBySearchTerm } from "IDE/utils";
|
import { filterEntityGroupsBySearchTerm } from "IDE/utils";
|
||||||
|
import { useLocation } from "react-router";
|
||||||
|
import { getIDETypeByUrl } from "ee/entities/IDE/utils";
|
||||||
|
import { useParentEntityInfo } from "ee/IDE/hooks/useParentEntityInfo";
|
||||||
|
import { useCreateActionsPermissions } from "ee/entities/IDE/hooks/useCreateActionsPermissions";
|
||||||
|
import type { EntityItem } from "ee/entities/IDE/constants";
|
||||||
|
import { JSEntity } from "ee/pages/Editor/IDE/EditorPane/JS/ListItem";
|
||||||
|
|
||||||
const JSContainer = styled(Flex)`
|
const JSContainer = styled(Flex)`
|
||||||
& .t--entity-item {
|
& .t--entity-item {
|
||||||
|
|
@ -30,25 +36,23 @@ const JSContainer = styled(Flex)`
|
||||||
|
|
||||||
const ListJSObjects = () => {
|
const ListJSObjects = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const pageId = useSelector(getCurrentPageId);
|
|
||||||
const itemGroups = useSelector(selectJSSegmentEditorList);
|
const itemGroups = useSelector(selectJSSegmentEditorList);
|
||||||
const activeActionBaseId = useActiveActionBaseId();
|
const activeActionBaseId = useActiveActionBaseId();
|
||||||
const applicationId = useSelector(getCurrentApplicationId);
|
|
||||||
|
|
||||||
const pagePermissions = useSelector(getPagePermissions);
|
const location = useLocation();
|
||||||
|
const ideType = getIDETypeByUrl(location.pathname);
|
||||||
|
const { editorId, parentEntityId } = useParentEntityInfo(ideType);
|
||||||
|
const canCreateActions = useCreateActionsPermissions(ideType);
|
||||||
|
|
||||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
const isNewADSTemplatesEnabled = useFeatureFlag(
|
||||||
|
FEATURE_FLAG.release_ads_entity_item_enabled,
|
||||||
|
);
|
||||||
|
|
||||||
const filteredItemGroups = filterEntityGroupsBySearchTerm(
|
const filteredItemGroups = filterEntityGroupsBySearchTerm(
|
||||||
searchTerm,
|
searchTerm,
|
||||||
itemGroups,
|
itemGroups,
|
||||||
);
|
);
|
||||||
|
|
||||||
const canCreateActions = getHasCreateActionPermission(
|
|
||||||
isFeatureEnabled,
|
|
||||||
pagePermissions,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { openAddJS } = useJSAdd();
|
const { openAddJS } = useJSAdd();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -70,14 +74,27 @@ const ListJSObjects = () => {
|
||||||
showAddButton={canCreateActions}
|
showAddButton={canCreateActions}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
data-testid="t--ide-list"
|
data-testid="t--ide-list"
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
gap="spaces-4"
|
gap="spaces-4"
|
||||||
overflowY="auto"
|
overflowY="auto"
|
||||||
>
|
>
|
||||||
{filteredItemGroups.map(({ group, items }) => {
|
{isNewADSTemplatesEnabled ? (
|
||||||
|
<EntityGroupsList
|
||||||
|
groups={filteredItemGroups.map(({ group, items }) => {
|
||||||
|
return {
|
||||||
|
groupTitle: group === "NA" ? "" : group,
|
||||||
|
items: items,
|
||||||
|
className: "",
|
||||||
|
renderList: (item: EntityItem) => {
|
||||||
|
return <JSEntity item={item} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
filteredItemGroups.map(({ group, items }) => {
|
||||||
return (
|
return (
|
||||||
<Flex flexDirection={"column"} key={group}>
|
<Flex flexDirection={"column"} key={group}>
|
||||||
{group !== "NA" ? (
|
{group !== "NA" ? (
|
||||||
|
|
@ -92,8 +109,8 @@ const ListJSObjects = () => {
|
||||||
) : null}
|
) : null}
|
||||||
<FilesContextProvider
|
<FilesContextProvider
|
||||||
canCreateActions={canCreateActions}
|
canCreateActions={canCreateActions}
|
||||||
editorId={applicationId}
|
editorId={editorId}
|
||||||
parentEntityId={pageId}
|
parentEntityId={parentEntityId}
|
||||||
parentEntityType={ActionParentEntityType.PAGE}
|
parentEntityType={ActionParentEntityType.PAGE}
|
||||||
>
|
>
|
||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
|
|
@ -102,14 +119,15 @@ const ListJSObjects = () => {
|
||||||
isActive={item.key === activeActionBaseId}
|
isActive={item.key === activeActionBaseId}
|
||||||
item={item}
|
item={item}
|
||||||
key={item.key}
|
key={item.key}
|
||||||
parentEntityId={pageId}
|
parentEntityId={parentEntityId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</FilesContextProvider>
|
</FilesContextProvider>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
})}
|
})
|
||||||
|
)}
|
||||||
{filteredItemGroups.length === 0 && searchTerm !== "" ? (
|
{filteredItemGroups.length === 0 && searchTerm !== "" ? (
|
||||||
<NoSearchResults
|
<NoSearchResults
|
||||||
text={createMessage(
|
text={createMessage(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from "react";
|
||||||
|
import type { Action } from "entities/Action";
|
||||||
|
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||||
|
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||||
|
import {
|
||||||
|
getHasDeleteActionPermission,
|
||||||
|
getHasManageActionPermission,
|
||||||
|
} from "ee/utils/BusinessFeatures/permissionPageHelpers";
|
||||||
|
import {
|
||||||
|
ConvertToModule,
|
||||||
|
Copy,
|
||||||
|
Delete,
|
||||||
|
Move,
|
||||||
|
Rename,
|
||||||
|
ShowBindings,
|
||||||
|
} from "pages/Editor/AppPluginActionEditor/components/ContextMenuItems";
|
||||||
|
import { MenuSeparator } from "@appsmith/ads";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
action: Action;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppQueryContextMenuItems(props: Props) {
|
||||||
|
const { action } = props;
|
||||||
|
const actionPermissions = action.userPermissions || [];
|
||||||
|
|
||||||
|
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||||
|
|
||||||
|
const canDeleteAction = getHasDeleteActionPermission(
|
||||||
|
isFeatureEnabled,
|
||||||
|
actionPermissions,
|
||||||
|
);
|
||||||
|
|
||||||
|
const canManageAction = getHasManageActionPermission(
|
||||||
|
isFeatureEnabled,
|
||||||
|
actionPermissions,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Rename action={action} disabled={!canManageAction} />
|
||||||
|
<ShowBindings action={action} />
|
||||||
|
<ConvertToModule action={action} />
|
||||||
|
<Copy action={action} disabled={!canManageAction} />
|
||||||
|
<Move action={action} disabled={!canManageAction} />
|
||||||
|
<MenuSeparator />
|
||||||
|
<Delete action={action} disabled={!canDeleteAction} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
import React, { useCallback, useMemo } from "react";
|
||||||
|
import { EntityItem } from "@appsmith/ads";
|
||||||
|
import type { EntityItem as EntityItemProps } from "ee/entities/IDE/constants";
|
||||||
|
import type { AppState } from "ee/reducers";
|
||||||
|
import {
|
||||||
|
getActionByBaseId,
|
||||||
|
getDatasource,
|
||||||
|
getPlugins,
|
||||||
|
} from "ee/selectors/entitiesSelector";
|
||||||
|
import { type Action, type StoredDatasource } from "entities/Action";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||||
|
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||||
|
import { getHasManageActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers";
|
||||||
|
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
|
||||||
|
import type { Datasource } from "entities/Datasource";
|
||||||
|
import history, { NavigationMethod } from "utils/history";
|
||||||
|
import { keyBy } from "lodash";
|
||||||
|
import { saveActionNameBasedOnIdeType } from "ee/actions/helpers";
|
||||||
|
import { useNameEditorState } from "pages/Editor/IDE/EditorPane/hooks/useNameEditorState";
|
||||||
|
import { useValidateEntityName } from "IDE";
|
||||||
|
import { useLocation } from "react-router";
|
||||||
|
import { getIDETypeByUrl } from "ee/entities/IDE/utils";
|
||||||
|
import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers";
|
||||||
|
import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks";
|
||||||
|
import { PluginType } from "entities/Plugin";
|
||||||
|
import { useParentEntityInfo } from "ee/IDE/hooks/useParentEntityInfo";
|
||||||
|
import { getQueryContextMenuByIdeType } from "ee/pages/Editor/IDE/EditorPane/Query/utils/getQueryContextMenuByIdeType";
|
||||||
|
|
||||||
|
export const QueryEntityItem = ({ item }: { item: EntityItemProps }) => {
|
||||||
|
const action = useSelector((state: AppState) =>
|
||||||
|
getActionByBaseId(state, item.key),
|
||||||
|
) as Action;
|
||||||
|
const datasource = useSelector((state) =>
|
||||||
|
getDatasource(state, (action?.datasource as StoredDatasource)?.id),
|
||||||
|
) as Datasource;
|
||||||
|
const plugins = useSelector(getPlugins);
|
||||||
|
const pluginGroups = useMemo(() => keyBy(plugins, "id"), [plugins]);
|
||||||
|
const location = useLocation();
|
||||||
|
const ideType = getIDETypeByUrl(location.pathname);
|
||||||
|
const activeActionBaseId = useActiveActionBaseId();
|
||||||
|
const { parentEntityId } = useParentEntityInfo(ideType);
|
||||||
|
|
||||||
|
const { editingEntity, enterEditMode, exitEditMode, updatingEntity } =
|
||||||
|
useNameEditorState();
|
||||||
|
|
||||||
|
const validateName = useValidateEntityName({
|
||||||
|
entityName: item.title,
|
||||||
|
});
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const contextMenu = getQueryContextMenuByIdeType(ideType, action);
|
||||||
|
|
||||||
|
const actionPermissions = action.userPermissions || [];
|
||||||
|
|
||||||
|
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||||
|
|
||||||
|
const canManageAction = getHasManageActionPermission(
|
||||||
|
isFeatureEnabled,
|
||||||
|
actionPermissions,
|
||||||
|
);
|
||||||
|
|
||||||
|
const config = getActionConfig(action.pluginType);
|
||||||
|
const url = config?.getURL(
|
||||||
|
parentEntityId ?? "",
|
||||||
|
action.baseId,
|
||||||
|
action.pluginType,
|
||||||
|
pluginGroups[action.pluginId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const switchToAction = useCallback(() => {
|
||||||
|
url && history.push(url, { invokedBy: NavigationMethod.EntityExplorer });
|
||||||
|
AnalyticsUtil.logEvent("ENTITY_EXPLORER_CLICK", {
|
||||||
|
type: "QUERIES/APIs",
|
||||||
|
fromUrl: location.pathname,
|
||||||
|
toUrl: url,
|
||||||
|
name: action.name,
|
||||||
|
});
|
||||||
|
AnalyticsUtil.logEvent("EDIT_ACTION_CLICK", {
|
||||||
|
actionId: action?.id,
|
||||||
|
datasourceId: datasource?.id,
|
||||||
|
pluginName: pluginGroups[action?.pluginId]?.name,
|
||||||
|
actionType: action?.pluginType === PluginType.DB ? "Query" : "API",
|
||||||
|
isMock: !!datasource?.isMock,
|
||||||
|
});
|
||||||
|
}, [url, location.pathname, action, datasource, pluginGroups]);
|
||||||
|
|
||||||
|
const nameEditorConfig = useMemo(() => {
|
||||||
|
return {
|
||||||
|
canEdit: canManageAction,
|
||||||
|
isEditing: editingEntity === action.id,
|
||||||
|
isLoading: updatingEntity === action.id,
|
||||||
|
onEditComplete: exitEditMode,
|
||||||
|
onNameSave: (newName: string) =>
|
||||||
|
dispatch(saveActionNameBasedOnIdeType(action.id, newName, ideType)),
|
||||||
|
validateName: (newName: string) => validateName(newName, item.title),
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
canManageAction,
|
||||||
|
editingEntity,
|
||||||
|
exitEditMode,
|
||||||
|
ideType,
|
||||||
|
item.title,
|
||||||
|
action.id,
|
||||||
|
updatingEntity,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EntityItem
|
||||||
|
className="action t--action-entity"
|
||||||
|
id={action.id}
|
||||||
|
isSelected={activeActionBaseId === action.id}
|
||||||
|
key={action.id}
|
||||||
|
nameEditorConfig={nameEditorConfig}
|
||||||
|
onClick={switchToAction}
|
||||||
|
onDoubleClick={() => enterEditMode(action.id)}
|
||||||
|
rightControl={contextMenu}
|
||||||
|
rightControlVisibility="hover"
|
||||||
|
startIcon={item.icon}
|
||||||
|
title={item.title}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,47 +1,55 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Flex, Text, SearchAndAdd, NoSearchResults } from "@appsmith/ads";
|
import {
|
||||||
|
Flex,
|
||||||
|
Text,
|
||||||
|
SearchAndAdd,
|
||||||
|
NoSearchResults,
|
||||||
|
EntityGroupsList,
|
||||||
|
} from "@appsmith/ads";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
import { getHasCreateActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers";
|
|
||||||
import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks";
|
import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks";
|
||||||
import {
|
|
||||||
getCurrentApplicationId,
|
|
||||||
getCurrentPageId,
|
|
||||||
getPagePermissions,
|
|
||||||
} from "selectors/editorSelectors";
|
|
||||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||||
import { selectQuerySegmentEditorList } from "ee/selectors/appIDESelectors";
|
import { selectQuerySegmentEditorList } from "ee/selectors/appIDESelectors";
|
||||||
import { ActionParentEntityType } from "ee/entities/Engine/actionHelpers";
|
import { ActionParentEntityType } from "ee/entities/Engine/actionHelpers";
|
||||||
import { FilesContextProvider } from "pages/Editor/Explorer/Files/FilesContextProvider";
|
import { FilesContextProvider } from "pages/Editor/Explorer/Files/FilesContextProvider";
|
||||||
import { useQueryAdd } from "ee/pages/Editor/IDE/EditorPane/Query/hooks";
|
import { useQueryAdd } from "ee/pages/Editor/IDE/EditorPane/Query/hooks";
|
||||||
import { QueryListItem } from "ee/pages/Editor/IDE/EditorPane/Query/ListItem";
|
import { QueryListItem } from "ee/pages/Editor/IDE/EditorPane/Query/old/ListItem";
|
||||||
import { getShowWorkflowFeature } from "ee/selectors/workflowSelectors";
|
import { getShowWorkflowFeature } from "ee/selectors/workflowSelectors";
|
||||||
import { BlankState } from "./BlankState";
|
import { BlankState } from "./BlankState";
|
||||||
import { EDITOR_PANE_TEXTS, createMessage } from "ee/constants/messages";
|
import { EDITOR_PANE_TEXTS, createMessage } from "ee/constants/messages";
|
||||||
import { filterEntityGroupsBySearchTerm } from "IDE/utils";
|
import { filterEntityGroupsBySearchTerm } from "IDE/utils";
|
||||||
|
import type { EntityItem } from "ee/entities/IDE/constants";
|
||||||
|
import { ActionEntityItem } from "ee/pages/Editor/IDE/EditorPane/Query/ListItem";
|
||||||
|
import { useLocation } from "react-router";
|
||||||
|
import { getIDETypeByUrl } from "ee/entities/IDE/utils";
|
||||||
|
import { useParentEntityInfo } from "ee/IDE/hooks/useParentEntityInfo";
|
||||||
|
import { useCreateActionsPermissions } from "ee/entities/IDE/hooks/useCreateActionsPermissions";
|
||||||
|
import { objectKeys } from "@appsmith/utils";
|
||||||
|
|
||||||
const ListQuery = () => {
|
const ListQuery = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const pageId = useSelector(getCurrentPageId) as string;
|
|
||||||
const itemGroups = useSelector(selectQuerySegmentEditorList);
|
const itemGroups = useSelector(selectQuerySegmentEditorList);
|
||||||
const activeActionBaseId = useActiveActionBaseId();
|
const activeActionBaseId = useActiveActionBaseId();
|
||||||
const pagePermissions = useSelector(getPagePermissions);
|
|
||||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
const location = useLocation();
|
||||||
|
const ideType = getIDETypeByUrl(location.pathname);
|
||||||
|
const { editorId, parentEntityId } = useParentEntityInfo(ideType);
|
||||||
|
const canCreateActions = useCreateActionsPermissions(ideType);
|
||||||
|
|
||||||
|
const showWorkflows = useSelector(getShowWorkflowFeature);
|
||||||
|
|
||||||
|
const isNewADSTemplatesEnabled = useFeatureFlag(
|
||||||
|
FEATURE_FLAG.release_ads_entity_item_enabled,
|
||||||
|
);
|
||||||
|
|
||||||
const filteredItemGroups = filterEntityGroupsBySearchTerm(
|
const filteredItemGroups = filterEntityGroupsBySearchTerm(
|
||||||
searchTerm,
|
searchTerm,
|
||||||
itemGroups,
|
itemGroups,
|
||||||
);
|
);
|
||||||
|
|
||||||
const canCreateActions = getHasCreateActionPermission(
|
|
||||||
isFeatureEnabled,
|
|
||||||
pagePermissions,
|
|
||||||
);
|
|
||||||
const applicationId = useSelector(getCurrentApplicationId);
|
|
||||||
|
|
||||||
const { openAddQuery } = useQueryAdd();
|
const { openAddQuery } = useQueryAdd();
|
||||||
const showWorkflows = useSelector(getShowWorkflowFeature);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
|
|
@ -52,7 +60,7 @@ const ListQuery = () => {
|
||||||
px="spaces-3"
|
px="spaces-3"
|
||||||
py="spaces-3"
|
py="spaces-3"
|
||||||
>
|
>
|
||||||
{Object.keys(itemGroups).length === 0 && <BlankState />}
|
{objectKeys(itemGroups).length === 0 && <BlankState />}
|
||||||
|
|
||||||
{itemGroups.length > 0 ? (
|
{itemGroups.length > 0 ? (
|
||||||
<SearchAndAdd
|
<SearchAndAdd
|
||||||
|
|
@ -61,8 +69,27 @@ const ListQuery = () => {
|
||||||
showAddButton={canCreateActions}
|
showAddButton={canCreateActions}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<Flex flexDirection={"column"} gap="spaces-4" overflowY="auto">
|
<Flex
|
||||||
{filteredItemGroups.map(({ group, items }) => {
|
data-testid="t--ide-list"
|
||||||
|
flexDirection={"column"}
|
||||||
|
gap="spaces-4"
|
||||||
|
overflowY="auto"
|
||||||
|
>
|
||||||
|
{isNewADSTemplatesEnabled ? (
|
||||||
|
<EntityGroupsList
|
||||||
|
groups={filteredItemGroups.map(({ group, items }) => {
|
||||||
|
return {
|
||||||
|
groupTitle: group,
|
||||||
|
items: items,
|
||||||
|
className: "",
|
||||||
|
renderList: (item: EntityItem) => {
|
||||||
|
return <ActionEntityItem item={item} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
filteredItemGroups.map(({ group, items }) => {
|
||||||
return (
|
return (
|
||||||
<Flex flexDirection={"column"} key={group}>
|
<Flex flexDirection={"column"} key={group}>
|
||||||
<Flex py="spaces-1">
|
<Flex py="spaces-1">
|
||||||
|
|
@ -75,8 +102,8 @@ const ListQuery = () => {
|
||||||
</Flex>
|
</Flex>
|
||||||
<FilesContextProvider
|
<FilesContextProvider
|
||||||
canCreateActions={canCreateActions}
|
canCreateActions={canCreateActions}
|
||||||
editorId={applicationId}
|
editorId={editorId}
|
||||||
parentEntityId={pageId}
|
parentEntityId={parentEntityId}
|
||||||
parentEntityType={ActionParentEntityType.PAGE}
|
parentEntityType={ActionParentEntityType.PAGE}
|
||||||
showWorkflows={showWorkflows}
|
showWorkflows={showWorkflows}
|
||||||
>
|
>
|
||||||
|
|
@ -86,14 +113,15 @@ const ListQuery = () => {
|
||||||
isActive={file.key === activeActionBaseId}
|
isActive={file.key === activeActionBaseId}
|
||||||
item={file}
|
item={file}
|
||||||
key={file.key}
|
key={file.key}
|
||||||
parentEntityId={pageId}
|
parentEntityId={parentEntityId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</FilesContextProvider>
|
</FilesContextProvider>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
})}
|
})
|
||||||
|
)}
|
||||||
{filteredItemGroups.length === 0 && searchTerm !== "" ? (
|
{filteredItemGroups.length === 0 && searchTerm !== "" ? (
|
||||||
<NoSearchResults
|
<NoSearchResults
|
||||||
text={createMessage(
|
text={createMessage(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Button, Menu, MenuContent, MenuTrigger, Tooltip } from "@appsmith/ads";
|
||||||
|
import { useToggle } from "@mantine/hooks";
|
||||||
|
import {
|
||||||
|
createMessage,
|
||||||
|
ENTITY_MORE_ACTIONS_TOOLTIP,
|
||||||
|
} from "ee/constants/messages";
|
||||||
|
import { EntityClassNames } from "pages/Editor/Explorer/Entity";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children?: React.ReactNode[] | React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EntityContextMenu = (props: Props) => {
|
||||||
|
const [isMenuOpen, toggleMenuOpen] = useToggle([false, true]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu onOpenChange={toggleMenuOpen} open={isMenuOpen}>
|
||||||
|
<MenuTrigger className="t--context-menu">
|
||||||
|
<div className="relative">
|
||||||
|
<Tooltip
|
||||||
|
content={createMessage(ENTITY_MORE_ACTIONS_TOOLTIP)}
|
||||||
|
isDisabled={isMenuOpen}
|
||||||
|
mouseLeaveDelay={0}
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={EntityClassNames.CONTEXT_MENU}
|
||||||
|
data-testid="t--more-action-trigger"
|
||||||
|
isIconButton
|
||||||
|
kind="tertiary"
|
||||||
|
startIcon="more-2-fill"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</MenuTrigger>
|
||||||
|
<MenuContent
|
||||||
|
align="start"
|
||||||
|
className={`t--entity-context-menu ${EntityClassNames.CONTEXT_MENU_CONTENT}`}
|
||||||
|
side="right"
|
||||||
|
style={{ maxHeight: "unset" }}
|
||||||
|
width="220px"
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</MenuContent>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EntityContextMenu;
|
||||||
|
|
@ -1,32 +1,4 @@
|
||||||
import React, { useCallback, useMemo } from "react";
|
import React from "react";
|
||||||
import { useBoolean } from "usehooks-ts";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import {
|
|
||||||
moveJSCollectionRequest,
|
|
||||||
copyJSCollectionRequest,
|
|
||||||
deleteJSCollection,
|
|
||||||
} from "actions/jsActionActions";
|
|
||||||
import noop from "lodash/noop";
|
|
||||||
import {
|
|
||||||
CONTEXT_COPY,
|
|
||||||
CONTEXT_DELETE,
|
|
||||||
CONFIRM_CONTEXT_DELETE,
|
|
||||||
CONTEXT_MOVE,
|
|
||||||
createMessage,
|
|
||||||
CONTEXT_RENAME,
|
|
||||||
} from "ee/constants/messages";
|
|
||||||
import { getPageListAsOptions } from "ee/selectors/entitiesSelector";
|
|
||||||
import {
|
|
||||||
autoIndentCode,
|
|
||||||
getAutoIndentShortcutKeyText,
|
|
||||||
} from "components/editorComponents/CodeEditor/utils/autoIndentUtils";
|
|
||||||
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
|
|
||||||
import { updateJSCollectionBody } from "actions/jsPaneActions";
|
|
||||||
import type { IconName } from "@blueprintjs/icons";
|
|
||||||
|
|
||||||
import type { ContextMenuOption } from "./JSEditorContextMenu";
|
|
||||||
import JSEditorContextMenu from "./JSEditorContextMenu";
|
|
||||||
import equal from "fast-deep-equal/es6";
|
|
||||||
import {
|
import {
|
||||||
getHasDeleteActionPermission,
|
getHasDeleteActionPermission,
|
||||||
getHasManageActionPermission,
|
getHasManageActionPermission,
|
||||||
|
|
@ -34,26 +6,18 @@ import {
|
||||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||||
import type { JSCollection } from "entities/JSCollection";
|
import type { JSCollection } from "entities/JSCollection";
|
||||||
import { setRenameEntity } from "actions/ideActions";
|
import { Copy, Delete, Move, Prettify } from "./ContextMenuItems";
|
||||||
import type CodeMirror from "codemirror";
|
import { RenameMenuItem } from "IDE";
|
||||||
|
import { MenuSeparator } from "@appsmith/ads";
|
||||||
|
import EntityContextMenu from "../IDE/EditorPane/components/EntityContextMenu";
|
||||||
|
|
||||||
interface AppJSEditorContextMenuProps {
|
interface AppJSEditorContextMenuProps {
|
||||||
pageId: string;
|
|
||||||
jsCollection: JSCollection;
|
jsCollection: JSCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prettifyCodeKeyboardShortCut = getAutoIndentShortcutKeyText();
|
|
||||||
|
|
||||||
export function AppJSEditorContextMenu({
|
export function AppJSEditorContextMenu({
|
||||||
jsCollection,
|
jsCollection,
|
||||||
pageId,
|
|
||||||
}: AppJSEditorContextMenuProps) {
|
}: AppJSEditorContextMenuProps) {
|
||||||
const {
|
|
||||||
setFalse: cancelConfirmDelete,
|
|
||||||
setValue: setConfirmDelete,
|
|
||||||
value: confirmDelete,
|
|
||||||
} = useBoolean(false);
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||||
const isDeletePermitted = getHasDeleteActionPermission(
|
const isDeletePermitted = getHasDeleteActionPermission(
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
|
|
@ -64,169 +28,18 @@ export function AppJSEditorContextMenu({
|
||||||
jsCollection?.userPermissions || [],
|
jsCollection?.userPermissions || [],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renameJS = useCallback(() => {
|
|
||||||
// We add a delay to avoid having the focus stuck in the menu trigger
|
|
||||||
setTimeout(() => {
|
|
||||||
dispatch(setRenameEntity(jsCollection.id));
|
|
||||||
}, 100);
|
|
||||||
}, [dispatch, jsCollection.id]);
|
|
||||||
|
|
||||||
const copyJSCollectionToPage = useCallback(
|
|
||||||
(actionId: string, actionName: string, pageId: string) => {
|
|
||||||
dispatch(
|
|
||||||
copyJSCollectionRequest({
|
|
||||||
id: actionId,
|
|
||||||
destinationPageId: pageId,
|
|
||||||
name: actionName,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[dispatch],
|
|
||||||
);
|
|
||||||
|
|
||||||
const moveJSCollectionToPage = useCallback(
|
|
||||||
(actionId: string, actionName: string, destinationPageId: string) => {
|
|
||||||
dispatch(
|
|
||||||
moveJSCollectionRequest({
|
|
||||||
id: actionId,
|
|
||||||
destinationPageId,
|
|
||||||
name: actionName,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[dispatch],
|
|
||||||
);
|
|
||||||
const deleteJSCollectionFromPage = useCallback(
|
|
||||||
(actionId: string, actionName: string) => {
|
|
||||||
dispatch(deleteJSCollection({ id: actionId, name: actionName }));
|
|
||||||
setConfirmDelete(false);
|
|
||||||
},
|
|
||||||
[dispatch, setConfirmDelete],
|
|
||||||
);
|
|
||||||
|
|
||||||
const menuPages = useSelector(getPageListAsOptions, equal);
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
|
||||||
const confirmDeletion = (value: boolean, event?: Event) => {
|
|
||||||
event?.preventDefault?.();
|
|
||||||
setConfirmDelete(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renameOption = {
|
|
||||||
icon: "input-cursor-move" as IconName,
|
|
||||||
value: "rename",
|
|
||||||
onSelect: renameJS,
|
|
||||||
label: createMessage(CONTEXT_RENAME),
|
|
||||||
disabled: !isChangePermitted,
|
|
||||||
};
|
|
||||||
|
|
||||||
const copyOption = {
|
|
||||||
icon: "duplicate" as IconName,
|
|
||||||
value: "copy",
|
|
||||||
onSelect: noop,
|
|
||||||
label: createMessage(CONTEXT_COPY),
|
|
||||||
children: menuPages.map((page) => {
|
|
||||||
return {
|
|
||||||
...page,
|
|
||||||
onSelect: () =>
|
|
||||||
copyJSCollectionToPage(jsCollection.id, jsCollection.name, page.id),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const moveOption = {
|
|
||||||
icon: "swap-horizontal" as IconName,
|
|
||||||
value: "move",
|
|
||||||
onSelect: noop,
|
|
||||||
label: createMessage(CONTEXT_MOVE),
|
|
||||||
children:
|
|
||||||
menuPages.length > 1
|
|
||||||
? menuPages
|
|
||||||
.filter((page) => page.id !== pageId) // Remove current page from the list
|
|
||||||
.map((page) => {
|
|
||||||
return {
|
|
||||||
...page,
|
|
||||||
onSelect: () =>
|
|
||||||
moveJSCollectionToPage(
|
|
||||||
jsCollection.id,
|
|
||||||
jsCollection.name,
|
|
||||||
page.id,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
: [{ value: "No Pages", onSelect: noop, label: "No Pages" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const prettifyOptions = {
|
|
||||||
value: "prettify",
|
|
||||||
icon: "code" as IconName,
|
|
||||||
subText: prettifyCodeKeyboardShortCut,
|
|
||||||
onSelect: () => {
|
|
||||||
const editorElement = document.querySelector(".CodeMirror");
|
|
||||||
|
|
||||||
if (
|
|
||||||
editorElement &&
|
|
||||||
"CodeMirror" in editorElement &&
|
|
||||||
editorElement.CodeMirror
|
|
||||||
) {
|
|
||||||
const editor = editorElement.CodeMirror as CodeMirror.Editor;
|
|
||||||
|
|
||||||
autoIndentCode(editor);
|
|
||||||
dispatch(updateJSCollectionBody(editor.getValue(), jsCollection.id));
|
|
||||||
AnalyticsUtil.logEvent("PRETTIFY_CODE_MANUAL_TRIGGER");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
label: "Prettify code",
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteOption = {
|
|
||||||
confirmDelete: confirmDelete,
|
|
||||||
icon: "delete-bin-line" as IconName,
|
|
||||||
value: "delete",
|
|
||||||
onSelect: (event?: Event): void => {
|
|
||||||
confirmDelete
|
|
||||||
? deleteJSCollectionFromPage(jsCollection.id, jsCollection.name)
|
|
||||||
: confirmDeletion(true, event);
|
|
||||||
},
|
|
||||||
label: confirmDelete
|
|
||||||
? createMessage(CONFIRM_CONTEXT_DELETE)
|
|
||||||
: createMessage(CONTEXT_DELETE),
|
|
||||||
className: "t--apiFormDeleteBtn error-menuitem",
|
|
||||||
};
|
|
||||||
|
|
||||||
const options: ContextMenuOption[] = [renameOption];
|
|
||||||
|
|
||||||
if (isChangePermitted) {
|
|
||||||
options.push(copyOption);
|
|
||||||
options.push(moveOption);
|
|
||||||
options.push(prettifyOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDeletePermitted) options.push(deleteOption);
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}, [
|
|
||||||
confirmDelete,
|
|
||||||
copyJSCollectionToPage,
|
|
||||||
deleteJSCollectionFromPage,
|
|
||||||
dispatch,
|
|
||||||
isChangePermitted,
|
|
||||||
isDeletePermitted,
|
|
||||||
jsCollection.id,
|
|
||||||
jsCollection.name,
|
|
||||||
menuPages,
|
|
||||||
moveJSCollectionToPage,
|
|
||||||
pageId,
|
|
||||||
renameJS,
|
|
||||||
setConfirmDelete,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<JSEditorContextMenu
|
<EntityContextMenu>
|
||||||
className="t--more-action-menu"
|
<RenameMenuItem
|
||||||
onMenuClose={cancelConfirmDelete}
|
disabled={!isChangePermitted}
|
||||||
options={options}
|
entityId={jsCollection.id}
|
||||||
/>
|
/>
|
||||||
|
<Copy disabled={!isChangePermitted} jsAction={jsCollection} />
|
||||||
|
<Move disabled={!isChangePermitted} jsAction={jsCollection} />
|
||||||
|
<Prettify disabled={!isChangePermitted} jsAction={jsCollection} />
|
||||||
|
<MenuSeparator />
|
||||||
|
<Delete disabled={!isDeletePermitted} jsAction={jsCollection} />
|
||||||
|
</EntityContextMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import React from "react";
|
||||||
|
import { MenuSub, MenuSubContent, MenuSubTrigger } from "@appsmith/ads";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { getPageList } from "selectors/editorSelectors";
|
||||||
|
import { PageMenuItem } from "./PageMenuItem";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import { CONTEXT_COPY, createMessage } from "ee/constants/messages";
|
||||||
|
import { copyJSCollectionRequest } from "actions/jsActionActions";
|
||||||
|
import type { JSCollection } from "entities/JSCollection";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
jsAction: JSCollection;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Copy = ({ disabled, jsAction }: Props) => {
|
||||||
|
const menuPages = useSelector(getPageList);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const copyJSActionToPage = useCallback(
|
||||||
|
(pageId: string) =>
|
||||||
|
dispatch(
|
||||||
|
copyJSCollectionRequest({
|
||||||
|
id: jsAction.id,
|
||||||
|
destinationPageId: pageId,
|
||||||
|
name: jsAction.name,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
[jsAction.id, jsAction.name, dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuSub>
|
||||||
|
<MenuSubTrigger startIcon="duplicate">
|
||||||
|
{createMessage(CONTEXT_COPY)}
|
||||||
|
</MenuSubTrigger>
|
||||||
|
<MenuSubContent style={{ maxHeight: "350px" }} width="220px">
|
||||||
|
{menuPages.map((page) => {
|
||||||
|
return (
|
||||||
|
<PageMenuItem
|
||||||
|
disabled={disabled}
|
||||||
|
key={page.basePageId}
|
||||||
|
onSelect={copyJSActionToPage}
|
||||||
|
page={page}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</MenuSubContent>
|
||||||
|
</MenuSub>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
import React, { useCallback, useState } from "react";
|
||||||
|
import {
|
||||||
|
CONFIRM_CONTEXT_DELETE,
|
||||||
|
CONTEXT_DELETE,
|
||||||
|
createMessage,
|
||||||
|
} from "ee/constants/messages";
|
||||||
|
import { MenuItem } from "@appsmith/ads";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { deleteJSCollection } from "actions/jsActionActions";
|
||||||
|
import type { JSCollection } from "entities/JSCollection";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
jsAction: JSCollection;
|
||||||
|
disabled?: boolean;
|
||||||
|
deleteJSAction?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Delete = ({ deleteJSAction, disabled, jsAction }: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
|
|
||||||
|
const handleDeleteClick = useCallback(() => {
|
||||||
|
jsAction.isPublic && deleteJSAction
|
||||||
|
? deleteJSAction()
|
||||||
|
: dispatch(
|
||||||
|
deleteJSCollection({
|
||||||
|
id: jsAction?.id ?? "",
|
||||||
|
name: jsAction?.name ?? "",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}, [jsAction.id, jsAction.name, jsAction.isPublic, deleteJSAction, dispatch]);
|
||||||
|
|
||||||
|
const handleSelect = useCallback(
|
||||||
|
(e?: Event) => {
|
||||||
|
e?.preventDefault();
|
||||||
|
confirmDelete ? handleDeleteClick() : setConfirmDelete(true);
|
||||||
|
e?.stopPropagation();
|
||||||
|
},
|
||||||
|
[confirmDelete, handleDeleteClick],
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuLabel = confirmDelete
|
||||||
|
? createMessage(CONFIRM_CONTEXT_DELETE)
|
||||||
|
: createMessage(CONTEXT_DELETE);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
className="t--apiFormDeleteBtn single-select error-menuitem"
|
||||||
|
disabled={disabled}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
startIcon="trash"
|
||||||
|
>
|
||||||
|
{menuLabel}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { getCurrentPageId } from "selectors/editorSelectors";
|
||||||
|
import { getPageList } from "ee/selectors/entitiesSelector";
|
||||||
|
import React, { useCallback, useMemo } from "react";
|
||||||
|
import {
|
||||||
|
MenuItem,
|
||||||
|
MenuSub,
|
||||||
|
MenuSubContent,
|
||||||
|
MenuSubTrigger,
|
||||||
|
} from "@appsmith/ads";
|
||||||
|
import { CONTEXT_MOVE, createMessage } from "ee/constants/messages";
|
||||||
|
import { PageMenuItem } from "./PageMenuItem";
|
||||||
|
import { moveJSCollectionRequest } from "actions/jsActionActions";
|
||||||
|
import type { JSCollection } from "entities/JSCollection";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
jsAction: JSCollection;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Move = ({ disabled, jsAction }: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const currentPageId = useSelector(getCurrentPageId);
|
||||||
|
const allPages = useSelector(getPageList);
|
||||||
|
const menuPages = useMemo(() => {
|
||||||
|
return allPages.filter((page) => page.pageId !== currentPageId);
|
||||||
|
}, [allPages, currentPageId]);
|
||||||
|
|
||||||
|
const moveJSActionToPage = useCallback(
|
||||||
|
(destinationPageId: string) =>
|
||||||
|
dispatch(
|
||||||
|
moveJSCollectionRequest({
|
||||||
|
id: jsAction.id,
|
||||||
|
destinationPageId,
|
||||||
|
name: jsAction.name,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
[dispatch, jsAction.id, jsAction.name],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuSub>
|
||||||
|
<MenuSubTrigger startIcon="swap-horizontal">
|
||||||
|
{createMessage(CONTEXT_MOVE)}
|
||||||
|
</MenuSubTrigger>
|
||||||
|
<MenuSubContent style={{ maxHeight: "350px" }} width="220px">
|
||||||
|
{menuPages.length ? (
|
||||||
|
menuPages.map((page) => {
|
||||||
|
return (
|
||||||
|
<PageMenuItem
|
||||||
|
disabled={disabled}
|
||||||
|
key={page.basePageId}
|
||||||
|
onSelect={moveJSActionToPage}
|
||||||
|
page={page}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<MenuItem key="no-pages">No pages</MenuItem>
|
||||||
|
)}
|
||||||
|
</MenuSubContent>
|
||||||
|
</MenuSub>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import type { Page } from "entities/Page";
|
||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { MenuItem } from "@appsmith/ads";
|
||||||
|
|
||||||
|
export const PageMenuItem = (props: {
|
||||||
|
page: Page;
|
||||||
|
onSelect: (id: string) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}) => {
|
||||||
|
const handleOnSelect = useCallback(() => {
|
||||||
|
props.onSelect(props.page.pageId);
|
||||||
|
}, [props]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem disabled={props.disabled} onSelect={handleOnSelect}>
|
||||||
|
{props.page.pageName}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { MenuItem, Text } from "@appsmith/ads";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import type { JSCollection } from "entities/JSCollection";
|
||||||
|
import { updateJSCollectionBody } from "actions/jsPaneActions";
|
||||||
|
import {
|
||||||
|
autoIndentCode,
|
||||||
|
getAutoIndentShortcutKeyText,
|
||||||
|
} from "components/editorComponents/CodeEditor/utils/autoIndentUtils";
|
||||||
|
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
jsAction: JSCollection;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prettifyCodeKeyboardShortCut = getAutoIndentShortcutKeyText();
|
||||||
|
|
||||||
|
export const Prettify = ({ disabled, jsAction }: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleSelect = useCallback(() => {
|
||||||
|
const editorElement = document.querySelector(".CodeMirror");
|
||||||
|
|
||||||
|
if (
|
||||||
|
editorElement &&
|
||||||
|
"CodeMirror" in editorElement &&
|
||||||
|
editorElement.CodeMirror
|
||||||
|
) {
|
||||||
|
const editor = editorElement.CodeMirror as CodeMirror.Editor;
|
||||||
|
|
||||||
|
autoIndentCode(editor);
|
||||||
|
dispatch(updateJSCollectionBody(editor.getValue(), jsAction.id));
|
||||||
|
AnalyticsUtil.logEvent("PRETTIFY_CODE_MANUAL_TRIGGER");
|
||||||
|
}
|
||||||
|
}, [jsAction.id, jsAction.name]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem disabled={disabled} onSelect={handleSelect} startIcon="code">
|
||||||
|
<Text color={"var(--ads-v2-color-fg)"}>Prettify code</Text>
|
||||||
|
<Text
|
||||||
|
color={"var(--ads-v2-color-fg-muted)"}
|
||||||
|
kind="body-s"
|
||||||
|
style={{ marginLeft: "7px" }}
|
||||||
|
>
|
||||||
|
{prettifyCodeKeyboardShortCut}
|
||||||
|
</Text>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { MenuItem } from "@appsmith/ads";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { initExplorerEntityNameEdit } from "actions/explorerActions";
|
||||||
|
import { CONTEXT_RENAME, createMessage } from "ee/constants/messages";
|
||||||
|
import type { JSCollection } from "entities/JSCollection";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
jsAction: JSCollection;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Rename = ({ disabled, jsAction }: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const setRename = useCallback(() => {
|
||||||
|
// We add a delay to avoid having the focus stuck in the menu trigger
|
||||||
|
setTimeout(() => {
|
||||||
|
dispatch(initExplorerEntityNameEdit(jsAction.id));
|
||||||
|
}, 100);
|
||||||
|
}, [dispatch, jsAction.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
disabled={disabled}
|
||||||
|
onSelect={setRename}
|
||||||
|
startIcon="input-cursor-move"
|
||||||
|
>
|
||||||
|
{createMessage(CONTEXT_RENAME)}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { CONTEXT_SHOW_BINDING, createMessage } from "ee/constants/messages";
|
||||||
|
import { MenuItem } from "@appsmith/ads";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
|
||||||
|
import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils";
|
||||||
|
import type { JSCollection } from "entities/JSCollection";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
jsAction: JSCollection;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShowBindings = ({ disabled, jsAction }: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleSelect = useCallback(() => {
|
||||||
|
dispatch({
|
||||||
|
type: ReduxActionTypes.SET_ENTITY_INFO,
|
||||||
|
payload: {
|
||||||
|
entityId: jsAction.id,
|
||||||
|
entityName: jsAction.name,
|
||||||
|
entityType: ENTITY_TYPE.JSACTION,
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [jsAction.id, jsAction.name]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
disabled={disabled}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
startIcon="binding-new"
|
||||||
|
>
|
||||||
|
{createMessage(CONTEXT_SHOW_BINDING)}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export { Copy } from "./Copy";
|
||||||
|
export { Move } from "./Move";
|
||||||
|
export { Delete } from "./Delete";
|
||||||
|
export { Rename } from "./Rename";
|
||||||
|
export { ShowBindings } from "./ShowBindings";
|
||||||
|
export { Prettify } from "./Prettify";
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import type { IconName } from "@blueprintjs/icons";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Menu,
|
|
||||||
MenuContent,
|
|
||||||
MenuItem,
|
|
||||||
MenuSub,
|
|
||||||
MenuSubContent,
|
|
||||||
MenuSubTrigger,
|
|
||||||
MenuTrigger,
|
|
||||||
Text,
|
|
||||||
} from "@appsmith/ads";
|
|
||||||
|
|
||||||
export interface ContextMenuOption {
|
|
||||||
id?: string;
|
|
||||||
icon: IconName;
|
|
||||||
value: string;
|
|
||||||
onSelect?: (event?: Event) => void;
|
|
||||||
label: string;
|
|
||||||
subText?: string;
|
|
||||||
className?: string;
|
|
||||||
children?: Omit<ContextMenuOption, "children" | "icon">[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EntityContextMenuProps {
|
|
||||||
className?: string;
|
|
||||||
options: ContextMenuOption[];
|
|
||||||
onMenuClose: (() => void) | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function JSEditorContextMenu({
|
|
||||||
className,
|
|
||||||
onMenuClose,
|
|
||||||
options,
|
|
||||||
}: EntityContextMenuProps) {
|
|
||||||
if (options.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu
|
|
||||||
className={className}
|
|
||||||
onOpenChange={(open) => {
|
|
||||||
if (!open) {
|
|
||||||
onMenuClose?.();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MenuTrigger>
|
|
||||||
<Button
|
|
||||||
data-testid="t--more-action-trigger"
|
|
||||||
isIconButton
|
|
||||||
kind="tertiary"
|
|
||||||
size={"sm"}
|
|
||||||
startIcon={"more-2-fill"}
|
|
||||||
/>
|
|
||||||
</MenuTrigger>
|
|
||||||
<MenuContent align="end" avoidCollisions>
|
|
||||||
{options.map((option, index) => {
|
|
||||||
if (option.children) {
|
|
||||||
return (
|
|
||||||
<MenuSub key={index}>
|
|
||||||
<MenuSubTrigger startIcon={option.icon}>
|
|
||||||
{option.label}
|
|
||||||
</MenuSubTrigger>
|
|
||||||
<MenuSubContent>
|
|
||||||
{option.children.map((children) => (
|
|
||||||
<MenuItem key={children.value} onSelect={children.onSelect}>
|
|
||||||
{children.label}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</MenuSubContent>
|
|
||||||
</MenuSub>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MenuItem
|
|
||||||
className={option?.className}
|
|
||||||
key={option.value}
|
|
||||||
// TODO: Fix this the next time the file is edited
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
onSelect={option.onSelect as any}
|
|
||||||
startIcon={option.icon}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<Text
|
|
||||||
color={
|
|
||||||
option?.value === "delete"
|
|
||||||
? "var(--ads-v2-color-fg-error)"
|
|
||||||
: "var(--ads-v2-color-fg)"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</Text>
|
|
||||||
{option.subText && (
|
|
||||||
<Text
|
|
||||||
color={"var(--ads-v2-color-fg-muted)"}
|
|
||||||
kind="body-s"
|
|
||||||
style={{ marginLeft: "7px" }}
|
|
||||||
>
|
|
||||||
{option.subText}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</MenuContent>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default JSEditorContextMenu;
|
|
||||||
|
|
@ -3,10 +3,7 @@ import type { RouteComponentProps } from "react-router";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import JsEditorForm from "./Form";
|
import JsEditorForm from "./Form";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import {
|
import { getJSCollectionDataByBaseId } from "selectors/editorSelectors";
|
||||||
getCurrentPageId,
|
|
||||||
getJSCollectionDataByBaseId,
|
|
||||||
} from "selectors/editorSelectors";
|
|
||||||
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||||
import Spinner from "components/editorComponents/Spinner";
|
import Spinner from "components/editorComponents/Spinner";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
@ -27,7 +24,6 @@ type Props = RouteComponentProps<{
|
||||||
|
|
||||||
function JSEditor(props: Props) {
|
function JSEditor(props: Props) {
|
||||||
const { baseCollectionId } = props.match.params;
|
const { baseCollectionId } = props.match.params;
|
||||||
const pageId = useSelector(getCurrentPageId);
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const jsCollectionData = useSelector((state) =>
|
const jsCollectionData = useSelector((state) =>
|
||||||
getJSCollectionDataByBaseId(state, baseCollectionId),
|
getJSCollectionDataByBaseId(state, baseCollectionId),
|
||||||
|
|
@ -40,10 +36,8 @@ function JSEditor(props: Props) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <AppJSEditorContextMenu jsCollection={jsCollection} />;
|
||||||
<AppJSEditorContextMenu jsCollection={jsCollection} pageId={pageId} />
|
}, [jsCollection]);
|
||||||
);
|
|
||||||
}, [jsCollection, pageId]);
|
|
||||||
|
|
||||||
if (isCreating) {
|
if (isCreating) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1062,6 +1062,7 @@ function* handleMoveOrCopySaga(actionPayload: ReduxAction<Action>) {
|
||||||
const isApi = pluginType === PluginType.API;
|
const isApi = pluginType === PluginType.API;
|
||||||
const isQuery = pluginType === PluginType.DB;
|
const isQuery = pluginType === PluginType.DB;
|
||||||
const isSaas = pluginType === PluginType.SAAS;
|
const isSaas = pluginType === PluginType.SAAS;
|
||||||
|
const isInternal = pluginType === PluginType.INTERNAL;
|
||||||
const { parentEntityId } = resolveParentEntityMetadata(actionPayload.payload);
|
const { parentEntityId } = resolveParentEntityMetadata(actionPayload.payload);
|
||||||
|
|
||||||
if (!parentEntityId) return;
|
if (!parentEntityId) return;
|
||||||
|
|
@ -1080,7 +1081,7 @@ function* handleMoveOrCopySaga(actionPayload: ReduxAction<Action>) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isQuery) {
|
if (isQuery || isInternal) {
|
||||||
history.push(
|
history.push(
|
||||||
queryEditorIdURL({
|
queryEditorIdURL({
|
||||||
baseParentEntityId,
|
baseParentEntityId,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user