import React, { memo, useCallback, useMemo, useRef, useState } from 'react' import { ArrowLeft } from 'react-feather' import ReactGA from 'react-ga' import { usePopper } from 'react-popper' import { useDispatch, useSelector } from 'react-redux' import { Text } from 'rebass' import styled from 'styled-components' import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg' import { useFetchListCallback } from '../../hooks/useFetchListCallback' import { useOnClickOutside } from '../../hooks/useOnClickOutside' import { useTranslation } from 'react-i18next' import useToggle from '../../hooks/useToggle' import { AppDispatch, AppState } from '../../state' import { acceptListUpdate, removeList, selectList } from '../../state/lists/actions' import { useSelectedListUrl } from '../../state/lists/hooks' import { CloseIcon, ExternalLink, LinkStyledButton, TYPE } from '../../theme' import listVersionLabel from '../../utils/listVersionLabel' import { parseENSAddress } from '../../utils/parseENSAddress' import uriToHttp from '../../utils/uriToHttp' import { ButtonOutlined, ButtonPrimary, ButtonSecondary } from '../Button' import Column from '../Column' import ListLogo from '../ListLogo' import QuestionHelper from '../QuestionHelper' import Row, { RowBetween } from '../Row' import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds' const UnpaddedLinkStyledButton = styled(LinkStyledButton)` padding: 0; font-size: 1rem; opacity: ${({ disabled }) => (disabled ? '0.4' : '1')}; ` const PopoverContainer = styled.div<{ show: boolean }>` z-index: 100; visibility: ${props => (props.show ? 'visible' : 'hidden')}; opacity: ${props => (props.show ? 1 : 0)}; transition: visibility 150ms linear, opacity 150ms linear; background: ${({ theme }) => theme.bg2}; border: 1px solid ${({ theme }) => theme.bg3}; box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 0px 24px 32px rgba(0, 0, 0, 0.01); color: ${({ theme }) => theme.text2}; border-radius: 0.5rem; padding: 1rem; display: grid; grid-template-rows: 1fr; grid-gap: 8px; font-size: 1rem; text-align: left; ` const StyledMenu = styled.div` display: flex; justify-content: center; align-items: center; position: relative; border: none; ` const StyledListUrlText = styled.div` max-width: 160px; opacity: 0.6; margin-right: 0.5rem; font-size: 14px; overflow: hidden; text-overflow: ellipsis; ` function ListOrigin({ listUrl }: { listUrl: string }) { const ensName = useMemo(() => parseENSAddress(listUrl)?.ensName, [listUrl]) const host = useMemo(() => { if (ensName) return undefined const lowerListUrl = listUrl.toLowerCase() if (lowerListUrl.startsWith('ipfs://') || lowerListUrl.startsWith('ipns://')) { return listUrl } try { const url = new URL(listUrl) return url.host } catch (error) { return undefined } }, [listUrl, ensName]) return <>{ensName ?? host} } function listUrlRowHTMLId(listUrl: string) { return `list-row-${listUrl.replace(/\./g, '-')}` } const ListRow = memo(function ListRow({ listUrl, onBack }: { listUrl: string; onBack: () => void }) { const listsByUrl = useSelector(state => state.lists.byUrl) const selectedListUrl = useSelectedListUrl() const dispatch = useDispatch() const { current: list, pendingUpdate: pending } = listsByUrl[listUrl] const isSelected = listUrl === selectedListUrl const { t } = useTranslation() const [open, toggle] = useToggle(false) const node = useRef() const [referenceElement, setReferenceElement] = useState() const [popperElement, setPopperElement] = useState() const { styles, attributes } = usePopper(referenceElement, popperElement, { placement: 'auto', strategy: 'fixed', modifiers: [{ name: 'offset', options: { offset: [8, 8] } }] }) useOnClickOutside(node, open ? toggle : undefined) const selectThisList = useCallback(() => { if (isSelected) return ReactGA.event({ category: 'Lists', action: 'Select List', label: listUrl }) dispatch(selectList(listUrl)) onBack() }, [dispatch, isSelected, listUrl, onBack]) const handleAcceptListUpdate = useCallback(() => { if (!pending) return ReactGA.event({ category: 'Lists', action: 'Update List from List Select', label: listUrl }) dispatch(acceptListUpdate(listUrl)) }, [dispatch, listUrl, pending]) const handleRemoveList = useCallback(() => { ReactGA.event({ category: 'Lists', action: 'Start Remove List', label: listUrl }) if (window.prompt(`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) { ReactGA.event({ category: 'Lists', action: 'Confirm Remove List', label: listUrl }) dispatch(removeList(listUrl)) } }, [dispatch, listUrl]) if (!list) return null return ( {list.logoURI ? ( ) : (
)} {list.name} {open && (
{list && listVersionLabel(list.version)}
View list Remove list {pending && ( Update list )}
)}
{isSelected ? ( {t('selected')} ) : ( <> {t('select')} )} ) }) const AddListButton = styled(ButtonSecondary)` /* height: 1.8rem; */ max-width: 4rem; margin-left: 1rem; border-radius: 12px; padding: 10px 18px; ` const ListContainer = styled.div` flex: 1; overflow: auto; ` export function ListSelect({ onDismiss, onBack }: { onDismiss: () => void; onBack: () => void }) { const [listUrlInput, setListUrlInput] = useState('') const dispatch = useDispatch() const lists = useSelector(state => state.lists.byUrl) const adding = Boolean(lists[listUrlInput]?.loadingRequestId) const [addError, setAddError] = useState(null) const { t } = useTranslation() const handleInput = useCallback(e => { setListUrlInput(e.target.value) setAddError(null) }, []) const fetchList = useFetchListCallback() const handleAddList = useCallback(() => { if (adding) return setAddError(null) fetchList(listUrlInput) .then(() => { setListUrlInput('') ReactGA.event({ category: 'Lists', action: 'Add List', label: listUrlInput }) }) .catch(error => { ReactGA.event({ category: 'Lists', action: 'Add List Failed', label: listUrlInput }) setAddError(error.message) dispatch(removeList(listUrlInput)) }) }, [adding, dispatch, fetchList, listUrlInput]) const validUrl: boolean = useMemo(() => { return uriToHttp(listUrlInput).length > 0 || Boolean(parseENSAddress(listUrlInput)) }, [listUrlInput]) const handleEnterKey = useCallback( e => { if (validUrl && e.key === 'Enter') { handleAddList() } }, [handleAddList, validUrl] ) const sortedLists = useMemo(() => { const listUrls = Object.keys(lists) return listUrls .filter(listUrl => { return Boolean(lists[listUrl].current) }) .sort((u1, u2) => { const { current: l1 } = lists[u1] const { current: l2 } = lists[u2] if (l1 && l2) { return l1.name.toLowerCase() < l2.name.toLowerCase() ? -1 : l1.name.toLowerCase() === l2.name.toLowerCase() ? 0 : 1 } if (l1) return -1 if (l2) return 1 return 0 }) }, [lists]) return (
{t('manageList')}
Add a list{' '} {t('add')} {addError ? ( {addError} ) : null} {sortedLists.map(listUrl => ( ))}
{t('browseList')}
) }