| import { useCallback, useEffect } from 'react' |
| import { AsyncSelect } from '@/components/ui/AsyncSelect' |
| import { useSettingsStore } from '@/stores/settings' |
| import { useGraphStore } from '@/stores/graph' |
| import { labelListLimit, controlButtonVariant } from '@/lib/constants' |
| import MiniSearch from 'minisearch' |
| import { useTranslation } from 'react-i18next' |
| import { RefreshCw } from 'lucide-react' |
| import Button from '@/components/ui/Button' |
|
|
| const GraphLabels = () => { |
| const { t } = useTranslation() |
| const label = useSettingsStore.use.queryLabel() |
| const allDatabaseLabels = useGraphStore.use.allDatabaseLabels() |
| const labelsFetchAttempted = useGraphStore.use.labelsFetchAttempted() |
|
|
| |
|
|
| const getSearchEngine = useCallback(() => { |
| |
| const searchEngine = new MiniSearch({ |
| idField: 'id', |
| fields: ['value'], |
| searchOptions: { |
| prefix: true, |
| fuzzy: 0.2, |
| boost: { |
| label: 2 |
| } |
| } |
| }) |
|
|
| |
| const documents = allDatabaseLabels.map((str, index) => ({ id: index, value: str })) |
| searchEngine.addAll(documents) |
|
|
| return { |
| labels: allDatabaseLabels, |
| searchEngine |
| } |
| }, [allDatabaseLabels]) |
|
|
| const fetchData = useCallback( |
| async (query?: string): Promise<string[]> => { |
| const { labels, searchEngine } = getSearchEngine() |
|
|
| let result: string[] = labels |
| if (query) { |
| |
| result = searchEngine.search(query).map((r: { id: number }) => labels[r.id]) |
|
|
| |
| |
| if (result.length < 15) { |
| |
| const matchedLabels = new Set(result) |
|
|
| |
| const middleMatchResults = labels.filter(label => { |
| |
| if (matchedLabels.has(label)) return false |
|
|
| |
| return label && |
| typeof label === 'string' && |
| !label.toLowerCase().startsWith(query.toLowerCase()) && |
| label.toLowerCase().includes(query.toLowerCase()) |
| }) |
|
|
| |
| result = [...result, ...middleMatchResults] |
| } |
| } |
|
|
| return result.length <= labelListLimit |
| ? result |
| : [...result.slice(0, labelListLimit), '...'] |
| }, |
| [getSearchEngine] |
| ) |
|
|
| |
| useEffect(() => { |
|
|
| if (labelsFetchAttempted) { |
| if (allDatabaseLabels.length > 1) { |
| if (label && label !== '*' && !allDatabaseLabels.includes(label)) { |
| console.log(`Label "${label}" not in available labels, setting to "*"`); |
| useSettingsStore.getState().setQueryLabel('*'); |
| } else { |
| console.log(`Label "${label}" is valid`); |
| } |
| } else if (label && allDatabaseLabels.length <= 1 && label && label !== '*') { |
| console.log('Available labels list is empty, setting label to empty'); |
| useSettingsStore.getState().setQueryLabel(''); |
| } |
| useGraphStore.getState().setLabelsFetchAttempted(false) |
| } |
|
|
| }, [allDatabaseLabels, label, labelsFetchAttempted]); |
|
|
| const handleRefresh = useCallback(() => { |
| |
| useGraphStore.getState().setLabelsFetchAttempted(false) |
| useGraphStore.getState().setGraphDataFetchAttempted(false) |
|
|
| |
| useGraphStore.getState().setLastSuccessfulQueryLabel('') |
|
|
| |
| const currentLabel = useSettingsStore.getState().queryLabel |
|
|
| |
| if (!currentLabel) { |
| useSettingsStore.getState().setQueryLabel('*') |
| } else { |
| |
| useSettingsStore.getState().setQueryLabel('') |
| setTimeout(() => { |
| useSettingsStore.getState().setQueryLabel(currentLabel) |
| }, 0) |
| } |
| }, []); |
|
|
| return ( |
| <div className="flex items-center"> |
| {/* Always show refresh button */} |
| <Button |
| size="icon" |
| variant={controlButtonVariant} |
| onClick={handleRefresh} |
| tooltip={t('graphPanel.graphLabels.refreshTooltip')} |
| className="mr-2" |
| > |
| <RefreshCw className="h-4 w-4" /> |
| </Button> |
| <AsyncSelect<string> |
| className="min-w-[300px]" |
| triggerClassName="max-h-8" |
| searchInputClassName="max-h-8" |
| triggerTooltip={t('graphPanel.graphLabels.selectTooltip')} |
| fetcher={fetchData} |
| renderOption={(item) => <div>{item}</div>} |
| getOptionValue={(item) => item} |
| getDisplayValue={(item) => <div>{item}</div>} |
| notFound={<div className="py-6 text-center text-sm">No labels found</div>} |
| label={t('graphPanel.graphLabels.label')} |
| placeholder={t('graphPanel.graphLabels.placeholder')} |
| value={label !== null ? label : '*'} |
| onChange={(newLabel) => { |
| const currentLabel = useSettingsStore.getState().queryLabel; |
| |
| // select the last item means query all |
| if (newLabel === '...') { |
| newLabel = '*'; |
| } |
| |
| // Handle reselecting the same label |
| if (newLabel === currentLabel && newLabel !== '*') { |
| newLabel = '*'; |
| } |
| |
| // Reset graphDataFetchAttempted flag to ensure data fetch is triggered |
| useGraphStore.getState().setGraphDataFetchAttempted(false); |
| |
| // Update the label to trigger data loading |
| useSettingsStore.getState().setQueryLabel(newLabel); |
| }} |
| clearable={false} // Prevent clearing value on reselect |
| /> |
| </div> |
| ) |
| } |
| |
| export default GraphLabels |
| |