Skip to content

Commit 2c81db5

Browse files
authored
feat: enhance GotoAnything UX with @ command selector (#23738)
1 parent 43411d7 commit 2c81db5

File tree

22 files changed

+145
-26
lines changed

22 files changed

+145
-26
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { FC } from 'react'
2+
import { Command } from 'cmdk'
3+
import { useTranslation } from 'react-i18next'
4+
import type { ActionItem } from './actions/types'
5+
6+
type Props = {
7+
actions: Record<string, ActionItem>
8+
onCommandSelect: (commandKey: string) => void
9+
}
10+
11+
const CommandSelector: FC<Props> = ({ actions, onCommandSelect }) => {
12+
const { t } = useTranslation()
13+
14+
return (
15+
<div className="p-4">
16+
<div className="mb-3 text-left text-sm font-medium text-text-secondary">
17+
{t('app.gotoAnything.selectSearchType')}
18+
</div>
19+
<Command.Group className="space-y-1">
20+
{Object.values(actions).map(action => (
21+
<Command.Item
22+
key={action.key}
23+
value={action.shortcut}
24+
className="flex cursor-pointer items-center rounded-md
25+
p-2.5
26+
transition-all
27+
duration-150 hover:bg-state-base-hover aria-[selected=true]:bg-state-base-hover"
28+
onSelect={() => onCommandSelect(action.shortcut)}
29+
>
30+
<span className="min-w-[4.5rem] text-left font-mono text-xs text-text-tertiary">
31+
{action.shortcut}
32+
</span>
33+
<span className="ml-3 text-sm text-text-secondary">
34+
{(() => {
35+
const keyMap: Record<string, string> = {
36+
'@app': 'app.gotoAnything.actions.searchApplicationsDesc',
37+
'@plugin': 'app.gotoAnything.actions.searchPluginsDesc',
38+
'@knowledge': 'app.gotoAnything.actions.searchKnowledgeBasesDesc',
39+
'@node': 'app.gotoAnything.actions.searchWorkflowNodesDesc',
40+
}
41+
return t(keyMap[action.key])
42+
})()}
43+
</span>
44+
</Command.Item>
45+
))}
46+
</Command.Group>
47+
</div>
48+
)
49+
}
50+
51+
export default CommandSelector

web/app/components/goto-anything/index.tsx

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { useTranslation } from 'react-i18next'
1717
import InstallFromMarketplace from '../plugins/install-plugin/install-from-marketplace'
1818
import type { Plugin } from '../plugins/types'
1919
import { Command } from 'cmdk'
20+
import CommandSelector from './command-selector'
2021

2122
type Props = {
2223
onHide?: () => void
@@ -81,11 +82,15 @@ const GotoAnything: FC<Props> = ({
8182
wait: 300,
8283
})
8384

85+
const isCommandsMode = searchQuery.trim() === '@'
86+
8487
const searchMode = useMemo(() => {
88+
if (isCommandsMode) return 'commands'
89+
8590
const query = searchQueryDebouncedValue.toLowerCase()
8691
const action = matchAction(query, Actions)
8792
return action ? action.key : 'general'
88-
}, [searchQueryDebouncedValue, Actions])
93+
}, [searchQueryDebouncedValue, Actions, isCommandsMode])
8994

