From 6e1f6c3f0b2c9bc5981c352fa513df9dc2c41a32 Mon Sep 17 00:00:00 2001 From: anastasiia Date: Thu, 18 Jan 2024 12:15:31 -0500 Subject: [PATCH] studio navigation using left sidebar --- .../CurrentTabContent/Header/TabButton.tsx | 41 ++++- .../StudioTab/Conversation/Input/index.tsx | 8 +- .../StudioTab/Conversation/index.tsx | 2 + .../CurrentTabContent/StudioTab/index.tsx | 6 +- .../ConversationsDropdown.tsx | 10 +- .../{ => Conversations}/CoversationEntry.tsx | 20 ++- .../index.tsx} | 50 ++---- .../NavPanel/{ => Repo}/RepoDropdown.tsx | 26 +-- .../NavPanel/{ => Repo}/RepoEntry.tsx | 29 ++-- .../NavPanel/{Repo.tsx => Repo/index.tsx} | 61 +++---- .../LeftSidebar/NavPanel/StudioEntry.tsx | 36 ----- .../NavPanel/Studios/StudioEntry.tsx | 149 ++++++++++++++++++ .../NavPanel/Studios/StudioSubItem.tsx | 68 ++++++++ .../{ => Studios}/StudiosDropdown.tsx | 10 +- .../{Studios.tsx => Studios/index.tsx} | 46 ++---- .../Project/LeftSidebar/NavPanel/index.tsx | 80 ++++------ .../RegexSearchPanel/Results/CodeResult.tsx | 30 ++-- .../RegexSearchPanel/Results/FileResult.tsx | 20 +-- .../RegexSearchPanel/Results/RepoResult.tsx | 19 +-- .../LeftSidebar/RegexSearchPanel/index.tsx | 6 +- client/src/Project/LeftSidebar/index.tsx | 48 +++--- .../MarkdownWithCode/FolderChip.tsx | 3 +- client/src/hooks/useEnterKey.ts | 16 ++ client/src/hooks/useNavPanel.ts | 50 ++++++ client/src/icons/Prompt.tsx | 18 +++ client/src/icons/index.ts | 1 + client/src/locales/en.json | 3 +- client/src/locales/es.json | 3 +- client/src/locales/it.json | 3 +- client/src/locales/ja.json | 3 +- client/src/locales/zh-CN.json | 3 +- client/src/services/api.ts | 4 +- client/tailwind.config.cjs | 1 + 33 files changed, 541 insertions(+), 332 deletions(-) rename client/src/Project/LeftSidebar/NavPanel/{ => Conversations}/ConversationsDropdown.tsx (74%) rename client/src/Project/LeftSidebar/NavPanel/{ => Conversations}/CoversationEntry.tsx (64%) rename client/src/Project/LeftSidebar/NavPanel/{Conversations.tsx => Conversations/index.tsx} (69%) rename client/src/Project/LeftSidebar/NavPanel/{ => Repo}/RepoDropdown.tsx (90%) rename client/src/Project/LeftSidebar/NavPanel/{ => Repo}/RepoEntry.tsx (89%) rename client/src/Project/LeftSidebar/NavPanel/{Repo.tsx => Repo/index.tsx} (78%) delete mode 100644 client/src/Project/LeftSidebar/NavPanel/StudioEntry.tsx create mode 100644 client/src/Project/LeftSidebar/NavPanel/Studios/StudioEntry.tsx create mode 100644 client/src/Project/LeftSidebar/NavPanel/Studios/StudioSubItem.tsx rename client/src/Project/LeftSidebar/NavPanel/{ => Studios}/StudiosDropdown.tsx (73%) rename client/src/Project/LeftSidebar/NavPanel/{Studios.tsx => Studios/index.tsx} (72%) create mode 100644 client/src/hooks/useEnterKey.ts create mode 100644 client/src/hooks/useNavPanel.ts create mode 100644 client/src/icons/Prompt.tsx diff --git a/client/src/Project/CurrentTabContent/Header/TabButton.tsx b/client/src/Project/CurrentTabContent/Header/TabButton.tsx index 64fdf7c0b7..cd61ddfdf9 100644 --- a/client/src/Project/CurrentTabContent/Header/TabButton.tsx +++ b/client/src/Project/CurrentTabContent/Header/TabButton.tsx @@ -38,6 +38,16 @@ type Props = TabType & { tokenRange?: string; focusedPanel: 'left' | 'right'; isTemp?: boolean; + studioId?: string; + initialRanges?: [number, number][]; + isFileInContext?: boolean; + conversationId?: string; + initialQuery?: { + path: string; + lines: [number, number]; + repoRef: string; + branch?: string | null | undefined; + }; }; const closeTabShortcut = ['cmd', 'W']; @@ -58,6 +68,11 @@ const TabButton = ({ tokenRange, focusedPanel, isTemp, + studioId, + initialRanges, + isFileInContext, + conversationId, + initialQuery, }: Props) => { const { t } = useTranslation(); const { closeTab, setActiveLeftTab, setActiveRightTab, setFocusedPanel } = @@ -140,6 +155,11 @@ const TabButton = ({ branch, scrollToLine, tokenRange, + studioId, + initialRanges, + isFileInContext, + conversationId, + initialQuery, }, side, }; @@ -170,9 +190,28 @@ const TabButton = ({ branch, scrollToLine, tokenRange, + studioId, + initialRanges, + isFileInContext, + conversationId, + initialQuery, }); setFocusedPanel(side); - }, [path, repoRef, tabKey, side, branch, scrollToLine, tokenRange, title]); + }, [ + path, + repoRef, + tabKey, + side, + branch, + scrollToLine, + tokenRange, + title, + studioId, + initialRanges, + isFileInContext, + conversationId, + initialQuery, + ]); return ( ; isTokenLimitExceeded: boolean; isLast: boolean; + isActiveTab: boolean; side: 'left' | 'right'; templates?: StudioTemplateType[]; setIsDropdownShown: (b: boolean) => void; @@ -55,6 +56,7 @@ const ConversationInput = ({ templates, setIsDropdownShown, templatesRef, + isActiveTab, }: Props) => { const { t } = useTranslation(); const { envConfig } = useContext(EnvContext); @@ -65,9 +67,11 @@ const ConversationInput = ({ const handleChange = useCallback( (e: ChangeEvent) => { - onMessageChange(e.target.value, i); + if (isActiveTab) { + onMessageChange(e.target.value, i); + } }, - [i, onMessageChange], + [i, onMessageChange, isActiveTab], ); useEffect(() => { diff --git a/client/src/Project/CurrentTabContent/StudioTab/Conversation/index.tsx b/client/src/Project/CurrentTabContent/StudioTab/Conversation/index.tsx index c35ee9d1e0..9792631f93 100644 --- a/client/src/Project/CurrentTabContent/StudioTab/Conversation/index.tsx +++ b/client/src/Project/CurrentTabContent/StudioTab/Conversation/index.tsx @@ -141,6 +141,7 @@ const Conversation = ({ isLast={i === studioData.conversation.length - 1} side={side} templates={templates} + isActiveTab={isActiveTab} setIsDropdownShown={setIsDropdownShown} /> ))} @@ -198,6 +199,7 @@ const Conversation = ({ templates={templates} setIsDropdownShown={setIsDropdownShown} templatesRef={templatesRef} + isActiveTab={isActiveTab} /> )} diff --git a/client/src/Project/CurrentTabContent/StudioTab/index.tsx b/client/src/Project/CurrentTabContent/StudioTab/index.tsx index 221aa8098d..55ab4f0dc8 100644 --- a/client/src/Project/CurrentTabContent/StudioTab/index.tsx +++ b/client/src/Project/CurrentTabContent/StudioTab/index.tsx @@ -17,11 +17,7 @@ import Dropdown from '../../../components/Dropdown'; import { checkEventKeys } from '../../../utils/keyboardUtils'; import useKeyboardNavigation from '../../../hooks/useKeyboardNavigation'; import { TabsContext } from '../../../context/tabsContext'; -import { - ChatTabType, - SettingSections, - StudioTabType, -} from '../../../types/general'; +import { SettingSections, StudioTabType } from '../../../types/general'; import { ProjectContext } from '../../../context/projectContext'; import { CommandBarContext } from '../../../context/commandBarContext'; import { openInSplitViewShortcut } from '../../../consts/commandBar'; diff --git a/client/src/Project/LeftSidebar/NavPanel/ConversationsDropdown.tsx b/client/src/Project/LeftSidebar/NavPanel/Conversations/ConversationsDropdown.tsx similarity index 74% rename from client/src/Project/LeftSidebar/NavPanel/ConversationsDropdown.tsx rename to client/src/Project/LeftSidebar/NavPanel/Conversations/ConversationsDropdown.tsx index f6e0cd7061..1930bdab5a 100644 --- a/client/src/Project/LeftSidebar/NavPanel/ConversationsDropdown.tsx +++ b/client/src/Project/LeftSidebar/NavPanel/Conversations/ConversationsDropdown.tsx @@ -1,10 +1,10 @@ import { memo, useCallback, useContext } from 'react'; import { useTranslation } from 'react-i18next'; -import DropdownSection from '../../../components/Dropdown/Section'; -import SectionItem from '../../../components/Dropdown/Section/SectionItem'; -import { TrashCanIcon } from '../../../icons'; -import { deleteConversation } from '../../../services/api'; -import { ProjectContext } from '../../../context/projectContext'; +import DropdownSection from '../../../../components/Dropdown/Section'; +import SectionItem from '../../../../components/Dropdown/Section/SectionItem'; +import { TrashCanIcon } from '../../../../icons'; +import { deleteConversation } from '../../../../services/api'; +import { ProjectContext } from '../../../../context/projectContext'; type Props = {}; diff --git a/client/src/Project/LeftSidebar/NavPanel/CoversationEntry.tsx b/client/src/Project/LeftSidebar/NavPanel/Conversations/CoversationEntry.tsx similarity index 64% rename from client/src/Project/LeftSidebar/NavPanel/CoversationEntry.tsx rename to client/src/Project/LeftSidebar/NavPanel/Conversations/CoversationEntry.tsx index 1586f043bd..b7d59c25d6 100644 --- a/client/src/Project/LeftSidebar/NavPanel/CoversationEntry.tsx +++ b/client/src/Project/LeftSidebar/NavPanel/Conversations/CoversationEntry.tsx @@ -1,20 +1,30 @@ import { memo, useCallback, useContext } from 'react'; -import { ConversationShortType } from '../../../types/api'; -import { TabsContext } from '../../../context/tabsContext'; -import { TabTypesEnum } from '../../../types/general'; +import { ConversationShortType } from '../../../../types/api'; +import { TabsContext } from '../../../../context/tabsContext'; +import { TabTypesEnum } from '../../../../types/general'; +import { useEnterKey } from '../../../../hooks/useEnterKey'; type Props = ConversationShortType & { index: string; focusedIndex: string; + isLeftSidebarFocused: boolean; }; -const ConversationEntry = ({ title, id, index, focusedIndex }: Props) => { +const ConversationEntry = ({ + title, + id, + index, + focusedIndex, + isLeftSidebarFocused, +}: Props) => { const { openNewTab } = useContext(TabsContext.Handlers); const handleClick = useCallback(() => { openNewTab({ type: TabTypesEnum.CHAT, conversationId: id, title }); }, [openNewTab, id, title]); + useEnterKey(handleClick, focusedIndex !== index || !isLeftSidebarFocused); + return ( { ? 'bg-bg-sub-hover text-label-title' : 'text-label-base' } - hover:bg-bg-base-hover hover:text-label-title active:bg-transparent pl-[2.625rem]`} + hover:bg-bg-base-hover hover:text-label-title active:bg-transparent pl-10.5`} onClick={handleClick} data-node-index={index} > diff --git a/client/src/Project/LeftSidebar/NavPanel/Conversations.tsx b/client/src/Project/LeftSidebar/NavPanel/Conversations/index.tsx similarity index 69% rename from client/src/Project/LeftSidebar/NavPanel/Conversations.tsx rename to client/src/Project/LeftSidebar/NavPanel/Conversations/index.tsx index d3072fc5f5..982aefdae2 100644 --- a/client/src/Project/LeftSidebar/NavPanel/Conversations.tsx +++ b/client/src/Project/LeftSidebar/NavPanel/Conversations/index.tsx @@ -1,27 +1,19 @@ -import React, { - Dispatch, - memo, - SetStateAction, - useCallback, - useEffect, - useRef, - MouseEvent, - useContext, -} from 'react'; +import React, { Dispatch, memo, SetStateAction, useContext } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import Dropdown from '../../../components/Dropdown'; +import Dropdown from '../../../../components/Dropdown'; import { ArrowTriangleBottomIcon, ChatBubblesIcon, MoreHorizontalIcon, -} from '../../../icons'; -import Button from '../../../components/Button'; -import { ProjectContext } from '../../../context/projectContext'; +} from '../../../../icons'; +import Button from '../../../../components/Button'; +import { ProjectContext } from '../../../../context/projectContext'; +import { useNavPanel } from '../../../../hooks/useNavPanel'; import ConversationsDropdown from './ConversationsDropdown'; import ConversationEntry from './CoversationEntry'; type Props = { - setExpanded: Dispatch>; + setExpanded: Dispatch>; isExpanded: boolean; focusedIndex: string; index: string; @@ -37,27 +29,8 @@ const ConversationsNav = ({ }: Props) => { const { t } = useTranslation(); const { project } = useContext(ProjectContext.Current); - const containerRef = useRef(null); - - const toggleExpanded = useCallback(() => { - setExpanded((prev) => (prev === 0 ? -1 : 0)); - }, []); - - useEffect(() => { - if (isExpanded) { - // containerRef.current?.scrollIntoView({ block: 'nearest' }); - } - }, [isExpanded]); - - const noPropagate = useCallback((e?: MouseEvent) => { - e?.stopPropagation(); - }, []); - - useEffect(() => { - if (focusedIndex === index && containerRef.current) { - containerRef.current.scrollIntoView({ block: 'nearest' }); - } - }, [focusedIndex, index]); + const { containerRef, toggleExpanded, noPropagate, isLeftSidebarFocused } = + useNavPanel(index, setExpanded, isExpanded, focusedIndex); return (
@@ -108,12 +81,13 @@ const ConversationsNav = ({ {isExpanded && (
- {project?.conversations.map((c, ci) => ( + {project?.conversations.map((c) => ( ))}
diff --git a/client/src/Project/LeftSidebar/NavPanel/RepoDropdown.tsx b/client/src/Project/LeftSidebar/NavPanel/Repo/RepoDropdown.tsx similarity index 90% rename from client/src/Project/LeftSidebar/NavPanel/RepoDropdown.tsx rename to client/src/Project/LeftSidebar/NavPanel/Repo/RepoDropdown.tsx index 69e7be9941..5241505f26 100644 --- a/client/src/Project/LeftSidebar/NavPanel/RepoDropdown.tsx +++ b/client/src/Project/LeftSidebar/NavPanel/Repo/RepoDropdown.tsx @@ -9,29 +9,29 @@ import { useEffect, } from 'react'; import { useTranslation } from 'react-i18next'; -import DropdownSection from '../../../components/Dropdown/Section'; -import SectionItem from '../../../components/Dropdown/Section/SectionItem'; +import DropdownSection from '../../../../components/Dropdown/Section'; +import SectionItem from '../../../../components/Dropdown/Section/SectionItem'; import { ArrowTriangleBottomIcon, BranchIcon, RefreshIcon, TrashCanIcon, -} from '../../../icons'; +} from '../../../../icons'; import { changeRepoBranch, indexRepoBranch, removeRepoFromProject, syncRepo, -} from '../../../services/api'; -import { DeviceContext } from '../../../context/deviceContext'; -import SpinLoaderContainer from '../../../components/Loaders/SpinnerLoader'; -import SectionLabel from '../../../components/Dropdown/Section/SectionLabel'; -import Button from '../../../components/Button'; -import { ProjectContext } from '../../../context/projectContext'; -import { PersonalQuotaContext } from '../../../context/personalQuotaContext'; -import { RepoIndexingStatusType } from '../../../types/general'; -import { RepositoriesContext } from '../../../context/repositoriesContext'; -import { UIContext } from '../../../context/uiContext'; +} from '../../../../services/api'; +import { DeviceContext } from '../../../../context/deviceContext'; +import SpinLoaderContainer from '../../../../components/Loaders/SpinnerLoader'; +import SectionLabel from '../../../../components/Dropdown/Section/SectionLabel'; +import Button from '../../../../components/Button'; +import { ProjectContext } from '../../../../context/projectContext'; +import { PersonalQuotaContext } from '../../../../context/personalQuotaContext'; +import { RepoIndexingStatusType } from '../../../../types/general'; +import { RepositoriesContext } from '../../../../context/repositoriesContext'; +import { UIContext } from '../../../../context/uiContext'; type Props = { repoRef: string; diff --git a/client/src/Project/LeftSidebar/NavPanel/RepoEntry.tsx b/client/src/Project/LeftSidebar/NavPanel/Repo/RepoEntry.tsx similarity index 89% rename from client/src/Project/LeftSidebar/NavPanel/RepoEntry.tsx rename to client/src/Project/LeftSidebar/NavPanel/Repo/RepoEntry.tsx index 9b4415e921..859a62a872 100644 --- a/client/src/Project/LeftSidebar/NavPanel/RepoEntry.tsx +++ b/client/src/Project/LeftSidebar/NavPanel/Repo/RepoEntry.tsx @@ -6,18 +6,17 @@ import React, { useRef, useState, } from 'react'; -import { ChevronRightIcon, EyeCutIcon, FolderIcon } from '../../../icons'; -import FileIcon from '../../../components/FileIcon'; -import { DirectoryEntry } from '../../../types/api'; -import { TabsContext } from '../../../context/tabsContext'; +import { ChevronRightIcon, EyeCutIcon, FolderIcon } from '../../../../icons'; +import FileIcon from '../../../../components/FileIcon'; +import { DirectoryEntry } from '../../../../types/api'; +import { TabsContext } from '../../../../context/tabsContext'; import { RepoIndexingStatusType, SyncStatus, TabTypesEnum, -} from '../../../types/general'; -import SpinLoaderContainer from '../../../components/Loaders/SpinnerLoader'; -import { UIContext } from '../../../context/uiContext'; -import useKeyboardNavigation from '../../../hooks/useKeyboardNavigation'; +} from '../../../../types/general'; +import SpinLoaderContainer from '../../../../components/Loaders/SpinnerLoader'; +import { useEnterKey } from '../../../../hooks/useEnterKey'; type Props = { name: string; @@ -34,6 +33,7 @@ type Props = { indexingData?: RepoIndexingStatusType; focusedIndex: string; index: string; + isLeftSidebarFocused: boolean; }; const RepoEntry = ({ @@ -51,9 +51,9 @@ const RepoEntry = ({ indexingData, focusedIndex, index, + isLeftSidebarFocused, }: Props) => { const { openNewTab } = useContext(TabsContext.Handlers); - const { isLeftSidebarFocused } = useContext(UIContext.Focus); const [isOpen, setOpen] = useState( defaultOpen || (currentPath && currentPath.startsWith(fullPath)), ); @@ -110,15 +110,7 @@ const RepoEntry = ({ } }, [isDirectory, fullPath, openNewTab, repoRef, branch]); - const handleKeyEvent = useCallback( - (e: KeyboardEvent) => { - if (e.key === 'Enter') { - handleClick(); - } - }, - [handleClick], - ); - useKeyboardNavigation(handleKeyEvent, focusedIndex !== index); + useEnterKey(handleClick, focusedIndex !== index || !isLeftSidebarFocused); useEffect(() => { if (focusedIndex === index && ref.current) { @@ -220,6 +212,7 @@ const RepoEntry = ({ branch={branch} focusedIndex={focusedIndex} index={`${index}-${sii}`} + isLeftSidebarFocused={isLeftSidebarFocused} /> ))}
diff --git a/client/src/Project/LeftSidebar/NavPanel/Repo.tsx b/client/src/Project/LeftSidebar/NavPanel/Repo/index.tsx similarity index 78% rename from client/src/Project/LeftSidebar/NavPanel/Repo.tsx rename to client/src/Project/LeftSidebar/NavPanel/Repo/index.tsx index ce3c61bb95..3783cd7eb4 100644 --- a/client/src/Project/LeftSidebar/NavPanel/Repo.tsx +++ b/client/src/Project/LeftSidebar/NavPanel/Repo/index.tsx @@ -5,34 +5,32 @@ import React, { useCallback, useEffect, useMemo, - useRef, useState, - MouseEvent, } from 'react'; import { useTranslation } from 'react-i18next'; -import { DirectoryEntry } from '../../../types/api'; -import { getFolderContent } from '../../../services/api'; -import { splitPath } from '../../../utils'; -import GitHubIcon from '../../../icons/GitHubIcon'; -import Dropdown from '../../../components/Dropdown'; +import { DirectoryEntry } from '../../../../types/api'; +import { getFolderContent } from '../../../../services/api'; +import { splitPath } from '../../../../utils'; +import GitHubIcon from '../../../../icons/GitHubIcon'; +import Dropdown from '../../../../components/Dropdown'; import { ArrowTriangleBottomIcon, HardDriveIcon, MoreHorizontalIcon, -} from '../../../icons'; -import Button from '../../../components/Button'; -import { RepoIndexingStatusType, SyncStatus } from '../../../types/general'; -import SpinLoaderContainer from '../../../components/Loaders/SpinnerLoader'; -import Tooltip from '../../../components/Tooltip'; -import { repoStatusMap } from '../../../consts/general'; +} from '../../../../icons'; +import Button from '../../../../components/Button'; +import { RepoIndexingStatusType, SyncStatus } from '../../../../types/general'; +import SpinLoaderContainer from '../../../../components/Loaders/SpinnerLoader'; +import Tooltip from '../../../../components/Tooltip'; +import { repoStatusMap } from '../../../../consts/general'; +import { useNavPanel } from '../../../../hooks/useNavPanel'; import RepoEntry from './RepoEntry'; import RepoDropdown from './RepoDropdown'; type Props = { repoRef: string; - setExpanded: Dispatch>; + setExpanded: Dispatch>; isExpanded: boolean; - i: number; projectId: string; lastIndex: string; currentPath?: string; @@ -41,14 +39,13 @@ type Props = { indexedBranches: string[]; indexingData?: RepoIndexingStatusType; focusedIndex: string; - index: number; + index: string; }; const reactRoot = document.getElementById('root')!; const RepoNav = ({ repoRef, - i, isExpanded, setExpanded, branch, @@ -63,7 +60,8 @@ const RepoNav = ({ }: Props) => { const { t } = useTranslation(); const [files, setFiles] = useState([]); - const containerRef = useRef(null); + const { containerRef, toggleExpanded, noPropagate, isLeftSidebarFocused } = + useNavPanel(index, setExpanded, isExpanded, focusedIndex); const fetchFiles = useCallback( async (path?: string) => { @@ -90,16 +88,6 @@ const RepoNav = ({ refetchParentFolder(); }, [refetchParentFolder]); - const toggleExpanded = useCallback(() => { - setExpanded((prev) => (prev === i ? -1 : i)); - }, [i]); - - useEffect(() => { - if (isExpanded) { - // containerRef.current?.scrollIntoView({ block: 'nearest' }); - } - }, [isExpanded]); - const dropdownComponentProps = useMemo(() => { return { key: repoRef, @@ -111,10 +99,6 @@ const RepoNav = ({ }; }, [projectId, repoRef, branch, indexedBranches, allBranches]); - const noPropagate = useCallback((e?: MouseEvent) => { - e?.stopPropagation(); - }, []); - const isIndexing = useMemo(() => { if (!indexingData) { return false; @@ -126,12 +110,6 @@ const RepoNav = ({ ].includes(indexingData.status); }, [indexingData]); - useEffect(() => { - if (focusedIndex === index.toString() && containerRef.current) { - containerRef.current.scrollIntoView({ block: 'nearest' }); - } - }, [focusedIndex, index]); - return (
{isIndexing && indexingData ? ( @@ -215,6 +191,7 @@ const RepoNav = ({ indexingData={indexingData} focusedIndex={focusedIndex} index={`${index}-${fi}`} + isLeftSidebarFocused={isLeftSidebarFocused} /> ))}
diff --git a/client/src/Project/LeftSidebar/NavPanel/StudioEntry.tsx b/client/src/Project/LeftSidebar/NavPanel/StudioEntry.tsx deleted file mode 100644 index dca9915a9f..0000000000 --- a/client/src/Project/LeftSidebar/NavPanel/StudioEntry.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { memo, useCallback, useContext } from 'react'; -import { CodeStudioType } from '../../../types/api'; -import { TabsContext } from '../../../context/tabsContext'; -import { TabTypesEnum } from '../../../types/general'; - -type Props = CodeStudioType & { - index: string; - focusedIndex: string; -}; - -const StudioEntry = ({ id, index, focusedIndex, name }: Props) => { - const { openNewTab } = useContext(TabsContext.Handlers); - - const handleClick = useCallback(() => { - openNewTab({ type: TabTypesEnum.STUDIO, studioId: id, title: name }); - }, [openNewTab, id, name]); - - return ( -
- {name} - - ); -}; - -export default memo(StudioEntry); diff --git a/client/src/Project/LeftSidebar/NavPanel/Studios/StudioEntry.tsx b/client/src/Project/LeftSidebar/NavPanel/Studios/StudioEntry.tsx new file mode 100644 index 0000000000..7139093e0e --- /dev/null +++ b/client/src/Project/LeftSidebar/NavPanel/Studios/StudioEntry.tsx @@ -0,0 +1,149 @@ +import React, { + Dispatch, + memo, + SetStateAction, + useCallback, + useEffect, +} from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { CodeStudioType } from '../../../../types/api'; +import { CodeStudioIcon, PromptIcon, RangeIcon } from '../../../../icons'; +import ChevronRight from '../../../../icons/ChevronRight'; +import TokenUsage from '../../../../components/TokenUsage'; +import { TOKEN_LIMIT } from '../../../../consts/codeStudio'; +import { useEnterKey } from '../../../../hooks/useEnterKey'; +import FileIcon from '../../../../components/FileIcon'; +import { humanNumber, splitPath } from '../../../../utils'; +import Tooltip from '../../../../components/Tooltip'; +import StudioSubItem from './StudioSubItem'; + +type Props = CodeStudioType & { + index: string; + focusedIndex: string; + expandedIndex: string; + setExpandedIndex: Dispatch>; + isLeftSidebarFocused: boolean; +}; + +const StudioEntry = ({ + id, + index, + focusedIndex, + name, + expandedIndex, + setExpandedIndex, + context, + token_counts, + isLeftSidebarFocused, +}: Props) => { + const { t } = useTranslation(); + + useEffect(() => { + if (focusedIndex.startsWith(index)) { + setExpandedIndex(index); + } + }, [index, focusedIndex]); + + const handleExpand = useCallback(() => { + setExpandedIndex((prev) => (prev === index ? '' : index)); + }, [index]); + + useEnterKey(handleExpand, focusedIndex !== index || !isLeftSidebarFocused); + + return ( +
+ + + + {name} + + {expandedIndex === index && ( +
+
+ + + + Prompts + + + + {!!context.length && ( +
+ Context files +
+ )} + {context.map((f, i) => ( + + + {splitPath(f.path).pop()} + {!!f.ranges.length && ( + + + + )} + 500 + ? 'text-yellow' + : (token_counts.per_file[i] || 0) < 500 + ? 'text-green' + : 'text-red' + }`} + > + {humanNumber(token_counts.per_file[i] || 0)} + + + ))} +
+ )} +
+ ); +}; + +export default memo(StudioEntry); diff --git a/client/src/Project/LeftSidebar/NavPanel/Studios/StudioSubItem.tsx b/client/src/Project/LeftSidebar/NavPanel/Studios/StudioSubItem.tsx new file mode 100644 index 0000000000..eca0c8a4df --- /dev/null +++ b/client/src/Project/LeftSidebar/NavPanel/Studios/StudioSubItem.tsx @@ -0,0 +1,68 @@ +import React, { memo, PropsWithChildren, useCallback, useContext } from 'react'; +import { TabsContext } from '../../../../context/tabsContext'; +import { TabTypesEnum } from '../../../../types/general'; +import { useEnterKey } from '../../../../hooks/useEnterKey'; +import { Range } from '../../../../types/results'; + +type Props = { + index: string; + focusedIndex: string; + studioId: string; + studioName: string; + path?: string; + repoRef?: string; + branch?: string | null; + isLeftSidebarFocused: boolean; + ranges?: Range[]; +}; + +const StudioSubItem = ({ + index, + focusedIndex, + children, + studioId, + studioName, + path, + repoRef, + branch, + isLeftSidebarFocused, + ranges, +}: PropsWithChildren) => { + const { openNewTab } = useContext(TabsContext.Handlers); + + const handleClick = useCallback(() => { + if (!path) { + openNewTab({ type: TabTypesEnum.STUDIO, studioId, title: studioName }); + } else if (path && repoRef) { + openNewTab({ + type: TabTypesEnum.FILE, + path, + repoRef, + branch, + studioId, + isFileInContext: true, + initialRanges: ranges?.map((r) => [r.start, r.end]), + }); + } + }, [path, openNewTab, studioId, studioName, repoRef, branch, ranges]); + + useEnterKey(handleClick, focusedIndex !== index || !isLeftSidebarFocused); + + return ( + + {children} + + ); +}; + +export default memo(StudioSubItem); diff --git a/client/src/Project/LeftSidebar/NavPanel/StudiosDropdown.tsx b/client/src/Project/LeftSidebar/NavPanel/Studios/StudiosDropdown.tsx similarity index 73% rename from client/src/Project/LeftSidebar/NavPanel/StudiosDropdown.tsx rename to client/src/Project/LeftSidebar/NavPanel/Studios/StudiosDropdown.tsx index 42dace53dd..6fe3548ec7 100644 --- a/client/src/Project/LeftSidebar/NavPanel/StudiosDropdown.tsx +++ b/client/src/Project/LeftSidebar/NavPanel/Studios/StudiosDropdown.tsx @@ -1,10 +1,10 @@ import { memo, useCallback, useContext } from 'react'; import { useTranslation } from 'react-i18next'; -import DropdownSection from '../../../components/Dropdown/Section'; -import SectionItem from '../../../components/Dropdown/Section/SectionItem'; -import { TrashCanIcon } from '../../../icons'; -import { deleteCodeStudio } from '../../../services/api'; -import { ProjectContext } from '../../../context/projectContext'; +import DropdownSection from '../../../../components/Dropdown/Section'; +import SectionItem from '../../../../components/Dropdown/Section/SectionItem'; +import { TrashCanIcon } from '../../../../icons'; +import { deleteCodeStudio } from '../../../../services/api'; +import { ProjectContext } from '../../../../context/projectContext'; type Props = {}; diff --git a/client/src/Project/LeftSidebar/NavPanel/Studios.tsx b/client/src/Project/LeftSidebar/NavPanel/Studios/index.tsx similarity index 72% rename from client/src/Project/LeftSidebar/NavPanel/Studios.tsx rename to client/src/Project/LeftSidebar/NavPanel/Studios/index.tsx index dc4a6eba48..3d77b22c75 100644 --- a/client/src/Project/LeftSidebar/NavPanel/Studios.tsx +++ b/client/src/Project/LeftSidebar/NavPanel/Studios/index.tsx @@ -7,22 +7,25 @@ import React, { useRef, MouseEvent, useContext, + useState, } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import Dropdown from '../../../components/Dropdown'; +import Dropdown from '../../../../components/Dropdown'; import { ArrowTriangleBottomIcon, - ChatBubblesIcon, CodeStudioIcon, MoreHorizontalIcon, -} from '../../../icons'; -import Button from '../../../components/Button'; -import { ProjectContext } from '../../../context/projectContext'; +} from '../../../../icons'; +import Button from '../../../../components/Button'; +import { ProjectContext } from '../../../../context/projectContext'; +import { useEnterKey } from '../../../../hooks/useEnterKey'; +import { UIContext } from '../../../../context/uiContext'; +import { useNavPanel } from '../../../../hooks/useNavPanel'; import StudioEntry from './StudioEntry'; import StudiosDropdown from './StudiosDropdown'; type Props = { - setExpanded: Dispatch>; + setExpanded: Dispatch>; isExpanded: boolean; focusedIndex: string; index: string; @@ -37,28 +40,10 @@ const StudiosNav = ({ index, }: Props) => { const { t } = useTranslation(); + const [expandedIndex, setExpandedIndex] = useState(''); const { project } = useContext(ProjectContext.Current); - const containerRef = useRef(null); - - const toggleExpanded = useCallback(() => { - setExpanded((prev) => (prev === 0 ? -1 : 0)); - }, []); - - useEffect(() => { - if (isExpanded) { - // containerRef.current?.scrollIntoView({ block: 'nearest' }); - } - }, [isExpanded]); - - const noPropagate = useCallback((e?: MouseEvent) => { - e?.stopPropagation(); - }, []); - - useEffect(() => { - if (focusedIndex === index && containerRef.current) { - containerRef.current.scrollIntoView({ block: 'nearest' }); - } - }, [focusedIndex, index]); + const { containerRef, toggleExpanded, noPropagate, isLeftSidebarFocused } = + useNavPanel(index, setExpanded, isExpanded, focusedIndex); return (
@@ -109,12 +94,15 @@ const StudiosNav = ({ {isExpanded && (
- {project?.studios.map((c, ci) => ( + {project?.studios.map((c) => ( ))}
diff --git a/client/src/Project/LeftSidebar/NavPanel/index.tsx b/client/src/Project/LeftSidebar/NavPanel/index.tsx index 4471dd8a84..5e830adce3 100644 --- a/client/src/Project/LeftSidebar/NavPanel/index.tsx +++ b/client/src/Project/LeftSidebar/NavPanel/index.tsx @@ -9,10 +9,11 @@ import StudiosNav from './Studios'; type Props = { focusedIndex: string; + setFocusedIndex: (i: string) => void; }; -const NavPanel = ({ focusedIndex }: Props) => { - const [expanded, setExpanded] = useState(-1); +const NavPanel = ({ focusedIndex, setFocusedIndex }: Props) => { + const [expanded, setExpanded] = useState(''); const { project } = useContext(ProjectContext.Current); const { focusedPanel } = useContext(TabsContext.All); const { tab: leftTab } = useContext(TabsContext.CurrentLeft); @@ -20,34 +21,36 @@ const NavPanel = ({ focusedIndex }: Props) => { const { indexingStatus } = useContext(RepositoriesContext); const currentlyFocusedTab = useMemo(() => { - const focusedTab = focusedPanel === 'left' ? leftTab : rightTab; - if (focusedTab?.type === TabTypesEnum.FILE) { - return focusedTab; - } - return null; + return focusedPanel === 'left' ? leftTab : rightTab; }, [focusedPanel, leftTab, rightTab]); useEffect(() => { if (project?.repos.length === 1) { - setExpanded( - Number(!!project?.conversations.length) + - Number(!!project?.studios.length), - ); + setExpanded(`repo-${project.repos[0].repo.ref}`); } }, [project?.repos]); useEffect(() => { - if (currentlyFocusedTab?.repoRef) { - const repoIndex = project?.repos.findIndex( - (r) => r.repo.ref === currentlyFocusedTab.repoRef, - ); - if (repoIndex !== undefined && repoIndex > -1) { - setExpanded( - repoIndex + - Number(!!project?.conversations.length) + - Number(!!project?.studios.length), - ); + if ( + currentlyFocusedTab?.type === TabTypesEnum.FILE && + currentlyFocusedTab?.repoRef + ) { + if (!currentlyFocusedTab.studioId) { + setExpanded(`repo-${currentlyFocusedTab.repoRef}`); + } else { + setExpanded('studios'); + const { studioId, repoRef, branch, path } = currentlyFocusedTab; + setFocusedIndex(`studios-${studioId}-${path}-${repoRef}-${branch}`); } + } else if (currentlyFocusedTab?.type === TabTypesEnum.STUDIO) { + setExpanded('studios'); + setFocusedIndex(`studios-${currentlyFocusedTab.studioId}-prompts`); + } else if ( + currentlyFocusedTab?.type === TabTypesEnum.CHAT && + currentlyFocusedTab.conversationId + ) { + setExpanded('conversations'); + setFocusedIndex(`conversations-${currentlyFocusedTab.conversationId}`); } }, [currentlyFocusedTab]); @@ -56,17 +59,17 @@ const NavPanel = ({ focusedIndex }: Props) => { {!!project?.studios.length && ( )} {!!project?.conversations.length && ( )} {project?.repos.map((r, i) => ( @@ -74,17 +77,7 @@ const NavPanel = ({ focusedIndex }: Props) => { projectId={project?.id} key={r.repo.ref} setExpanded={setExpanded} - isExpanded={ - expanded === - i + - Number(!!project?.conversations.length) + - Number(!!project?.studios.length) - } - i={ - i + - Number(!!project?.conversations.length) + - Number(!!project?.studios.length) - } + isExpanded={expanded === `repo-${r.repo.ref}`} repoRef={r.repo.ref} branch={r.branch} lastIndex={r.repo.last_index} @@ -92,24 +85,13 @@ const NavPanel = ({ focusedIndex }: Props) => { indexingData={indexingStatus[r.repo.ref]} indexedBranches={r.repo.branch_filter?.select || []} currentPath={ + currentlyFocusedTab?.type === TabTypesEnum.FILE && currentlyFocusedTab?.repoRef === r.repo.ref ? currentlyFocusedTab?.path : undefined } focusedIndex={focusedIndex} - index={ - i + - (project?.studios.length - ? expanded === 0 - ? project?.studios.length + 1 - : 1 - : 0) + - (project?.conversations.length - ? expanded === (project?.studios.length ? 1 : 0) - ? project?.conversations.length + 1 - : 1 - : 0) - } + index={`repo-${r.repo.ref}`} /> ))}
diff --git a/client/src/Project/LeftSidebar/RegexSearchPanel/Results/CodeResult.tsx b/client/src/Project/LeftSidebar/RegexSearchPanel/Results/CodeResult.tsx index 8594b2c141..59c4ed735b 100644 --- a/client/src/Project/LeftSidebar/RegexSearchPanel/Results/CodeResult.tsx +++ b/client/src/Project/LeftSidebar/RegexSearchPanel/Results/CodeResult.tsx @@ -11,8 +11,8 @@ import { ChevronRightIcon } from '../../../../icons'; import FileIcon from '../../../../components/FileIcon'; import { TabsContext } from '../../../../context/tabsContext'; import { TabTypesEnum } from '../../../../types/general'; -import useKeyboardNavigation from '../../../../hooks/useKeyboardNavigation'; import { UIContext } from '../../../../context/uiContext'; +import { useEnterKey } from '../../../../hooks/useEnterKey'; import CodeLine from './CodeLine'; type Props = { @@ -48,25 +48,15 @@ const CodeResult = ({ } }, [focusedIndex, index]); - const handleKeyEvent = useCallback( - (e: KeyboardEvent) => { - if (e.key === 'Enter') { - e.stopPropagation(); - e.preventDefault(); - openNewTab({ - type: TabTypesEnum.FILE, - path: relative_path, - repoRef: repo_ref, - scrollToLine: `${snippets[0].line_range.start}_${snippets[0].line_range.end}`, - }); - } - }, - [repo_ref, relative_path, openNewTab], - ); - useKeyboardNavigation( - handleKeyEvent, - focusedIndex !== index || !isLeftSidebarFocused, - ); + const handleEnter = useCallback(() => { + openNewTab({ + type: TabTypesEnum.FILE, + path: relative_path, + repoRef: repo_ref, + scrollToLine: `${snippets[0].line_range.start}_${snippets[0].line_range.end}`, + }); + }, [repo_ref, relative_path, openNewTab]); + useEnterKey(handleEnter, focusedIndex !== index || !isLeftSidebarFocused); const handleClick = useCallback(() => { openNewTab({ diff --git a/client/src/Project/LeftSidebar/RegexSearchPanel/Results/FileResult.tsx b/client/src/Project/LeftSidebar/RegexSearchPanel/Results/FileResult.tsx index 500a430eeb..4f8b539b58 100644 --- a/client/src/Project/LeftSidebar/RegexSearchPanel/Results/FileResult.tsx +++ b/client/src/Project/LeftSidebar/RegexSearchPanel/Results/FileResult.tsx @@ -11,10 +11,10 @@ import { TabTypesEnum } from '../../../../types/general'; import { TabsContext } from '../../../../context/tabsContext'; import { DirectoryEntry, RepoFileNameItem } from '../../../../types/api'; import { FolderIcon } from '../../../../icons'; -import useKeyboardNavigation from '../../../../hooks/useKeyboardNavigation'; import { UIContext } from '../../../../context/uiContext'; import { getFolderContent } from '../../../../services/api'; -import RepoEntry from '../../NavPanel/RepoEntry'; +import RepoEntry from '../../NavPanel/Repo/RepoEntry'; +import { useEnterKey } from '../../../../hooks/useEnterKey'; type Props = { relative_path: RepoFileNameItem; @@ -80,20 +80,7 @@ const FileResult = ({ } }, [relative_path, repo_ref, is_dir, openNewTab]); - const handleKeyEvent = useCallback( - (e: KeyboardEvent) => { - if (e.key === 'Enter') { - e.stopPropagation(); - e.preventDefault(); - handleClick(); - } - }, - [handleClick], - ); - useKeyboardNavigation( - handleKeyEvent, - focusedIndex !== index || !isLeftSidebarFocused, - ); + useEnterKey(handleClick, focusedIndex !== index || !isLeftSidebarFocused); return ( ))}
diff --git a/client/src/Project/LeftSidebar/RegexSearchPanel/Results/RepoResult.tsx b/client/src/Project/LeftSidebar/RegexSearchPanel/Results/RepoResult.tsx index 79bf033aae..c96e388070 100644 --- a/client/src/Project/LeftSidebar/RegexSearchPanel/Results/RepoResult.tsx +++ b/client/src/Project/LeftSidebar/RegexSearchPanel/Results/RepoResult.tsx @@ -10,9 +10,9 @@ import { HardDriveIcon } from '../../../../icons'; import { splitPath } from '../../../../utils'; import { DirectoryEntry } from '../../../../types/api'; import { getFolderContent } from '../../../../services/api'; -import RepoEntry from '../../NavPanel/RepoEntry'; -import useKeyboardNavigation from '../../../../hooks/useKeyboardNavigation'; +import RepoEntry from '../../NavPanel/Repo/RepoEntry'; import { UIContext } from '../../../../context/uiContext'; +import { useEnterKey } from '../../../../hooks/useEnterKey'; type Props = { repoRef: string; @@ -55,18 +55,8 @@ const RepoResult = ({ repoRef, isExpandable, index, focusedIndex }: Props) => { } }, [isExpandable]); - const handleKeyEvent = useCallback( - (e: KeyboardEvent) => { - if (e.key === 'Enter') { - e.stopPropagation(); - e.preventDefault(); - onClick(); - } - }, - [onClick], - ); - useKeyboardNavigation( - handleKeyEvent, + useEnterKey( + onClick, focusedIndex !== index.toString() || !isExpandable || !isLeftSidebarFocused, ); @@ -114,6 +104,7 @@ const RepoResult = ({ repoRef, isExpandable, index, focusedIndex }: Props) => { focusedIndex={focusedIndex} index={`${index}-${fi}`} lastIndex={''} + isLeftSidebarFocused={isLeftSidebarFocused} /> ))} diff --git a/client/src/Project/LeftSidebar/RegexSearchPanel/index.tsx b/client/src/Project/LeftSidebar/RegexSearchPanel/index.tsx index 20f76524de..b2f457a87d 100644 --- a/client/src/Project/LeftSidebar/RegexSearchPanel/index.tsx +++ b/client/src/Project/LeftSidebar/RegexSearchPanel/index.tsx @@ -32,7 +32,7 @@ type Props = { projectId?: string; isRegexEnabled?: boolean; focusedIndex: string; - setFocusedIndex: (i: number) => void; + setFocusedIndex: (i: string) => void; }; // const getAutocompleteThrottled = throttle( @@ -70,7 +70,7 @@ const RegexSearchPanel = ({ const onChange = useCallback((e: ChangeEvent) => { setInputValue(e.target.value); - setFocusedIndex(0); + setFocusedIndex('input'); }, []); const onClear = useCallback(() => { @@ -182,7 +182,7 @@ const RegexSearchPanel = ({ if (focusedIndex === 'input') { onClear(); } else { - setFocusedIndex(0); + setFocusedIndex('input'); } } }, diff --git a/client/src/Project/LeftSidebar/index.tsx b/client/src/Project/LeftSidebar/index.tsx index 6079465301..5f6ffc232b 100644 --- a/client/src/Project/LeftSidebar/index.tsx +++ b/client/src/Project/LeftSidebar/index.tsx @@ -5,7 +5,6 @@ import React, { MouseEvent, useRef, useState, - useEffect, } from 'react'; import useResizeableWidth from '../../hooks/useResizeableWidth'; import { LEFT_SIDEBAR_WIDTH_KEY } from '../../services/storage'; @@ -30,7 +29,6 @@ const LeftSidebar = ({}: Props) => { UIContext.Focus, ); const ref = useRef(null); - const [focusedIndex, setFocusedIndex] = useState(-1); const [focusedIndexFull, setFocusedIndexFull] = useState(''); const { panelRef, dividerRef } = useResizeableWidth( @@ -52,15 +50,25 @@ const LeftSidebar = ({}: Props) => { if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); e.stopPropagation(); - const nodes = ref.current.querySelectorAll('[data-node-index]'); - setFocusedIndex((prev) => { - return e.key === 'ArrowDown' - ? prev < nodes.length - 1 - ? prev + 1 - : 0 - : prev > 0 - ? prev - 1 - : nodes.length - 1; + // eslint-disable-next-line no-undef + const nodes: NodeListOf = + ref.current.querySelectorAll('[data-node-index]'); + setFocusedIndexFull((prev) => { + const prevIndex = Array.from(nodes).findIndex( + (n) => n.dataset.nodeIndex === prev, + ); + if (prevIndex > -1) { + const newIndex = + e.key === 'ArrowDown' + ? prevIndex < nodes.length - 1 + ? prevIndex + 1 + : 0 + : prevIndex > 0 + ? prevIndex - 1 + : nodes.length - 1; + return nodes[newIndex]?.dataset?.nodeIndex || ''; + } + return nodes[0]?.dataset?.nodeIndex || ''; }); } } @@ -70,15 +78,6 @@ const LeftSidebar = ({}: Props) => { ); useKeyboardNavigation(handleKeyEvent); - useEffect(() => { - if (ref.current) { - const nodes = ref.current.querySelectorAll('[data-node-index]'); - setFocusedIndexFull( - (nodes[focusedIndex] as HTMLElement)?.dataset?.nodeIndex || '', - ); - } - }, [focusedIndex]); - const handleClick = useCallback((e: MouseEvent) => { e.stopPropagation(); setIsLeftSidebarFocused(true); @@ -110,9 +109,14 @@ const LeftSidebar = ({}: Props) => { projectId={project?.id} isRegexEnabled={isRegexSearchEnabled} focusedIndex={focusedIndexFull} - setFocusedIndex={setFocusedIndex} + setFocusedIndex={setFocusedIndexFull} /> - {!isRegexSearchEnabled && } + {!isRegexSearchEnabled && ( + + )}
void; @@ -65,6 +65,7 @@ const FolderChip = ({ onClick, path, repoRef }: Props) => { lastIndex={''} focusedIndex={''} index={'0'} + isLeftSidebarFocused={false} />
diff --git a/client/src/hooks/useEnterKey.ts b/client/src/hooks/useEnterKey.ts new file mode 100644 index 0000000000..eadd7ac77b --- /dev/null +++ b/client/src/hooks/useEnterKey.ts @@ -0,0 +1,16 @@ +import { useCallback } from 'react'; +import useKeyboardNavigation from './useKeyboardNavigation'; + +export const useEnterKey = (handleKey: () => void, isDisabled?: boolean) => { + const handleKeyEvent = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + e.stopPropagation(); + handleKey(); + } + }, + [handleKey], + ); + useKeyboardNavigation(handleKeyEvent, isDisabled); +}; diff --git a/client/src/hooks/useNavPanel.ts b/client/src/hooks/useNavPanel.ts new file mode 100644 index 0000000000..b71dfc7ece --- /dev/null +++ b/client/src/hooks/useNavPanel.ts @@ -0,0 +1,50 @@ +import { + Dispatch, + MouseEvent, + SetStateAction, + useCallback, + useContext, + useEffect, + useRef, +} from 'react'; +import { UIContext } from '../context/uiContext'; +import { useEnterKey } from './useEnterKey'; + +export const useNavPanel = ( + index: string, + setExpanded: Dispatch>, + isExpanded: boolean, + focusedIndex: string, +) => { + const { isLeftSidebarFocused } = useContext(UIContext.Focus); + const containerRef = useRef(null); + + const toggleExpanded = useCallback(() => { + setExpanded((prev) => (prev === index ? '' : index)); + }, [index]); + + useEffect(() => { + if (isExpanded) { + // containerRef.current?.scrollIntoView({ block: 'nearest' }); + } + }, [isExpanded]); + + const noPropagate = useCallback((e?: MouseEvent) => { + e?.stopPropagation(); + }, []); + + useEffect(() => { + if (focusedIndex === index && containerRef.current) { + containerRef.current.scrollIntoView({ block: 'nearest' }); + } + }, [focusedIndex, index]); + + useEnterKey(toggleExpanded, focusedIndex !== index || !isLeftSidebarFocused); + + return { + toggleExpanded, + noPropagate, + containerRef, + isLeftSidebarFocused, + }; +}; diff --git a/client/src/icons/Prompt.tsx b/client/src/icons/Prompt.tsx new file mode 100644 index 0000000000..e6a4c008c6 --- /dev/null +++ b/client/src/icons/Prompt.tsx @@ -0,0 +1,18 @@ +import IconWrapper from './Wrapper'; + +const RawIcon = ( + + + + +); + +export default IconWrapper(RawIcon); diff --git a/client/src/icons/index.ts b/client/src/icons/index.ts index 5369b01757..2d56f2eaa4 100644 --- a/client/src/icons/index.ts +++ b/client/src/icons/index.ts @@ -45,6 +45,7 @@ export { default as MoreHorizontalIcon } from './MoreHorizontal'; export { default as PencilIcon } from './Pencil'; export { default as PersonIcon } from './Person'; export { default as PlusSignIcon } from './PlusSign'; +export { default as PromptIcon } from './Prompt'; export { default as RangeIcon } from './Range'; export { default as RefIcon } from './Ref'; export { default as RefreshIcon } from './Refresh'; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 4151ee8261..6165883696 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -639,5 +639,6 @@ "Write your prompt...": "Write your prompt...", "Delete template": "Delete template", "Search templates...": "Search templates...", - "Manage templates": "Manage templates" + "Manage templates": "Manage templates", + "Prompts": "Prompts" } \ No newline at end of file diff --git a/client/src/locales/es.json b/client/src/locales/es.json index 66de14bb1b..bf213c4230 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -638,5 +638,6 @@ "Give your template a title": "Dale un título a tu plantilla", "Delete template": "Plantilla eliminar", "Search templates...": "Plantillas de búsqueda ...", - "Manage templates": "Administrar plantillas" + "Manage templates": "Administrar plantillas", + "Prompts": "Indicaciones" } \ No newline at end of file diff --git a/client/src/locales/it.json b/client/src/locales/it.json index f35138daa6..4042aaa7b6 100644 --- a/client/src/locales/it.json +++ b/client/src/locales/it.json @@ -601,5 +601,6 @@ "Give your template a title": "Dai un titolo al tuo modello", "Delete template": "Elimina modello", "Search templates...": "Modelli di ricerca ...", - "Manage templates": "Gestisci modelli" + "Manage templates": "Gestisci modelli", + "Prompts": "Richiede" } \ No newline at end of file diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json index 114ba6f65c..62b39c9a28 100644 --- a/client/src/locales/ja.json +++ b/client/src/locales/ja.json @@ -620,5 +620,6 @@ "Give your template a title": "テンプレートにタイトルを付けてください", "Delete template": "テンプレートを削除します", "Search templates...": "テンプレートを検索...", - "Manage templates": "テンプレートを管理します" + "Manage templates": "テンプレートを管理します", + "Prompts": "プロンプト" } \ No newline at end of file diff --git a/client/src/locales/zh-CN.json b/client/src/locales/zh-CN.json index 6ea1724d96..faef2e729b 100644 --- a/client/src/locales/zh-CN.json +++ b/client/src/locales/zh-CN.json @@ -632,5 +632,6 @@ "Template title": "模板标题", "Delete template": "删除模板", "Search templates...": "搜索模板...", - "Manage templates": "管理模板" + "Manage templates": "管理模板", + "Prompts": "提示" } \ No newline at end of file diff --git a/client/src/services/api.ts b/client/src/services/api.ts index 8aca782bc8..8fb1579b74 100644 --- a/client/src/services/api.ts +++ b/client/src/services/api.ts @@ -327,9 +327,7 @@ export const upvoteAnswer = ( export const getIndexQueue = () => http('/repos/queue').then((r) => r.data); -export const getCodeStudios = ( - projectId: string, -): Promise => +export const getCodeStudios = (projectId: string): Promise => http(`/projects/${projectId}/studios`).then((r) => r.data); export const patchCodeStudio = ( projectId: string, diff --git a/client/tailwind.config.cjs b/client/tailwind.config.cjs index f29952de98..1bbbcd4df9 100644 --- a/client/tailwind.config.cjs +++ b/client/tailwind.config.cjs @@ -72,6 +72,7 @@ module.exports = { }, spacing: { "4.5": "1.125rem", + "10.5": "2.625rem", "11.5": "2.875rem", '13': '3.25rem', '15': '3.75rem',