diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index 72f97892a0..c5775ce571 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -2312,10 +2312,17 @@ export const EDITOR_PANE_TEXTS = { query_create_tab_title: () => "Create new query from", widgets_create_tab_title: () => "Drag & drop UI elements", js_create_tab_title: () => "Create JS object from", - queries_create_from_existing: () => "From existing datasource", - queries_create_new: () => "New API", + js_create_modules: () => "JS modules (Beta)", + queries_create_from_existing: () => "Datasources", + queries_create_new: () => "Quick actions", + queries_create_modules: () => "Query modules (Beta)", loading_building_blocks: () => "Loading building blocks", empty_search_result: (type: string) => `No ${type} match your search`, + search_objects: { + jsObject: () => "JS object", + queries: () => "queries", + datasources: () => "datasources", + }, }; export const PARTIAL_IMPORT_EXPORT = { diff --git a/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/hooks.tsx b/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/hooks.tsx index f669310209..ef00213224 100644 --- a/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/hooks.tsx +++ b/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/hooks.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo } from "react"; +import { useCallback, useMemo } from "react"; import history from "utils/history"; import { useLocation } from "react-router"; import { FocusEntity, identifyEntityFromPath } from "navigation/FocusEntity"; @@ -33,7 +33,7 @@ import ListQuery from "pages/Editor/IDE/EditorPane/Query/List"; import type { AppState } from "@appsmith/reducers"; import keyBy from "lodash/keyBy"; import { getPluginEntityIcon } from "pages/Editor/Explorer/ExplorerIcons"; -import { Tag, type ListItemProps } from "design-system"; +import type { ListItemProps } from "design-system"; import { useCurrentEditorState } from "pages/Editor/IDE/hooks"; import { createAddClassName } from "pages/Editor/IDE/EditorPane/utils"; import { QueriesBlankState } from "pages/Editor/QueryEditor/QueriesBlankState"; @@ -182,12 +182,16 @@ export const useAddQueryListItems = () => { [pageId, dispatch], ); - const getListItems = (data: any[]) => { + const getListItems = (data: ActionOperation[]) => { return data.map((fileOperation) => { - const title = + let title = fileOperation.entityExplorerTitle || fileOperation.dsName || fileOperation.title; + title = + fileOperation.focusEntityType === FocusEntity.QUERY_MODULE_INSTANCE + ? fileOperation.title + : title; const className = createAddClassName(title); const icon = fileOperation.icon || @@ -197,11 +201,10 @@ export const useAddQueryListItems = () => { startIcon: icon, wrapperClassName: className, title, - description: !!fileOperation.isBeta ? ( - Beta - ) : ( - "" - ), + description: + fileOperation.focusEntityType === FocusEntity.QUERY_MODULE_INSTANCE + ? fileOperation.dsName + : "", descriptionType: "inline", onClick: onCreateItemClick.bind(null, fileOperation), } as ListItemProps; diff --git a/app/client/src/pages/Editor/IDE/EditorPane/JS/Add.tsx b/app/client/src/pages/Editor/IDE/EditorPane/JS/Add.tsx index b45d056bf7..de9e629181 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/JS/Add.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/JS/Add.tsx @@ -1,8 +1,8 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useState } from "react"; import SegmentAddHeader from "../components/SegmentAddHeader"; -import { EDITOR_PANE_TEXTS } from "@appsmith/constants/messages"; +import { EDITOR_PANE_TEXTS, createMessage } from "@appsmith/constants/messages"; import type { ListItemProps } from "design-system"; -import { Flex, Tag } from "design-system"; +import { Flex, SearchInput } from "design-system"; import { useDispatch, useSelector } from "react-redux"; import { getCurrentPageId } from "selectors/editorSelectors"; import GroupedList from "../components/GroupedList"; @@ -12,11 +12,15 @@ import { } from "@appsmith/pages/Editor/IDE/EditorPane/JS/hooks"; import type { ActionOperation } from "components/editorComponents/GlobalSearch/utils"; import type { AddProps } from "../types/AddProps"; -import { createAddClassName } from "../utils"; +import { createAddClassName, fuzzySearchInObjectItems } from "../utils"; +import { FocusEntity } from "navigation/FocusEntity"; +import type { GroupedListProps } from "../components/types"; +import { EmptySearchResult } from "../components/EmptySearchResult"; const AddJS = ({ containerProps, innerContainerProps }: AddProps) => { const dispatch = useDispatch(); const pageId = useSelector(getCurrentPageId); + const [searchTerm, setSearchTerm] = useState(""); const groupedJsOperations = useGroupedAddJsOperations(); @@ -35,13 +39,29 @@ const AddJS = ({ containerProps, innerContainerProps }: AddProps) => { return { startIcon: data.icon, title, - description: !!data.isBeta ? Beta : "", + description: + data.focusEntityType === FocusEntity.JS_MODULE_INSTANCE + ? data.dsName + : "", descriptionType: "inline", onClick: onCreateItemClick.bind(null, data), wrapperClassName: createAddClassName(title), } as ListItemProps; }; + const groups = groupedJsOperations.map( + ({ className, operations, title }) => ({ + groupTitle: title, + className: className, + items: operations.map(getListItems), + }), + ); + + const localGroups = fuzzySearchInObjectItems( + searchTerm, + groups, + ); + return ( { onCloseClick={closeAddJS} titleMessage={EDITOR_PANE_TEXTS.js_create_tab_title} /> - - ({ - groupTitle: op.title, - className: op.className, - items: op.operations.map(getListItems), - }))} - /> + + {localGroups.length > 0 ? : null} + {localGroups.length === 0 && searchTerm !== "" ? ( + + ) : null} ); diff --git a/app/client/src/pages/Editor/IDE/EditorPane/JS/List.tsx b/app/client/src/pages/Editor/IDE/EditorPane/JS/List.tsx index 620ac0b373..3861f9cc28 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/JS/List.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/JS/List.tsx @@ -3,6 +3,7 @@ import { useSelector } from "react-redux"; import { Flex, Text } from "design-system"; import styled from "styled-components"; +import type { EditorSegmentList } from "@appsmith/selectors/appIDESelectors"; import { selectJSSegmentEditorList } from "@appsmith/selectors/appIDESelectors"; import { useActiveAction } from "@appsmith/pages/Editor/Explorer/hooks"; import { @@ -19,7 +20,8 @@ import { useJSAdd } from "@appsmith/pages/Editor/IDE/EditorPane/JS/hooks"; import { JSListItem } from "@appsmith/pages/Editor/IDE/EditorPane/JS/ListItem"; import { BlankState } from "./BlankState"; import { AddAndSearchbar } from "../components/AddAndSearchbar"; -import { fuzzySearchInFiles } from "../utils"; +import { fuzzySearchInObjectItems } from "../utils"; +import { EmptySearchResult } from "../components/EmptySearchResult"; import { EDITOR_PANE_TEXTS, createMessage } from "@appsmith/constants/messages"; const JSContainer = styled(Flex)` @@ -44,7 +46,10 @@ const ListJSObjects = () => { const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); - const localFiles = fuzzySearchInFiles(searchTerm, files); + const localFiles = fuzzySearchInObjectItems( + searchTerm, + files, + ); const canCreateActions = getHasCreateActionPermission( isFeatureEnabled, @@ -112,13 +117,9 @@ const ListJSObjects = () => { ); })} {localFiles.length === 0 && searchTerm !== "" ? ( - - {createMessage(EDITOR_PANE_TEXTS.empty_search_result, "JS")} - + ) : null} diff --git a/app/client/src/pages/Editor/IDE/EditorPane/Query/Add.tsx b/app/client/src/pages/Editor/IDE/EditorPane/Query/Add.tsx index ff95fb1f09..843be08f1a 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/Query/Add.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/Query/Add.tsx @@ -1,7 +1,7 @@ -import React from "react"; -import { Flex } from "design-system"; +import React, { useState } from "react"; +import { Flex, SearchInput } from "design-system"; -import { EDITOR_PANE_TEXTS } from "@appsmith/constants/messages"; +import { EDITOR_PANE_TEXTS, createMessage } from "@appsmith/constants/messages"; import SegmentAddHeader from "../components/SegmentAddHeader"; import GroupedList from "../components/GroupedList"; import { @@ -10,12 +10,27 @@ import { useQueryAdd, } from "@appsmith/pages/Editor/IDE/EditorPane/Query/hooks"; import type { AddProps } from "../types/AddProps"; +import { fuzzySearchInObjectItems } from "../utils"; +import type { GroupedListProps } from "../components/types"; +import { EmptySearchResult } from "../components/EmptySearchResult"; const AddQuery = ({ containerProps, innerContainerProps }: AddProps) => { + const [searchTerm, setSearchTerm] = useState(""); const { getListItems } = useAddQueryListItems(); const groupedActionOperations = useGroupedAddQueryOperations(); const { closeAddQuery } = useQueryAdd(); + const groups = groupedActionOperations.map((group) => ({ + groupTitle: group.title, + className: group.className, + items: getListItems(group.operations), + })); + + const localGroups = fuzzySearchInObjectItems( + searchTerm, + groups, + ); + return ( { onCloseClick={closeAddQuery} titleMessage={EDITOR_PANE_TEXTS.query_create_tab_title} /> - ({ - groupTitle: group.title, - className: group.className, - items: getListItems(group.operations), - }))} - /> + + {localGroups.length > 0 ? : null} + {localGroups.length === 0 && searchTerm !== "" ? ( + + ) : null} ); diff --git a/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx b/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx index 9321d44d80..447cd38971 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx @@ -11,6 +11,7 @@ import { import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag"; import { getCurrentPageId } from "@appsmith/selectors/entitiesSelector"; +import type { EditorSegmentList } from "@appsmith/selectors/appIDESelectors"; import { selectQuerySegmentEditorList } from "@appsmith/selectors/appIDESelectors"; import { ActionParentEntityType } from "@appsmith/entities/Engine/actionHelpers"; import { FilesContextProvider } from "pages/Editor/Explorer/Files/FilesContextProvider"; @@ -19,7 +20,8 @@ import { QueryListItem } from "@appsmith/pages/Editor/IDE/EditorPane/Query/ListI import { getShowWorkflowFeature } from "@appsmith/selectors/workflowSelectors"; import { BlankState } from "./BlankState"; import { AddAndSearchbar } from "../components/AddAndSearchbar"; -import { fuzzySearchInFiles } from "../utils"; +import { fuzzySearchInObjectItems } from "../utils"; +import { EmptySearchResult } from "../components/EmptySearchResult"; import { EDITOR_PANE_TEXTS, createMessage } from "@appsmith/constants/messages"; const ListQuery = () => { @@ -30,7 +32,10 @@ const ListQuery = () => { const pagePermissions = useSelector(getPagePermissions); const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); - const localFiles = fuzzySearchInFiles(searchTerm, files); + const localFiles = fuzzySearchInObjectItems( + searchTerm, + files, + ); const canCreateActions = getHasCreateActionPermission( isFeatureEnabled, @@ -92,13 +97,9 @@ const ListQuery = () => { ); })} {localFiles.length === 0 && searchTerm !== "" ? ( - - {createMessage(EDITOR_PANE_TEXTS.empty_search_result, "queries")} - + ) : null} diff --git a/app/client/src/pages/Editor/IDE/EditorPane/components/EmptySearchResult.tsx b/app/client/src/pages/Editor/IDE/EditorPane/components/EmptySearchResult.tsx new file mode 100644 index 0000000000..6407ea6e30 --- /dev/null +++ b/app/client/src/pages/Editor/IDE/EditorPane/components/EmptySearchResult.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { Text } from "design-system"; +import { EDITOR_PANE_TEXTS, createMessage } from "@appsmith/constants/messages"; + +const EmptySearchResult = ({ type }: { type: string }) => { + return ( + + {createMessage(EDITOR_PANE_TEXTS.empty_search_result, type)} + + ); +}; + +export { EmptySearchResult }; diff --git a/app/client/src/pages/Editor/IDE/EditorPane/components/Group.tsx b/app/client/src/pages/Editor/IDE/EditorPane/components/Group.tsx new file mode 100644 index 0000000000..571e5b447f --- /dev/null +++ b/app/client/src/pages/Editor/IDE/EditorPane/components/Group.tsx @@ -0,0 +1,81 @@ +import React, { useMemo, useState } from "react"; +import type { GroupedListProps } from "./types"; +import { DEFAULT_GROUP_LIST_SIZE } from "./constants"; +import { Flex, List, Text } from "design-system"; +import styled from "styled-components"; + +interface GroupProps { + group: GroupedListProps; +} + +const StyledList = styled(List)` + padding: 0; + gap: 0; + + & .ds-load-more .ads-v2-listitem__title { + --color: var(--ads-v2-color-fg-subtle); + } + & .ads-v2-listitem__wrapper .ads-v2-listitem__idesc { + opacity: 0; + } + + & .ads-v2-listitem__wrapper:hover .ads-v2-listitem__idesc { + opacity: 1; + } +`; + +const Group: React.FC = ({ group }) => { + const [visibleItemsCount, setVisibleItemsCount] = useState( + DEFAULT_GROUP_LIST_SIZE, + ); + const { className, groupTitle, items: groupItems } = group; + + const items = useMemo(() => { + const items = groupItems.slice(0, visibleItemsCount); + const hasMoreItems = groupItems.length > visibleItemsCount; + + const handleLoadMore = () => { + setVisibleItemsCount(groupItems.length); + }; + + if (hasMoreItems) { + items.push({ + title: "Load more...", + description: "", + descriptionType: "inline", + onClick: handleLoadMore, + className: "ds-load-more", + }); + } + + // TODO: try to avoid this + if (hasMoreItems && groupTitle === "Datasources") { + items.push(groupItems[groupItems.length - 1]); + } + + return items; + }, [groupItems, visibleItemsCount, groupTitle]); + + return ( + + {groupTitle ? ( + + {groupTitle} + + ) : null} + + + ); +}; + +export { Group }; diff --git a/app/client/src/pages/Editor/IDE/EditorPane/components/GroupedList.tsx b/app/client/src/pages/Editor/IDE/EditorPane/components/GroupedList.tsx index e3c634d7fb..960b245961 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/components/GroupedList.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/components/GroupedList.tsx @@ -1,27 +1,24 @@ import React from "react"; +import type { FlexProps } from "design-system"; +import { Flex } from "design-system"; import styled from "styled-components"; -import type { FlexProps, ListItemProps } from "design-system"; -import { Flex, List, Text } from "design-system"; - -const StyledList = styled(List)` - padding: 0; - gap: 0; -`; - -export type GroupedListProps = Array<{ - groupTitle?: string; - className: string; - items: ListItemProps[]; -}>; +import type { GroupedListProps } from "./types"; +import { Group } from "./Group"; interface Props { - groups: GroupedListProps; + groups: GroupedListProps[]; flexProps?: FlexProps; } +const StyledFlex = styled(Flex)` + & .groups-list-group:last-child { + border-bottom: none; + } +`; + const GroupedList = (props: Props) => { return ( - { {...props.flexProps} > {props.groups.map((group) => ( - - {group.groupTitle ? ( - - {group.groupTitle} - - ) : null} - - + ))} - + ); }; diff --git a/app/client/src/pages/Editor/IDE/EditorPane/components/SegmentAddHeader.tsx b/app/client/src/pages/Editor/IDE/EditorPane/components/SegmentAddHeader.tsx index d198110c53..c33dfe8bda 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/components/SegmentAddHeader.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/components/SegmentAddHeader.tsx @@ -20,7 +20,6 @@ const SegmentAddHeader = (props: Props) => { : "var(--ads-v2-color-gray-50)" } justifyContent="space-between" - py="spaces-2" > {createMessage(props.titleMessage)} diff --git a/app/client/src/pages/Editor/IDE/EditorPane/components/constants.ts b/app/client/src/pages/Editor/IDE/EditorPane/components/constants.ts new file mode 100644 index 0000000000..3f7096e37f --- /dev/null +++ b/app/client/src/pages/Editor/IDE/EditorPane/components/constants.ts @@ -0,0 +1 @@ +export const DEFAULT_GROUP_LIST_SIZE = 5; diff --git a/app/client/src/pages/Editor/IDE/EditorPane/components/types.ts b/app/client/src/pages/Editor/IDE/EditorPane/components/types.ts new file mode 100644 index 0000000000..2ad6e33c09 --- /dev/null +++ b/app/client/src/pages/Editor/IDE/EditorPane/components/types.ts @@ -0,0 +1,7 @@ +import type { ListItemProps } from "design-system"; + +export interface GroupedListProps { + groupTitle?: string; + className: string; + items: ListItemProps[]; +} diff --git a/app/client/src/pages/Editor/IDE/EditorPane/constants.ts b/app/client/src/pages/Editor/IDE/EditorPane/constants.ts new file mode 100644 index 0000000000..78472bee20 --- /dev/null +++ b/app/client/src/pages/Editor/IDE/EditorPane/constants.ts @@ -0,0 +1 @@ +export const EDITOR_TABS_HEIGHT = "32px"; diff --git a/app/client/src/pages/Editor/IDE/EditorPane/fuzzySearchInFiles.test.ts b/app/client/src/pages/Editor/IDE/EditorPane/fuzzySearchInFiles.test.ts index b9a4c068a3..458e9e188a 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/fuzzySearchInFiles.test.ts +++ b/app/client/src/pages/Editor/IDE/EditorPane/fuzzySearchInFiles.test.ts @@ -1,5 +1,5 @@ import type { EditorSegmentList } from "@appsmith/selectors/appIDESelectors"; -import { fuzzySearchInFiles } from "./utils"; +import { fuzzySearchInObjectItems } from "./utils"; import { PluginType } from "entities/Action"; const sampleFiles: EditorSegmentList = [ @@ -19,14 +19,14 @@ const sampleFiles: EditorSegmentList = [ }, ]; -describe("fuzzySearchInFiles", () => { +describe("fuzzySearchInObjectItems", () => { it("should return all files when the search string is empty", () => { - const result = fuzzySearchInFiles("", sampleFiles); + const result = fuzzySearchInObjectItems("", sampleFiles); expect(result).toEqual(sampleFiles); }); it("should return the correct file when the search string exactly matches a file title", () => { - const result = fuzzySearchInFiles("file1", sampleFiles); + const result = fuzzySearchInObjectItems("file1", sampleFiles); expect(result).toEqual([ { group: "Group 1", @@ -36,12 +36,12 @@ describe("fuzzySearchInFiles", () => { }); it("should return an empty array when no files match the search string", () => { - const result = fuzzySearchInFiles("nonexistentfile", sampleFiles); + const result = fuzzySearchInObjectItems("nonexistentfile", sampleFiles); expect(result).toEqual([]); }); it("should return all files containing the common substring in their titles", () => { - const result = fuzzySearchInFiles("file", sampleFiles); + const result = fuzzySearchInObjectItems("file", sampleFiles); expect(result).toEqual(sampleFiles); }); }); diff --git a/app/client/src/pages/Editor/IDE/EditorPane/utils.ts b/app/client/src/pages/Editor/IDE/EditorPane/utils.ts index 390f5d201f..4dc7feb81f 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/utils.ts +++ b/app/client/src/pages/Editor/IDE/EditorPane/utils.ts @@ -1,5 +1,4 @@ import Fuse from "fuse.js"; -import type { EditorSegmentList } from "@appsmith/selectors/appIDESelectors"; export const createAddClassName = (name: string) => { return "t--datasoucre-create-option-" + name.toLowerCase().replace(/ /g, "_"); @@ -11,19 +10,20 @@ const FUSE_OPTIONS = { keys: ["title"], }; -export const fuzzySearchInFiles = ( +export const fuzzySearchInObjectItems = ( searchStr: string, - files: EditorSegmentList, -) => { + files: T, +): T => { if (searchStr && searchStr !== "") { const newFiles = files .map((group) => { - const fuse = new Fuse(group.items, FUSE_OPTIONS); + const items = group["items"]; + const fuse = new Fuse(items, FUSE_OPTIONS); const resultItems = fuse.search(searchStr); return { ...group, items: resultItems }; }) .filter((group) => group.items.length > 0); - return newFiles; + return newFiles as T; } return files; diff --git a/app/client/src/pages/Editor/IDE/EditorTabs/Container.tsx b/app/client/src/pages/Editor/IDE/EditorTabs/Container.tsx index dbc77dd103..c02710d2e0 100644 --- a/app/client/src/pages/Editor/IDE/EditorTabs/Container.tsx +++ b/app/client/src/pages/Editor/IDE/EditorTabs/Container.tsx @@ -1,6 +1,7 @@ import type { ReactNode } from "react"; import React from "react"; import { Flex } from "design-system"; +import { EDITOR_TABS_HEIGHT } from "../EditorPane/constants"; const Container = (props: { children: ReactNode }) => { return ( @@ -10,8 +11,8 @@ const Container = (props: { children: ReactNode }) => { borderBottom="1px solid var(--ads-v2-color-border-muted)" gap="spaces-2" id="ide-tabs-container" - maxHeight="32px" - minHeight="32px" + maxHeight={EDITOR_TABS_HEIGHT} + minHeight={EDITOR_TABS_HEIGHT} px="spaces-2" width="100%" > diff --git a/app/client/src/pages/Editor/JSEditor/JSAddState.tsx b/app/client/src/pages/Editor/JSEditor/JSAddState.tsx index 8d58080709..9cd066d399 100644 --- a/app/client/src/pages/Editor/JSEditor/JSAddState.tsx +++ b/app/client/src/pages/Editor/JSEditor/JSAddState.tsx @@ -1,10 +1,11 @@ import React from "react"; import { Flex } from "design-system"; import AddJS from "pages/Editor/IDE/EditorPane/JS/Add"; +import { EDITOR_TABS_HEIGHT } from "../IDE/EditorPane/constants"; const JSAddState = () => { return ( - + { return ( - +