|
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 |
|
|