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 { useDispatch } from "react-redux";
|
||||
import { setRenameEntity } from "actions/ideActions";
|
||||
import { CONTEXT_RENAME, createMessage } from "ee/constants/messages";
|
||||
|
||||
interface Props {
|
||||
disabled?: boolean;
|
||||
|
|
@ -24,7 +25,7 @@ export const RenameMenuItem = ({ disabled, entityId }: Props) => {
|
|||
onSelect={setRename}
|
||||
startIcon="input-cursor-move"
|
||||
>
|
||||
Rename
|
||||
{createMessage(CONTEXT_RENAME)}
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,27 +1,7 @@
|
|||
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 { JSEntityItem } from "pages/Editor/IDE/EditorPane/JS/EntityItem/JSEntityItem";
|
||||
|
||||
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>
|
||||
);
|
||||
export const JSEntity = (props: { item: EntityItem }) => {
|
||||
return <JSEntityItem {...props} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 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";
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
export const ActionEntityItem = (props: { item: EntityItem }) => {
|
||||
return <QueryEntityItem {...props} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 { usePluginActionContext } from "PluginActionEditor";
|
||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||
import { useSelector } from "react-redux";
|
||||
|
|
@ -11,21 +10,27 @@ import {
|
|||
import { MODULE_TYPE } from "ee/constants/ModuleConstants";
|
||||
import ConvertToModuleInstanceCTA from "ee/pages/Editor/EntityEditor/ConvertToModuleInstanceCTA";
|
||||
import { PluginType } from "entities/Plugin";
|
||||
import type { Action } from "entities/Action";
|
||||
|
||||
const ConvertToModuleCTA = () => {
|
||||
const { action, plugin } = usePluginActionContext();
|
||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||
interface Props {
|
||||
action: Action;
|
||||
}
|
||||
|
||||
export const ConvertToModule = ({ action }: Props) => {
|
||||
const pagePermissions = useSelector(getPagePermissions);
|
||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||
|
||||
const isCreatePermitted = getHasCreateActionPermission(
|
||||
isFeatureEnabled,
|
||||
pagePermissions,
|
||||
);
|
||||
|
||||
const isDeletePermitted = getHasDeleteActionPermission(
|
||||
isFeatureEnabled,
|
||||
action.userPermissions,
|
||||
);
|
||||
|
||||
if (plugin.type === PluginType.INTERNAL) {
|
||||
if (action.pluginType === PluginType.INTERNAL) {
|
||||
// Workflow queries cannot be converted to modules
|
||||
return null;
|
||||
}
|
||||
|
|
@ -39,5 +44,3 @@ const ConvertToModuleCTA = () => {
|
|||
|
||||
return <ConvertToModuleInstanceCTA {...convertToModuleProps} />;
|
||||
};
|
||||
|
||||
export default ConvertToModuleCTA;
|
||||
|
|
@ -1,19 +1,20 @@
|
|||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { getPageList } from "ee/selectors/entitiesSelector";
|
||||
import { usePluginActionContext } from "PluginActionEditor";
|
||||
import React, { useCallback } from "react";
|
||||
import { copyActionRequest } from "actions/pluginActionActions";
|
||||
import React from "react";
|
||||
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 { useCallback } from "react";
|
||||
import type { Action } from "entities/Action";
|
||||
import { copyActionRequest } from "actions/pluginActionActions";
|
||||
import { CONTEXT_COPY, createMessage } from "ee/constants/messages";
|
||||
|
||||
interface Props {
|
||||
action: Action;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const Copy = ({ disabled }: Props) => {
|
||||
export const Copy = ({ action, disabled }: Props) => {
|
||||
const menuPages = useSelector(getPageList);
|
||||
const { action } = usePluginActionContext();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const copyActionToPage = useCallback(
|
||||
|
|
@ -30,13 +31,14 @@ export const Copy = ({ disabled }: Props) => {
|
|||
|
||||
return (
|
||||
<MenuSub>
|
||||
<MenuSubTrigger disabled={disabled} startIcon="duplicate">
|
||||
<MenuSubTrigger startIcon="duplicate">
|
||||
{createMessage(CONTEXT_COPY)}
|
||||
</MenuSubTrigger>
|
||||
<MenuSubContent>
|
||||
<MenuSubContent style={{ maxHeight: "350px" }} width="220px">
|
||||
{menuPages.map((page) => {
|
||||
return (
|
||||
<PageMenuItem
|
||||
disabled={disabled}
|
||||
key={page.basePageId}
|
||||
onSelect={copyActionToPage}
|
||||
page={page}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import { useHandleDeleteClick } from "PluginActionEditor/hooks";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import {
|
||||
CONFIRM_CONTEXT_DELETE,
|
||||
|
|
@ -6,19 +5,37 @@ import {
|
|||
createMessage,
|
||||
} from "ee/constants/messages";
|
||||
import { MenuItem } from "@appsmith/ads";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { deleteAction } from "actions/pluginActionActions";
|
||||
import type { Action } from "entities/Action";
|
||||
|
||||
interface Props {
|
||||
action: Action;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const Delete = ({ disabled }: Props) => {
|
||||
const { handleDeleteClick } = useHandleDeleteClick();
|
||||
export const Delete = ({ action, disabled }: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
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(
|
||||
(e?: Event) => {
|
||||
e?.preventDefault();
|
||||
confirmDelete ? handleDeleteClick({}) : setConfirmDelete(true);
|
||||
e?.stopPropagation();
|
||||
},
|
||||
[confirmDelete, handleDeleteClick],
|
||||
);
|
||||
|
|
@ -29,7 +46,7 @@ export const Delete = ({ disabled }: Props) => {
|
|||
|
||||
return (
|
||||
<MenuItem
|
||||
className="t--apiFormDeleteBtn error-menuitem"
|
||||
className="t--apiFormDeleteBtn single-select error-menuitem"
|
||||
disabled={disabled}
|
||||
onSelect={handleSelect}
|
||||
startIcon="trash"
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { usePluginActionContext } from "PluginActionEditor";
|
||||
import { getCurrentPageId } from "selectors/editorSelectors";
|
||||
import { getPageList } from "ee/selectors/entitiesSelector";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
|
|
@ -12,14 +11,15 @@ import {
|
|||
} from "@appsmith/ads";
|
||||
import { CONTEXT_MOVE, createMessage } from "ee/constants/messages";
|
||||
import { PageMenuItem } from "./PageMenuItem";
|
||||
import type { Action } from "entities/Action";
|
||||
|
||||
interface Props {
|
||||
action: Action;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const Move = ({ disabled }: Props) => {
|
||||
export const Move = ({ action, disabled }: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
const { action } = usePluginActionContext();
|
||||
|
||||
const currentPageId = useSelector(getCurrentPageId);
|
||||
const allPages = useSelector(getPageList);
|
||||
|
|
@ -42,14 +42,15 @@ export const Move = ({ disabled }: Props) => {
|
|||
|
||||
return (
|
||||
<MenuSub>
|
||||
<MenuSubTrigger disabled={disabled} startIcon="swap-horizontal">
|
||||
<MenuSubTrigger startIcon="swap-horizontal">
|
||||
{createMessage(CONTEXT_MOVE)}
|
||||
</MenuSubTrigger>
|
||||
<MenuSubContent>
|
||||
<MenuSubContent style={{ maxHeight: "350px" }} width="220px">
|
||||
{menuPages.length ? (
|
||||
menuPages.map((page) => {
|
||||
return (
|
||||
<PageMenuItem
|
||||
disabled={disabled}
|
||||
key={page.basePageId}
|
||||
onSelect={moveActionToPage}
|
||||
page={page}
|
||||
|
|
@ -5,10 +5,15 @@ 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 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 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,
|
||||
DocsMenuItem as Docs,
|
||||
} from "PluginActionEditor";
|
||||
import { ConvertToModuleCTA } from "../ConvertToModule";
|
||||
import { Move } from "./Move";
|
||||
import { Copy } from "./Copy";
|
||||
import { Delete } from "./Delete";
|
||||
import { ConvertToModule, Copy, Delete, Move } from "../ContextMenuItems";
|
||||
import { RenameMenuItem } from "IDE";
|
||||
|
||||
export const ToolbarMenu = () => {
|
||||
|
|
@ -32,12 +29,12 @@ export const ToolbarMenu = () => {
|
|||
return (
|
||||
<>
|
||||
<RenameMenuItem disabled={!isChangePermitted} entityId={action.id} />
|
||||
<ConvertToModuleCTA />
|
||||
<Copy disabled={!isChangePermitted} />
|
||||
<Move disabled={!isChangePermitted} />
|
||||
<ConvertToModule action={action} />
|
||||
<Copy action={action} disabled={!isChangePermitted} />
|
||||
<Move action={action} disabled={!isChangePermitted} />
|
||||
<Docs />
|
||||
<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(
|
||||
|
|
|
|||
|
|
@ -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 { 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 { selectJSSegmentEditorList } from "ee/selectors/appIDESelectors";
|
||||
import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks";
|
||||
import {
|
||||
getCurrentApplicationId,
|
||||
getCurrentPageId,
|
||||
getPagePermissions,
|
||||
} from "selectors/editorSelectors";
|
||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||
import { getHasCreateActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers";
|
||||
import { ActionParentEntityType } from "ee/entities/Engine/actionHelpers";
|
||||
import { FilesContextProvider } from "pages/Editor/Explorer/Files/FilesContextProvider";
|
||||
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 { EDITOR_PANE_TEXTS, createMessage } from "ee/constants/messages";
|
||||
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)`
|
||||
& .t--entity-item {
|
||||
|
|
@ -30,25 +36,23 @@ const JSContainer = styled(Flex)`
|
|||
|
||||
const ListJSObjects = () => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const pageId = useSelector(getCurrentPageId);
|
||||
const itemGroups = useSelector(selectJSSegmentEditorList);
|
||||
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(
|
||||
searchTerm,
|
||||
itemGroups,
|
||||
);
|
||||
|
||||
const canCreateActions = getHasCreateActionPermission(
|
||||
isFeatureEnabled,
|
||||
pagePermissions,
|
||||
);
|
||||
|
||||
const { openAddJS } = useJSAdd();
|
||||
|
||||
return (
|
||||
|
|
@ -70,46 +74,60 @@ const ListJSObjects = () => {
|
|||
showAddButton={canCreateActions}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Flex
|
||||
data-testid="t--ide-list"
|
||||
flexDirection="column"
|
||||
gap="spaces-4"
|
||||
overflowY="auto"
|
||||
>
|
||||
{filteredItemGroups.map(({ group, items }) => {
|
||||
return (
|
||||
<Flex flexDirection={"column"} key={group}>
|
||||
{group !== "NA" ? (
|
||||
<Flex py="spaces-1">
|
||||
<Text
|
||||
className="overflow-hidden overflow-ellipsis whitespace-nowrap"
|
||||
kind="body-s"
|
||||
>
|
||||
{group}
|
||||
</Text>
|
||||
</Flex>
|
||||
) : null}
|
||||
<FilesContextProvider
|
||||
canCreateActions={canCreateActions}
|
||||
editorId={applicationId}
|
||||
parentEntityId={pageId}
|
||||
parentEntityType={ActionParentEntityType.PAGE}
|
||||
>
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<JSListItem
|
||||
isActive={item.key === activeActionBaseId}
|
||||
item={item}
|
||||
key={item.key}
|
||||
parentEntityId={pageId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</FilesContextProvider>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
{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 (
|
||||
<Flex flexDirection={"column"} key={group}>
|
||||
{group !== "NA" ? (
|
||||
<Flex py="spaces-1">
|
||||
<Text
|
||||
className="overflow-hidden overflow-ellipsis whitespace-nowrap"
|
||||
kind="body-s"
|
||||
>
|
||||
{group}
|
||||
</Text>
|
||||
</Flex>
|
||||
) : null}
|
||||
<FilesContextProvider
|
||||
canCreateActions={canCreateActions}
|
||||
editorId={editorId}
|
||||
parentEntityId={parentEntityId}
|
||||
parentEntityType={ActionParentEntityType.PAGE}
|
||||
>
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<JSListItem
|
||||
isActive={item.key === activeActionBaseId}
|
||||
item={item}
|
||||
key={item.key}
|
||||
parentEntityId={parentEntityId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</FilesContextProvider>
|
||||
</Flex>
|
||||
);
|
||||
})
|
||||
)}
|
||||
{filteredItemGroups.length === 0 && searchTerm !== "" ? (
|
||||
<NoSearchResults
|
||||
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 { Flex, Text, SearchAndAdd, NoSearchResults } from "@appsmith/ads";
|
||||
import {
|
||||
Flex,
|
||||
Text,
|
||||
SearchAndAdd,
|
||||
NoSearchResults,
|
||||
EntityGroupsList,
|
||||
} from "@appsmith/ads";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { getHasCreateActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers";
|
||||
import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks";
|
||||
import {
|
||||
getCurrentApplicationId,
|
||||
getCurrentPageId,
|
||||
getPagePermissions,
|
||||
} from "selectors/editorSelectors";
|
||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||
import { selectQuerySegmentEditorList } from "ee/selectors/appIDESelectors";
|
||||
import { ActionParentEntityType } from "ee/entities/Engine/actionHelpers";
|
||||
import { FilesContextProvider } from "pages/Editor/Explorer/Files/FilesContextProvider";
|
||||
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 { BlankState } from "./BlankState";
|
||||
import { EDITOR_PANE_TEXTS, createMessage } from "ee/constants/messages";
|
||||
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 [searchTerm, setSearchTerm] = useState("");
|
||||
const pageId = useSelector(getCurrentPageId) as string;
|
||||
const itemGroups = useSelector(selectQuerySegmentEditorList);
|
||||
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(
|
||||
searchTerm,
|
||||
itemGroups,
|
||||
);
|
||||
|
||||
const canCreateActions = getHasCreateActionPermission(
|
||||
isFeatureEnabled,
|
||||
pagePermissions,
|
||||
);
|
||||
const applicationId = useSelector(getCurrentApplicationId);
|
||||
|
||||
const { openAddQuery } = useQueryAdd();
|
||||
const showWorkflows = useSelector(getShowWorkflowFeature);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
|
@ -52,7 +60,7 @@ const ListQuery = () => {
|
|||
px="spaces-3"
|
||||
py="spaces-3"
|
||||
>
|
||||
{Object.keys(itemGroups).length === 0 && <BlankState />}
|
||||
{objectKeys(itemGroups).length === 0 && <BlankState />}
|
||||
|
||||
{itemGroups.length > 0 ? (
|
||||
<SearchAndAdd
|
||||
|
|
@ -61,39 +69,59 @@ const ListQuery = () => {
|
|||
showAddButton={canCreateActions}
|
||||
/>
|
||||
) : null}
|
||||
<Flex flexDirection={"column"} gap="spaces-4" overflowY="auto">
|
||||
{filteredItemGroups.map(({ group, items }) => {
|
||||
return (
|
||||
<Flex flexDirection={"column"} key={group}>
|
||||
<Flex py="spaces-1">
|
||||
<Text
|
||||
className="overflow-hidden overflow-ellipsis whitespace-nowrap"
|
||||
kind="body-s"
|
||||
<Flex
|
||||
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 (
|
||||
<Flex flexDirection={"column"} key={group}>
|
||||
<Flex py="spaces-1">
|
||||
<Text
|
||||
className="overflow-hidden overflow-ellipsis whitespace-nowrap"
|
||||
kind="body-s"
|
||||
>
|
||||
{group}
|
||||
</Text>
|
||||
</Flex>
|
||||
<FilesContextProvider
|
||||
canCreateActions={canCreateActions}
|
||||
editorId={editorId}
|
||||
parentEntityId={parentEntityId}
|
||||
parentEntityType={ActionParentEntityType.PAGE}
|
||||
showWorkflows={showWorkflows}
|
||||
>
|
||||
{group}
|
||||
</Text>
|
||||
{items.map((file) => {
|
||||
return (
|
||||
<QueryListItem
|
||||
isActive={file.key === activeActionBaseId}
|
||||
item={file}
|
||||
key={file.key}
|
||||
parentEntityId={parentEntityId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</FilesContextProvider>
|
||||
</Flex>
|
||||
<FilesContextProvider
|
||||
canCreateActions={canCreateActions}
|
||||
editorId={applicationId}
|
||||
parentEntityId={pageId}
|
||||
parentEntityType={ActionParentEntityType.PAGE}
|
||||
showWorkflows={showWorkflows}
|
||||
>
|
||||
{items.map((file) => {
|
||||
return (
|
||||
<QueryListItem
|
||||
isActive={file.key === activeActionBaseId}
|
||||
item={file}
|
||||
key={file.key}
|
||||
parentEntityId={pageId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</FilesContextProvider>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})
|
||||
)}
|
||||
{filteredItemGroups.length === 0 && searchTerm !== "" ? (
|
||||
<NoSearchResults
|
||||
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 { 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 React from "react";
|
||||
import {
|
||||
getHasDeleteActionPermission,
|
||||
getHasManageActionPermission,
|
||||
|
|
@ -34,26 +6,18 @@ import {
|
|||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||
import type { JSCollection } from "entities/JSCollection";
|
||||
import { setRenameEntity } from "actions/ideActions";
|
||||
import type CodeMirror from "codemirror";
|
||||
import { Copy, Delete, Move, Prettify } from "./ContextMenuItems";
|
||||
import { RenameMenuItem } from "IDE";
|
||||
import { MenuSeparator } from "@appsmith/ads";
|
||||
import EntityContextMenu from "../IDE/EditorPane/components/EntityContextMenu";
|
||||
|
||||
interface AppJSEditorContextMenuProps {
|
||||
pageId: string;
|
||||
jsCollection: JSCollection;
|
||||
}
|
||||
|
||||
const prettifyCodeKeyboardShortCut = getAutoIndentShortcutKeyText();
|
||||
|
||||
export function AppJSEditorContextMenu({
|
||||
jsCollection,
|
||||
pageId,
|
||||
}: AppJSEditorContextMenuProps) {
|
||||
const {
|
||||
setFalse: cancelConfirmDelete,
|
||||
setValue: setConfirmDelete,
|
||||
value: confirmDelete,
|
||||
} = useBoolean(false);
|
||||
const dispatch = useDispatch();
|
||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||
const isDeletePermitted = getHasDeleteActionPermission(
|
||||
isFeatureEnabled,
|
||||
|
|
@ -64,169 +28,18 @@ export function AppJSEditorContextMenu({
|
|||
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 (
|
||||
<JSEditorContextMenu
|
||||
className="t--more-action-menu"
|
||||
onMenuClose={cancelConfirmDelete}
|
||||
options={options}
|
||||
/>
|
||||
<EntityContextMenu>
|
||||
<RenameMenuItem
|
||||
disabled={!isChangePermitted}
|
||||
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 JsEditorForm from "./Form";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import {
|
||||
getCurrentPageId,
|
||||
getJSCollectionDataByBaseId,
|
||||
} from "selectors/editorSelectors";
|
||||
import { getJSCollectionDataByBaseId } from "selectors/editorSelectors";
|
||||
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||
import Spinner from "components/editorComponents/Spinner";
|
||||
import styled from "styled-components";
|
||||
|
|
@ -27,7 +24,6 @@ type Props = RouteComponentProps<{
|
|||
|
||||
function JSEditor(props: Props) {
|
||||
const { baseCollectionId } = props.match.params;
|
||||
const pageId = useSelector(getCurrentPageId);
|
||||
const dispatch = useDispatch();
|
||||
const jsCollectionData = useSelector((state) =>
|
||||
getJSCollectionDataByBaseId(state, baseCollectionId),
|
||||
|
|
@ -40,10 +36,8 @@ function JSEditor(props: Props) {
|
|||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppJSEditorContextMenu jsCollection={jsCollection} pageId={pageId} />
|
||||
);
|
||||
}, [jsCollection, pageId]);
|
||||
return <AppJSEditorContextMenu jsCollection={jsCollection} />;
|
||||
}, [jsCollection]);
|
||||
|
||||
if (isCreating) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1062,6 +1062,7 @@ function* handleMoveOrCopySaga(actionPayload: ReduxAction<Action>) {
|
|||
const isApi = pluginType === PluginType.API;
|
||||
const isQuery = pluginType === PluginType.DB;
|
||||
const isSaas = pluginType === PluginType.SAAS;
|
||||
const isInternal = pluginType === PluginType.INTERNAL;
|
||||
const { parentEntityId } = resolveParentEntityMetadata(actionPayload.payload);
|
||||
|
||||
if (!parentEntityId) return;
|
||||
|
|
@ -1080,7 +1081,7 @@ function* handleMoveOrCopySaga(actionPayload: ReduxAction<Action>) {
|
|||
);
|
||||
}
|
||||
|
||||
if (isQuery) {
|
||||
if (isQuery || isInternal) {
|
||||
history.push(
|
||||
queryEditorIdURL({
|
||||
baseParentEntityId,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user