import type { FC } from '../../lib/teact/teact'; import React, { memo, useCallback, useEffect, useMemo, useRef, } from '../../lib/teact/teact'; import type { ApiCountry } from '../../api/types'; import type { CustomPeer, CustomPeerType, UniqueCustomPeer } from '../../types'; import { requestMeasure } from '../../lib/fasterdom/fasterdom'; import { isUserId } from '../../global/helpers'; import buildClassName from '../../util/buildClassName'; import { buildCollectionByKey } from '../../util/iteratees'; import useInfiniteScroll from '../../hooks/useInfiniteScroll'; import useLastCallback from '../../hooks/useLastCallback'; import useOldLang from '../../hooks/useOldLang'; import Checkbox from '../ui/Checkbox'; import InfiniteScroll from '../ui/InfiniteScroll'; import InputText from '../ui/InputText'; import ListItem from '../ui/ListItem'; import Loading from '../ui/Loading'; import GroupChatInfo from './GroupChatInfo'; import PickerSelectedItem from './PickerSelectedItem'; import PrivateChatInfo from './PrivateChatInfo'; import './Picker.scss'; type OwnProps = { className?: string; categories?: UniqueCustomPeer[]; itemIds: string[]; selectedCategories?: CustomPeerType[]; selectedIds: string[]; lockedSelectedIds?: string[]; lockedUnselectedIds?: string[]; lockedUnselectedSubtitle?: string; filterValue?: string; filterPlaceholder?: string; notFoundText?: string; searchInputId?: string; isLoading?: boolean; noScrollRestore?: boolean; isSearchable?: boolean; isRoundCheckbox?: boolean; forceShowSelf?: boolean; isViewOnly?: boolean; onSelectedCategoriesChange?: (categories: CustomPeerType[]) => void; onSelectedIdsChange?: (ids: string[]) => void; onFilterChange?: (value: string) => void; onDisabledClick?: (id: string, isSelected: boolean) => void; onLoadMore?: () => void; isCountryList?: boolean; countryList?: ApiCountry[]; }; // Focus slows down animation, also it breaks transition layout in Chrome const FOCUS_DELAY_MS = 500; const MAX_FULL_ITEMS = 10; const ALWAYS_FULL_ITEMS_COUNT = 5; const Picker: FC = ({ className, categories, itemIds, selectedCategories, selectedIds, filterValue, filterPlaceholder, notFoundText, searchInputId, isLoading, noScrollRestore, isSearchable, isRoundCheckbox, lockedSelectedIds, lockedUnselectedIds, lockedUnselectedSubtitle, forceShowSelf, isViewOnly, onSelectedCategoriesChange, onSelectedIdsChange, onFilterChange, onDisabledClick, onLoadMore, isCountryList, countryList, }) => { // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); const shouldMinimize = selectedIds.length > MAX_FULL_ITEMS; useEffect(() => { if (!isSearchable) return; setTimeout(() => { requestMeasure(() => { inputRef.current!.focus(); }); }, FOCUS_DELAY_MS); }, [isSearchable]); const lockedSelectedIdsSet = useMemo(() => new Set(lockedSelectedIds), [lockedSelectedIds]); const lockedUnselectedIdsSet = useMemo(() => new Set(lockedUnselectedIds), [lockedUnselectedIds]); const unlockedSelectedIds = useMemo(() => { return selectedIds.filter((id) => !lockedSelectedIdsSet.has(id)); }, [lockedSelectedIdsSet, selectedIds]); const categoriesByType = useMemo(() => { if (!categories) return {}; return buildCollectionByKey(categories, 'type'); }, [categories]); const sortedItemIds = useMemo(() => { if (filterValue) { return itemIds; } const lockedSelectedBucket: string[] = []; const unlockedBucket: string[] = []; const lockedUnselectableBucket: string[] = []; itemIds.forEach((id) => { if (lockedSelectedIdsSet.has(id)) { lockedSelectedBucket.push(id); } else if (lockedUnselectedIdsSet.has(id)) { lockedUnselectableBucket.push(id); } else { unlockedBucket.push(id); } }); return lockedSelectedBucket.concat(unlockedBucket, lockedUnselectableBucket); }, [filterValue, itemIds, lockedSelectedIdsSet, lockedUnselectedIdsSet]); const handleItemClick = useLastCallback((id: string) => { if (lockedSelectedIdsSet.has(id)) { onDisabledClick?.(id, true); return; } if (lockedUnselectedIdsSet.has(id)) { onDisabledClick?.(id, false); return; } if (categoriesByType[id]) { const categoryType = categoriesByType[id].type; const newSelectedCategories = selectedCategories?.slice() || []; if (newSelectedCategories.includes(categoryType)) { newSelectedCategories.splice(newSelectedCategories.indexOf(categoryType), 1); } else { newSelectedCategories.push(categoryType); } onSelectedCategoriesChange?.(newSelectedCategories); } else { const newSelectedIds = selectedIds.slice(); if (newSelectedIds.includes(id)) { newSelectedIds.splice(newSelectedIds.indexOf(id), 1); } else { newSelectedIds.push(id); } onSelectedIdsChange?.(newSelectedIds); } onFilterChange?.(''); }); const handleFilterChange = useLastCallback((e: React.ChangeEvent) => { const { value } = e.currentTarget; onFilterChange?.(value); }); const [viewportIds, getMore] = useInfiniteScroll(onLoadMore, sortedItemIds, Boolean(filterValue)); const lang = useOldLang(); const countriesByIso = useMemo(() => { if (!countryList) return undefined; return buildCollectionByKey(countryList, 'iso2'); }, [countryList]); const renderCategory = useLastCallback((category: CustomPeer) => { return ( ); }); const renderChatInfo = useLastCallback((id: string) => { const isUnselectable = lockedUnselectedIdsSet.has(id); if (isCountryList && countriesByIso) { const country = countriesByIso[id]; return
{country.defaultName}
; } else if (isUserId(id)) { return ( ); } else { return ; } }); const renderItem = useCallback((id: string, isCategory?: boolean) => { const category = isCategory ? categoriesByType[id] : undefined; const shouldRenderLockIcon = lockedUnselectedIdsSet.has(id); const isLocked = lockedSelectedIdsSet.has(id) || shouldRenderLockIcon; const isChecked = category ? selectedCategories?.includes(category.type) : selectedIds.includes(id); const renderCheckbox = () => { return (isViewOnly || shouldRenderLockIcon) ? undefined : ( ); }; return ( handleItemClick(id)} ripple > {!isRoundCheckbox ? renderCheckbox() : undefined} {category ? renderCategory(category) : renderChatInfo(id)} {isRoundCheckbox ? renderCheckbox() : undefined} ); }, [ categoriesByType, isRoundCheckbox, isViewOnly, lockedSelectedIdsSet, lockedUnselectedIdsSet, onDisabledClick, renderChatInfo, selectedCategories, selectedIds, ]); const beforeChildren = useMemo(() => { return (
{Boolean(categories?.length) && (
{lang('PrivacyUserTypes')}
)} {categories?.map((category) => renderItem(category.type, true))}
{lang('FilterChats')}
); }, [categories, lang, renderItem]); return (
{isSearchable && (
{selectedCategories?.map((category) => ( ))} {lockedSelectedIds?.map((id, i) => ( ))} {unlockedSelectedIds.map((id, i) => ( ))}
)} {viewportIds?.length ? ( {viewportIds.map((id) => renderItem(id))} ) : !isLoading && viewportIds && !viewportIds.length ? (

{notFoundText || 'Sorry, nothing found.'}

) : ( )}
); }; export default memo(Picker);