import React, { useRef, useEffect, useContext, useState, useCallback, } from "react"; import { Flex, Spinner, Button, Center, Text, Menu, MenuButton, MenuList, MenuItem, MenuGroup, IconButton, Input, Select, Drawer, DrawerBody, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerContent, DrawerCloseButton, useDisclosure, Tag, TagLabel, TagCloseButton, Stack, Spacer, } from "@chakra-ui/react"; import { useSubscriptions } from "../core/hooks"; import StreamEntry from "./StreamEntry"; import UIContext from "../core/providers/UIProvider/context"; import { FaFilter } from "react-icons/fa"; import useStream from "../core/hooks/useStream"; import { ImCancelCircle } from "react-icons/im"; const FILTER_TYPES = { ADDRESS: 0, GAS: 1, GAS_PRICE: 2, AMMOUNT: 3, HASH: 4, DISABLED: 99, }; const DIRECTIONS = { SOURCE: "from", DESTINATION: "to" }; const CONDITION = { EQUAL: 0, CONTAINS: 1, LESS: 2, LESS_EQUAL: 3, GREATER: 4, GREATER_EQUAL: 5, NOT_EQUAL: 6, }; const EntriesNavigation = () => { const ui = useContext(UIContext); const { isOpen, onOpen, onClose } = useDisclosure(); const { subscriptionsCache } = useSubscriptions(); const [newFilterState, setNewFilterState] = useState([ { type: FILTER_TYPES.ADDRESS, direction: DIRECTIONS.SOURCE, condition: CONDITION.EQUAL, value: null, }, ]); const [filterState, setFilterState] = useState([]); const loadMoreButtonRef = useRef(null); const [streamBoundary, setStreamBoundary] = useState({ start_time: null, end_time: null, include_start: false, include_end: true, next_event_time: null, previous_event_time: null, }); const updateStreamBoundaryWith = (pageBoundary) => { if (!pageBoundary) { return streamBoundary; } let newBoundary = { ...streamBoundary }; // We do not check if there is no overlap between the streamBoundary and the pageBoundary - we assume // that there *is* an overlap and even if there isn't the stream should gracefully respect the // pageBoundary because that was the most recent request the user made. // TODO(zomglings): If there is no overlap in boundaries, replace streamBoundary with pageBoundary. // No overlap logic: // if () { // setStreamBoundary(pageBoundary) // return pageBoundary // } if ( !newBoundary.start_time || (pageBoundary.start_time && pageBoundary.start_time <= newBoundary.start_time) ) { newBoundary.start_time = pageBoundary.start_time; newBoundary.include_start = newBoundary.include_start || pageBoundary.include_start; } newBoundary.include_start = newBoundary.include_start || pageBoundary.include_start; if ( !newBoundary.end_time || (pageBoundary.end_time && pageBoundary.end_time >= newBoundary.end_time) ) { newBoundary.end_time = pageBoundary.end_time; newBoundary.include_end = newBoundary.include_end || pageBoundary.include_end; } newBoundary.include_end = newBoundary.include_end || pageBoundary.include_end; if ( !newBoundary.next_event_time || !pageBoundary.next_event_time || (pageBoundary.next_event_time && pageBoundary.next_event_time > newBoundary.next_event_time) ) { newBoundary.next_event_time = pageBoundary.next_event_time; } if ( !newBoundary.previous_event_time || !pageBoundary.previous_event_time || (pageBoundary.previous_event_time && pageBoundary.previous_event_time < newBoundary.previous_event_time) ) { newBoundary.previous_event_time = pageBoundary.previous_event_time; } setStreamBoundary(newBoundary); return newBoundary; }; const { EntriesPages, isLoading, refetch, isFetching, remove } = useStream({ searchQuery: ui.searchTerm, start_time: streamBoundary.start_time, end_time: streamBoundary.end_time, include_start: streamBoundary.include_start, include_end: streamBoundary.include_end, updateStreamBoundaryWith: updateStreamBoundaryWith, streamBoundary: streamBoundary, setStreamBoundary: setStreamBoundary, isContent: false, }); useEffect(() => { if (!streamBoundary.start_time && !streamBoundary.end_time) { refetch(); } }, [streamBoundary, refetch]); const setFilterProps = useCallback( (filterIdx, props) => { const newFilterProps = [...newFilterState]; newFilterProps[filterIdx] = { ...newFilterProps[filterIdx], ...props }; setNewFilterState(newFilterProps); }, [newFilterState, setNewFilterState] ); useEffect(() => { if ( subscriptionsCache.data?.subscriptions[0]?.id && newFilterState[0]?.value === null ) { setFilterProps(0, { value: subscriptionsCache?.data?.subscriptions[0]?.address, }); } }, [subscriptionsCache, newFilterState, setFilterProps]); const entriesPagesData = EntriesPages ? EntriesPages.data.map((page) => { return page; }) : [""]; const entries = entriesPagesData.flat(); const canCreate = false; const canDelete = false; const dropNewFilterArrayItem = (idx) => { const oldArray = [...newFilterState]; const newArray = oldArray.filter(function (ele) { return ele != oldArray[idx]; }); setNewFilterState(newArray); }; const dropFilterArrayItem = (idx) => { const oldArray = [...filterState]; const newArray = oldArray.filter(function (ele) { return ele != oldArray[idx]; }); setFilterState(newArray); setNewFilterState(newArray); ui.setSearchTerm( newArray .map((filter) => { return filter.direction + ":" + filter.value; }) .join("+") ); }; const handleFilterSubmit = () => { setFilterState(newFilterState); ui.setSearchTerm( newFilterState .map((filter) => { return filter.direction + ":" + filter.value; }) .join("+") ); onClose(); }; const handleAddressChange = (idx) => (e) => { setFilterProps(idx, { value: e.target.value }); }; const handleConditionChange = (idx) => (e) => { setFilterProps(idx, { condition: parseInt(e.target.value) }); }; const handleFilterStateCallback = (props) => { const currentFilterState = [...filterState]; currentFilterState.push({ ...props }); ui.setSearchTerm( currentFilterState .map((filter) => { return filter.direction + ":" + filter.value; }) .join("+") ); setFilterState(currentFilterState); }; if (subscriptionsCache.isLoading) return ""; return ( {entries && !isLoading ? ( <> {`Filter results`} Source: {newFilterState.map((filter, idx) => { if (filter.type === FILTER_TYPES.DISABLED) return ""; return ( {filter.type === FILTER_TYPES.ADDRESS && ( <> {filter.direction === DIRECTIONS.SOURCE ? `From:` : `To:`} {filter.direction === DIRECTIONS.SOURCE && ( )} {filter.direction === DIRECTIONS.DESTINATION && ( setFilterProps(idx, { value: e.target.value, }) } placeholder="Type in address" /> )} )} dropNewFilterArrayItem(idx)} icon={} /> ); })} Add filter row setNewFilterState([ ...newFilterState, { type: FILTER_TYPES.ADDRESS, direction: DIRECTIONS.SOURCE, condition: CONDITION.EQUAL, value: subscriptionsCache?.data?.subscriptions[0] ?.address, }, ]) } > Source setNewFilterState([ ...newFilterState, { type: FILTER_TYPES.ADDRESS, direction: DIRECTIONS.DESTINATION, condition: CONDITION.EQUAL, value: subscriptionsCache?.data?.subscriptions[0] ?.address, }, ]) } > Destination {filterState.map((filter, idx) => { if (filter.type === FILTER_TYPES.DISABLED) return ""; return ( {filter?.type === FILTER_TYPES.ADDRESS && ( {filter.condition === CONDITION.NOT_EQUAL && "Not "} {filter.direction === DIRECTIONS.SOURCE ? "From: " : "To: "} {subscriptionsCache?.data?.subscriptions.find( (subscription) => subscription.address === filter.value )?.label ?? filter.value} )} dropFilterArrayItem(idx)} /> ); })} } /> handleScroll(e)} > {!isFetching ? ( ) : ( )} {streamBoundary.next_event_time && streamBoundary.end_time != 0 && !isFetching ? ( ) : ( "" // some strange behaivior without else condition return 0 wich can see on frontend page )} {entries ?.sort((a, b) => b.timestamp - a.timestamp) // TODO(Andrey) improve that for bi chunks of data sorting can take time .map((entry, idx) => ( ))} {streamBoundary.previous_event_time && !isFetching ? (
) : (
{!isFetching ? ( "Тransactions not found. You can subscribe to more addresses in Subscriptions menu." ) : ( )}
)} {streamBoundary.previous_event_time && isLoading ? (
) : ( "" )}
) : (
)}
); }; export default EntriesNavigation;