From 6ddf1c8e1c9893cfddd72262d00980bee823e820 Mon Sep 17 00:00:00 2001 From: albinAppsmith <87797149+albinAppsmith@users.noreply.github.com> Date: Wed, 17 Jul 2024 12:07:52 +0530 Subject: [PATCH] feat: Updated new tab ui with search and load more --- app/client/src/ce/constants/messages.ts | 6 +- .../Editor/IDE/EditorPane/Query/hooks.tsx | 18 ++--- .../pages/Editor/IDE/EditorPane/JS/Add.tsx | 43 +++++++---- .../pages/Editor/IDE/EditorPane/JS/List.tsx | 18 ++--- .../pages/Editor/IDE/EditorPane/Query/Add.tsx | 35 +++++---- .../Editor/IDE/EditorPane/Query/List.tsx | 18 ++--- .../components/EmptySearchResult.tsx | 17 +++++ .../IDE/EditorPane/components/Group.tsx | 76 +++++++++++++++++++ .../IDE/EditorPane/components/GroupedList.tsx | 42 ++++------ .../components/SegmentAddHeader.tsx | 3 - .../IDE/EditorPane/components/constants.ts | 1 + .../Editor/IDE/EditorPane/components/types.ts | 7 ++ .../IDE/EditorPane/fuzzySearchInFiles.test.ts | 12 +-- .../src/pages/Editor/IDE/EditorPane/utils.ts | 18 +++-- .../src/pages/Editor/JSEditor/JSAddState.tsx | 2 +- .../Editor/QueryEditor/QueriesAddState.tsx | 2 +- 16 files changed, 211 insertions(+), 107 deletions(-) create mode 100644 app/client/src/pages/Editor/IDE/EditorPane/components/EmptySearchResult.tsx create mode 100644 app/client/src/pages/Editor/IDE/EditorPane/components/Group.tsx create mode 100644 app/client/src/pages/Editor/IDE/EditorPane/components/constants.ts create mode 100644 app/client/src/pages/Editor/IDE/EditorPane/components/types.ts diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index 72f97892a048..7c9ba6239a30 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -2312,8 +2312,10 @@ 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`, }; 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 f6693102099e..1973d893e064 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,13 @@ export const useAddQueryListItems = () => { [pageId, dispatch], ); - const getListItems = (data: any[]) => { + const getListItems = (data: ActionOperation[]) => { return data.map((fileOperation) => { const title = fileOperation.entityExplorerTitle || + fileOperation.title || fileOperation.dsName || - fileOperation.title; + ""; const className = createAddClassName(title); const icon = fileOperation.icon || @@ -197,11 +198,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 2e524f80deaf..e6daa9177b00 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 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,27 @@ 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((op) => ({ + groupTitle: op.title, + className: op.className, + items: op.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 465b05c7b5fd..78d355775d6c 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,8 +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 { EDITOR_PANE_TEXTS, createMessage } from "@appsmith/constants/messages"; +import { fuzzySearchInObjectItems } from "../utils"; +import { EmptySearchResult } from "../components/EmptySearchResult"; const JSContainer = styled(Flex)` & .t--entity-item { @@ -44,7 +45,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 +116,7 @@ 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 b46513abcc7e..686c98890194 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/Query/Add.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/Query/Add.tsx @@ -1,5 +1,5 @@ -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 SegmentAddHeader from "../components/SegmentAddHeader"; @@ -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 f23a0e2e02c4..ab40854ef2a4 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,8 +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 { EDITOR_PANE_TEXTS, createMessage } from "@appsmith/constants/messages"; +import { fuzzySearchInObjectItems } from "../utils"; +import { EmptySearchResult } from "../components/EmptySearchResult"; const ListQuery = () => { const [searchTerm, setSearchTerm] = useState(""); @@ -30,7 +31,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, @@ -96,13 +100,7 @@ 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 000000000000..6407ea6e30b0 --- /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 000000000000..134d190d7e51 --- /dev/null +++ b/app/client/src/pages/Editor/IDE/EditorPane/components/Group.tsx @@ -0,0 +1,76 @@ +import React, { 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 [visibleItems, setVisibleItems] = useState( + DEFAULT_GROUP_LIST_SIZE, + ); + const { className, groupTitle } = group; + const items = group.items.slice(0, visibleItems); + const hasMoreItems = group.items.length > visibleItems; + + const handleLoadMore = () => { + setVisibleItems(group.items.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(group.items[group.items.length - 1]); + } + + 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 268bf614b8be..960b2459613c 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 b07ba8870e3f..c33dfe8bdaef 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/components/SegmentAddHeader.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/components/SegmentAddHeader.tsx @@ -20,9 +20,6 @@ const SegmentAddHeader = (props: Props) => { : "var(--ads-v2-color-gray-50)" } justifyContent="space-between" - pl="spaces-4" - pr="spaces-2" - 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 000000000000..3f7096e37f7b --- /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 000000000000..2ad6e33c0993 --- /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/fuzzySearchInFiles.test.ts b/app/client/src/pages/Editor/IDE/EditorPane/fuzzySearchInFiles.test.ts index b9a4c068a381..458e9e188ace 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 390f5d201f1e..30bae96772ea 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/utils.ts +++ b/app/client/src/pages/Editor/IDE/EditorPane/utils.ts @@ -1,5 +1,5 @@ import Fuse from "fuse.js"; -import type { EditorSegmentList } from "@appsmith/selectors/appIDESelectors"; +import get from "lodash/get"; export const createAddClassName = (name: string) => { return "t--datasoucre-create-option-" + name.toLowerCase().replace(/ /g, "_"); @@ -8,22 +8,24 @@ export const createAddClassName = (name: string) => { const FUSE_OPTIONS = { shouldSort: true, threshold: 0.1, - keys: ["title"], }; -export const fuzzySearchInFiles = ( +export const fuzzySearchInObjectItems = ( searchStr: string, - files: EditorSegmentList, -) => { + files: T, + keysToSearch = ["title"], + itemsKey = "items", +): T => { if (searchStr && searchStr !== "") { const newFiles = files - .map((group) => { - const fuse = new Fuse(group.items, FUSE_OPTIONS); + .map((group: any) => { + const items = get(group, itemsKey); + const fuse = new Fuse(items, { ...FUSE_OPTIONS, keys: keysToSearch }); 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/JSEditor/JSAddState.tsx b/app/client/src/pages/Editor/JSEditor/JSAddState.tsx index 8d58080709a8..bd0e2dd410a7 100644 --- a/app/client/src/pages/Editor/JSEditor/JSAddState.tsx +++ b/app/client/src/pages/Editor/JSEditor/JSAddState.tsx @@ -4,7 +4,7 @@ import AddJS from "pages/Editor/IDE/EditorPane/JS/Add"; const JSAddState = () => { return ( - + { return ( - +