156 lines
3.9 KiB
TypeScript
156 lines
3.9 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
|
import { useDebouncedCallback } from 'use-debounce';
|
|
|
|
import { fetchRelationships } from 'mastodon/actions/accounts';
|
|
import { importFetchedAccounts } from 'mastodon/actions/importer';
|
|
import { apiRequest } from 'mastodon/api';
|
|
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
|
import { useAppDispatch } from 'mastodon/store';
|
|
|
|
import { useCurrentAccountId } from './useAccountId';
|
|
|
|
export function useSearchAccounts({
|
|
onSettled,
|
|
filterResults,
|
|
resetOnInputClear = true,
|
|
withRelationships = false,
|
|
withDefaultFollows = false,
|
|
}: {
|
|
onSettled?: (value: string) => void;
|
|
filterResults?: (account: ApiAccountJSON) => boolean;
|
|
resetOnInputClear?: boolean;
|
|
withRelationships?: boolean;
|
|
withDefaultFollows?: boolean;
|
|
} = {}) {
|
|
const dispatch = useAppDispatch();
|
|
|
|
const [accounts, setAccounts] = useState<ApiAccountJSON[]>([]);
|
|
const [loadingState, setLoadingState] = useState<
|
|
'idle' | 'loading' | 'error'
|
|
>('idle');
|
|
|
|
const searchRequestRef = useRef<AbortController | null>(null);
|
|
|
|
const searchAccounts = useDebouncedCallback(
|
|
async (value: string) => {
|
|
if (searchRequestRef.current) {
|
|
searchRequestRef.current.abort();
|
|
}
|
|
|
|
if (value.trim().length === 0) {
|
|
onSettled?.('');
|
|
if (resetOnInputClear) {
|
|
setAccounts([]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
setLoadingState('loading');
|
|
|
|
searchRequestRef.current = new AbortController();
|
|
|
|
try {
|
|
const accounts = await apiRequest<ApiAccountJSON[]>(
|
|
'GET',
|
|
'v1/accounts/search',
|
|
{
|
|
signal: searchRequestRef.current.signal,
|
|
params: {
|
|
q: value,
|
|
resolve: true,
|
|
},
|
|
},
|
|
);
|
|
const accountIds = accounts.map((a) => a.id);
|
|
dispatch(importFetchedAccounts(accounts));
|
|
if (withRelationships) {
|
|
dispatch(fetchRelationships(accountIds));
|
|
}
|
|
setAccounts(accounts);
|
|
setLoadingState('idle');
|
|
onSettled?.(value);
|
|
} catch {
|
|
setLoadingState('error');
|
|
onSettled?.(value);
|
|
}
|
|
},
|
|
500,
|
|
{ leading: true, trailing: true },
|
|
);
|
|
|
|
const startSearch = useCallback(
|
|
(value: string) => {
|
|
void searchAccounts(value);
|
|
},
|
|
[searchAccounts],
|
|
);
|
|
|
|
const resetAccounts = useCallback(() => {
|
|
setAccounts([]);
|
|
}, []);
|
|
|
|
const currentUserId = useCurrentAccountId();
|
|
const [defaultAccounts, setDefaultAccounts] = useState<
|
|
ApiAccountJSON[] | null
|
|
>(null);
|
|
|
|
useEffect(() => {
|
|
if (
|
|
!currentUserId ||
|
|
loadingState !== 'idle' ||
|
|
defaultAccounts !== null ||
|
|
!withDefaultFollows
|
|
) {
|
|
return;
|
|
}
|
|
|
|
async function doRequest() {
|
|
setLoadingState('loading');
|
|
try {
|
|
const accounts = await apiRequest<ApiAccountJSON[]>(
|
|
'GET',
|
|
`v1/accounts/${currentUserId}/following`,
|
|
{ params: { limit: 40 } },
|
|
);
|
|
const accountIds = accounts.map((a) => a.id);
|
|
dispatch(importFetchedAccounts(accounts));
|
|
if (withRelationships) {
|
|
dispatch(fetchRelationships(accountIds));
|
|
}
|
|
setDefaultAccounts(accounts);
|
|
setLoadingState('idle');
|
|
} catch {
|
|
setLoadingState('error');
|
|
}
|
|
}
|
|
void doRequest();
|
|
}, [
|
|
currentUserId,
|
|
accounts,
|
|
dispatch,
|
|
filterResults,
|
|
loadingState,
|
|
withRelationships,
|
|
defaultAccounts,
|
|
withDefaultFollows,
|
|
]);
|
|
|
|
const accountsToReturn =
|
|
accounts.length === 0 && withDefaultFollows
|
|
? (defaultAccounts ?? [])
|
|
: accounts;
|
|
|
|
const filteredAccounts = filterResults
|
|
? accountsToReturn.filter(filterResults)
|
|
: accountsToReturn;
|
|
|
|
return {
|
|
searchAccounts: startSearch,
|
|
resetAccounts,
|
|
accounts: filteredAccounts,
|
|
isLoading: loadingState === 'loading',
|
|
isError: loadingState === 'error',
|
|
};
|
|
}
|