9095
const { data: searchResults = [], isLoading, isError, error } = useQuery(
9196
{
@@ -103,12 +108,20 @@ const GotoAnything: FC<Props> = ({
103108
const action = matchAction(query, Actions)
104109
return await searchAnything(defaultLocale, query, action)
105110
},
106-
enabled: !!searchQueryDebouncedValue,
111+
enabled: !!searchQueryDebouncedValue && !isCommandsMode,
107112
staleTime: 30000,
108113
gcTime: 300000,
109114
},
110115
)
111116

117+
const handleCommandSelect = useCallback((commandKey: string) => {
118+
setSearchQuery(`${commandKey} `)
119+
setCmdVal('')
120+
setTimeout(() => {
121+
inputRef.current?.focus()
122+
}, 0)
123+
}, [])
124+
112125
// Handle navigation to selected result
113126
const handleNavigate = useCallback((result: SearchResult) => {
114127
setShow(false)
@@ -141,7 +154,7 @@ const GotoAnything: FC<Props> = ({
141154
[searchResults])
142155

143156
const emptyResult = useMemo(() => {
144-
if (searchResults.length || !searchQueryDebouncedValue.trim() || isLoading)
157+
if (searchResults.length || !searchQuery.trim() || isLoading || isCommandsMode)
145158
return null
146159

147160
const isCommandSearch = searchMode !== 'general'
@@ -186,34 +199,22 @@ const GotoAnything: FC<Props> = ({
186199
</div>
187200
</div>
188201
)
189-
}, [searchResults, searchQueryDebouncedValue, Actions, searchMode, isLoading, isError])
202+
}, [searchResults, searchQuery, Actions, searchMode, isLoading, isError, isCommandsMode])
190203

191204
const defaultUI = useMemo(() => {
192-
if (searchQueryDebouncedValue.trim())
205+
if (searchQuery.trim())
193206
return null
194207

195-
return (<div className="flex items-center justify-center py-8 text-center text-text-tertiary">
208+
return (<div className="flex items-center justify-center py-12 text-center text-text-tertiary">
196209
<div>
197210
<div className='text-sm font-medium'>{t('app.gotoAnything.searchTitle')}</div>
198-
<div className='mt-3 space-y-2 text-xs text-text-quaternary'>
199-
{Object.values(Actions).map(action => (
200-
<div key={action.key} className='flex items-center gap-2'>
201-
<span className='inline-flex items-center rounded bg-gray-200 px-2 py-1 font-mono text-xs font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-200'>{action.shortcut}</span>
202-
<span>{(() => {
203-
const keyMap: Record<string, string> = {
204-
'@app': 'app.gotoAnything.actions.searchApplicationsDesc',
205-
'@plugin': 'app.gotoAnything.actions.searchPluginsDesc',
206-
'@knowledge': 'app.gotoAnything.actions.searchKnowledgeBasesDesc',
207-
'@node': 'app.gotoAnything.actions.searchWorkflowNodesDesc',
208-
}
209-
return t(keyMap[action.key])
210-
})()}</span>
211-
</div>
212-
))}
211+
<div className='mt-3 space-y-1 text-xs text-text-quaternary'>
212+
<div>{t('app.gotoAnything.searchHint')}</div>
213+
<div>{t('app.gotoAnything.commandHint')}</div>
213214
</div>
214215
</div>
215216
</div>)
216-
}, [searchQueryDebouncedValue, Actions])
217+
}, [searchQuery, Actions])
217218

218219
useEffect(() => {
219220
if (show) {
@@ -296,7 +297,13 @@ const GotoAnything: FC<Props> = ({
296297
)}
297298
{!isLoading && !isError && (
298299
<>
299-
{Object.entries(groupedResults).map(([type, results], groupIndex) => (
300+
{isCommandsMode ? (
301+
<CommandSelector
302+
actions={Actions}
303+
onCommandSelect={handleCommandSelect}
304+
/>
305+
) : (
306+
Object.entries(groupedResults).map(([type, results], groupIndex) => (
300307
<Command.Group key={groupIndex} heading={(() => {
301308
const typeMap: Record<string, string> = {
302309
'app': 'app.gotoAnything.groups.apps',
@@ -330,9 +337,10 @@ const GotoAnything: FC<Props> = ({
330337
</Command.Item>
331338
))}
332339
</Command.Group>
333-
))}
334-
{emptyResult}
335-
{defaultUI}
340+
))
341+
)}
342+
{!isCommandsMode && emptyResult}
343+
{!isCommandsMode && defaultUI}
336344
</>
337345
)}
338346
</Command.List>

web/i18n/de-DE/app.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@ const translation = {
288288
useAtForSpecific: 'Verwenden von @ für bestimmte Typen',
289289
searchTitle: 'Suchen Sie nach irgendetwas',
290290
searching: 'Suche...',
291+
selectSearchType: 'Wählen Sie aus, wonach gesucht werden soll',
292+
commandHint: 'Geben Sie @ ein, um nach Kategorie zu suchen',
293+
searchHint: 'Beginnen Sie mit der Eingabe, um alles sofort zu durchsuchen',
291294
},
292295
}
293296

web/i18n/en-US/app.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ const translation = {
266266
inScope: 'in {{scope}}s',
267267
clearToSearchAll: 'Clear @ to search all',
268268
useAtForSpecific: 'Use @ for specific types',
269+
selectSearchType: 'Choose what to search for',
270+
searchHint: 'Start typing to search everything instantly',
271+
commandHint: 'Type @ to browse by category',
269272
actions: {
270273
searchApplications: 'Search Applications',
271274
searchApplicationsDesc: 'Search and navigate to your applications',

web/i18n/es-ES/app.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,9 @@ const translation = {
286286
searchTitle: 'Busca cualquier cosa',
287287
someServicesUnavailable: 'Algunos servicios de búsqueda no están disponibles',
288288
servicesUnavailableMessage: 'Algunos servicios de búsqueda pueden estar experimentando problemas. Inténtalo de nuevo en un momento.',
289+
searchHint: 'Empieza a escribir para buscar todo al instante',
290+
commandHint: 'Escriba @ para buscar por categoría',
291+
selectSearchType: 'Elige qué buscar',
289292
},
290293
}
291294

web/i18n/fa-IR/app.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,9 @@ const translation = {
286286
searchTemporarilyUnavailable: 'جستجو به طور موقت در دسترس نیست',
287287
servicesUnavailableMessage: 'برخی از سرویس های جستجو ممکن است با مشکل مواجه شوند. یک لحظه دیگر دوباره امتحان کنید.',
288288
someServicesUnavailable: 'برخی از سرویس های جستجو دردسترس نیستند',
289+
selectSearchType: 'انتخاب کنید چه چیزی را جستجو کنید',
290+
commandHint: '@ را برای مرور بر اساس دسته بندی تایپ کنید',
291+
searchHint: 'شروع به تایپ کنید تا فورا همه چیز را جستجو کنید',
289292
},
290293
}
291294

web/i18n/fr-FR/app.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,9 @@ const translation = {
286286
searchPlaceholder: 'Recherchez ou tapez @ pour les commandes...',
287287
searchFailed: 'Echec de la recherche',
288288
noResults: 'Aucun résultat trouvé',
289+
commandHint: 'Tapez @ pour parcourir par catégorie',
290+
selectSearchType: 'Choisissez les éléments de recherche',
291+
searchHint: 'Commencez à taper pour tout rechercher instantanément',
289292
},
290293
}
291294

web/i18n/hi-IN/app.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,9 @@ const translation = {
286286
searchPlaceholder: 'कमांड के लिए खोजें या टाइप करें @...',
287287
searchTemporarilyUnavailable: 'खोज अस्थायी रूप से उपलब्ध नहीं है',
288288
servicesUnavailableMessage: 'कुछ खोज सेवाएँ समस्याओं का सामना कर सकती हैं। थोड़ी देर बाद फिर से प्रयास करें।',
289+
commandHint: '@ का उपयोग कर श्रेणी के अनुसार ब्राउज़ करें',
290+
selectSearchType: 'खोजने के लिए क्या चुनें',
291+
searchHint: 'सब कुछ तुरंत खोजने के लिए टाइप करना शुरू करें',
289292
},
290293
}
291294

web/i18n/it-IT/app.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,9 @@ const translation = {
292292
noResults: 'Nessun risultato trovato',
293293
useAtForSpecific: 'Utilizzare @ per tipi specifici',
294294
clearToSearchAll: 'Cancella @ per cercare tutto',
295+
selectSearchType: 'Scegli cosa cercare',
296+
commandHint: 'Digita @ per sfogliare per categoria',
297+
searchHint: 'Inizia a digitare per cercare tutto all\'istante',
295298
},
296299
}
297300

web/i18n/ja-JP/app.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ const translation = {
265265
inScope: '{{scope}}s 内',
266266
clearToSearchAll: '@ をクリアしてすべてを検索',
267267
useAtForSpecific: '特定のタイプには @ を使用',
268+
selectSearchType: '検索対象を選択',
269+
searchHint: '入力を開始してすべてを瞬時に検索',
270+
commandHint: '@ を入力してカテゴリ別に参照',
268271
actions: {
269272
searchApplications: 'アプリケーションを検索',
270273
searchApplicationsDesc: 'アプリケーションを検索してナビゲート',

0 commit comments

Comments
 (0)