import React, { useState, useEffect, useRef, Fragment } from 'react'; import { useNavigate, useOutletContext } from 'react-router'; import { Tabs, Tab, TextField, InputAdornment, Typography, List, ListItem, ListItemButton, Divider } from '@mui/material'; import { Box } from '@mui/system'; import { useObjectState } from '@hyper-hyper-space/react'; import { Hash, Resources } from '@hyper-hyper-space/core'; import { Contact, ProfileUtils } from '../../../model/ProfileUtils'; import ContactListDisplay from './ContactListDisplay'; import { Home } from '@hyper-hyper-space/home'; type LetterIndexEntry = { letter: string, hash?: Hash } type ContactSelectorProps = { handleSelect?: Function preFilter?: (c: Contact) => boolean, excludedHashes?: Hash[], selectedHashes?: Hash[], resourcesForDiscovery: Resources, home: Home } const ContactSelector = ({ handleSelect, preFilter, excludedHashes, selectedHashes, home}: ContactSelectorProps) => { preFilter = preFilter || ((c: Contact) => true) const navigate = useNavigate(); const contactsState = useObjectState(home?.contacts?.current); const attemptSelection = (...x: any[]) => { try { handleSelect!(...x) } catch (e) { // todo: handle errors such as invalid selection, maybe by showing a warning throw(e) } } const handleChangeTab = () => { }; const [searchValue, setSearchValue] = useState(''); const searchValueChanged = (e: React.ChangeEvent) => { const newValue = e.currentTarget.value; setSearchValue(newValue); }; const searchKeyUp = (e: React.KeyboardEvent) => { if (e.key === 'Escape') { setSearchValue(''); } } const [contacts, setContacts] = useState>([]); const [letters, setLetters] = useState>([]); const [currentLetter, setCurrentLetter] = useState(); const contactElements = useRef({} as any); const [scrollTimeout, setScrollTimeout] = useState(undefined); useEffect(() => { const load = async () => { if (contactsState?.value !== undefined) { const keywords = ProfileUtils.normalizeStringForKeywordSearch(searchValue).split(/[ -]+/); const contactProfiles = Array.from(contactsState.value?.values()); const p = home?.profile; if (p !== undefined) { contactProfiles.push(p); } const cs = contactProfiles.map(ProfileUtils.createContact).filter((c: Contact) => preFilter!(c!) && ProfileUtils.filterContactForKeywordSearch(c, keywords)); cs.sort((a: { order: string }, b: { order: string }) => a.order.localeCompare(b.order)); let letter = ''; const letterIndexEntries: Array = []; const possibleLetters = new Set(); const allLetters: Array = [] for (let i = 'A'.charCodeAt(0); i <= 'Z'.charCodeAt(0); i++) { allLetters.push(String.fromCharCode(i)); } let nextLetter = 0; let firstLetter = 'A'; for (const c of cs) { const normalizedName = ProfileUtils.normalizeStringForKeywordSearch(c.name); if (normalizedName.length > 0) { const newLetter = normalizedName[0].toUpperCase(); if (letter !== newLetter) { if (letter === '') { firstLetter = newLetter; } while (nextLetter < allLetters.length && allLetters[nextLetter].localeCompare(newLetter) < 0) { letterIndexEntries.push({ letter: allLetters[nextLetter] }); nextLetter = nextLetter + 1; } if (nextLetter < allLetters.length && allLetters[nextLetter].localeCompare(newLetter) === 0) { nextLetter = nextLetter + 1; } letter = newLetter; possibleLetters.add(letter); letterIndexEntries.push({ letter: letter, hash: c.hash }) c.isFirstForLetter = letter; } } } while (nextLetter < allLetters.length) { letterIndexEntries.push({ letter: allLetters[nextLetter] }); nextLetter = nextLetter + 1; } setContacts(cs); setLetters(letterIndexEntries); if (currentLetter === undefined || !possibleLetters.has(currentLetter)) { setCurrentLetter(firstLetter); } } const cs = contactsState?.getValue() }; load(); }, [contactsState, searchValue]); //const sx = fullScreen? {} : {maxHeight: '80%'} const share = () => { navigate('../share-profile'); }; const refreshLetterIndex = () => { for (const idx of letters) { if (idx.hash !== undefined) { let elmt = contactElements.current[idx.hash]; var rect = elmt.getBoundingClientRect(); if ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /* or $(window).height() */ rect.right <= (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */ ) { setCurrentLetter(idx.letter); break; } } } } const onScroll = () => { if (scrollTimeout === undefined) { window.setTimeout(refreshLetterIndex, 200); } } return ( 🔎 ), }} value={searchValue} onChange={searchValueChanged} onKeyUp={searchKeyUp} /> {/* */} {letters.map((idx: LetterIndexEntry) => ( ) => { e.preventDefault(); setCurrentLetter(idx.letter); if (idx.hash !== undefined) { contactElements.current[idx.hash].scrollIntoView(); } }} /> ))} {contacts.filter((c: Contact) => !(excludedHashes || []).includes(c.hash)).map((c: Contact) => ( { contactElements.current[c.hash] = instance; }} // secondaryAction={c.hash !== home?.getAuthor()?.getLastHash() ? children : undefined} > attemptSelection(c)}> ))} {/* */} ); } export default ContactSelector;