import { FrontPageItem, GetCatalogIndexComposer, GetCatalogPageComposer, GetClubGiftInfo, GetGiftWrappingConfigurationComposer, GetMarketplaceConfigurationMessageComposer, ILinkEventTracker, RoomPreviewer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import { AddEventLinkTracker, GetRoomEngine, LocalizeText, RemoveLinkEventTracker } from '../../api'; import { CREDITS, PlaySound } from '../../api/utils/PlaySound'; import { Column } from '../../common/Column'; import { Grid } from '../../common/Grid'; import { CatalogPurchasedEvent } from '../../events'; import { BatchUpdates } from '../../hooks'; import { useUiEvent } from '../../hooks/events/ui/ui-event'; import { SendMessageHook } from '../../hooks/messages/message-event'; import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout'; import { CatalogContextProvider } from './CatalogContext'; import { CatalogMessageHandler } from './CatalogMessageHandler'; import { CatalogPage } from './common/CatalogPage'; import { CatalogType } from './common/CatalogType'; import { ICatalogNode } from './common/ICatalogNode'; import { ICatalogOptions } from './common/ICatalogOptions'; import { ICatalogPage } from './common/ICatalogPage'; import { IPageLocalization } from './common/IPageLocalization'; import { IPurchasableOffer } from './common/IPurchasableOffer'; import { IPurchaseOptions } from './common/IPurchaseOptions'; import { RequestedPage } from './common/RequestedPage'; import { SearchResult } from './common/SearchResult'; import { CatalogGiftView } from './views/gift/CatalogGiftView'; import { CatalogNavigationView } from './views/navigation/CatalogNavigationView'; import { GetCatalogLayout } from './views/page/layout/GetCatalogLayout'; import { MarketplacePostOfferView } from './views/page/layout/marketplace/MarketplacePostOfferView'; const REQUESTED_PAGE = new RequestedPage(); export const CatalogView: FC<{}> = props => { const [ isVisible, setIsVisible ] = useState(false); const [ isBusy, setIsBusy ] = useState(false); const [ pageId, setPageId ] = useState(-1); const [ previousPageId, setPreviousPageId ] = useState(-1); const [ currentType, setCurrentType ] = useState(CatalogType.NORMAL); const [ rootNode, setRootNode ] = useState(null); const [ offersToNodes, setOffersToNodes ] = useState>(null); const [ currentPage, setCurrentPage ] = useState(null); const [ currentOffer, setCurrentOffer ] = useState(null); const [ activeNodes, setActiveNodes ] = useState([]); const [ searchResult, setSearchResult ] = useState(null); const [ frontPageItems, setFrontPageItems ] = useState([]); const [ roomPreviewer, setRoomPreviewer ] = useState(null); const [ navigationHidden, setNavigationHidden ] = useState(false); const [ purchaseOptions, setPurchaseOptions ] = useState({}); const [ catalogOptions, setCatalogOptions ] = useState({}); const resetState = useCallback(() => { BatchUpdates(() => { setPageId(-1); setPreviousPageId(-1); setRootNode(null); setOffersToNodes(null); setCurrentPage(null); setCurrentOffer(null); setActiveNodes([]); setSearchResult(null); setFrontPageItems([]); setIsVisible(true); }); }, []); const getNodeById = useCallback((id: number, node: ICatalogNode) => { if((node.pageId === id) && (node !== rootNode)) return node; for(const child of node.children) { const found = (getNodeById(id, child) as ICatalogNode); if(found) return found; } return null; }, [ rootNode ]); const getNodeByName = useCallback((name: string, node: ICatalogNode) => { if((node.pageName === name) && (node !== rootNode)) return node; for(const child of node.children) { const found = (getNodeByName(name, child) as ICatalogNode); if(found) return found; } return null; }, [ rootNode ]); const getNodesByOfferId = useCallback((offerId: number, flag: boolean = false) => { if(!offersToNodes || !offersToNodes.size) return null; if(flag) { const nodes: ICatalogNode[] = []; const offers = offersToNodes.get(offerId); if(offers && offers.length) for(const offer of offers) (offer.isVisible && nodes.push(offer)); if(nodes.length) return nodes; } return offersToNodes.get(offerId); }, [ offersToNodes ]); const loadCatalogPage = useCallback((pageId: number, offerId: number) => { if(pageId < 0) return; BatchUpdates(() => { setIsBusy(true); setPageId(pageId); }); if(pageId > -1) SendMessageHook(new GetCatalogPageComposer(pageId, offerId, currentType)); }, [ currentType ]); const showCatalogPage = useCallback((pageId: number, layoutCode: string, localization: IPageLocalization, offers: IPurchasableOffer[], offerId: number, acceptSeasonCurrencyAsCredits: boolean) => { const catalogPage = (new CatalogPage(pageId, layoutCode, localization, offers, acceptSeasonCurrencyAsCredits) as ICatalogPage); BatchUpdates(() => { setCurrentPage(catalogPage); setPreviousPageId(prevValue => ((pageId !== -1) ? pageId : prevValue)); setNavigationHidden(false); if((offerId > -1) && catalogPage.offers.length) { for(const offer of catalogPage.offers) { if(offer.offerId !== offerId) continue; setCurrentOffer(offer) break; } } }); }, []); const activateNode = useCallback((targetNode: ICatalogNode, offerId: number = -1) => { if(targetNode.parent.pageName === 'root') { if(targetNode.children.length) { for(const child of targetNode.children) { if(!child.isVisible) continue; targetNode = child; break; } } } const nodes: ICatalogNode[] = []; let node = targetNode; while(node && (node.pageName !== 'root')) { nodes.push(node); node = node.parent; } nodes.reverse(); setActiveNodes(prevValue => { const isActive = (prevValue.indexOf(targetNode) >= 0); const isOpen = targetNode.isOpen; for(const existing of prevValue) { existing.deactivate(); if(nodes.indexOf(existing) === -1) existing.close(); } for(const n of nodes) { n.activate(); if(n.parent) n.open(); if((n === targetNode.parent) && n.children.length) n.open(); } if(isActive && isOpen) targetNode.close(); else targetNode.open(); return nodes; }); if(targetNode.pageId > -1) loadCatalogPage(targetNode.pageId, offerId); }, [ setActiveNodes, loadCatalogPage ]); const openPageById = useCallback((id: number) => { BatchUpdates(() => { setSearchResult(null); if(!isVisible) { REQUESTED_PAGE.requestById = id; setIsVisible(true); } else { const node = getNodeById(id, rootNode); if(node) activateNode(node); } }); }, [ isVisible, rootNode, getNodeById, activateNode ]); const openPageByName = useCallback((name: string) => { BatchUpdates(() => { setSearchResult(null); if(!isVisible) { REQUESTED_PAGE.requestByName = name; setIsVisible(true); } else { const node = getNodeByName(name, rootNode); if(node) activateNode(node); } }); }, [ isVisible, rootNode, getNodeByName, activateNode ]); const openPageByOfferId = useCallback((offerId: number) => { BatchUpdates(() => { setSearchResult(null); if(!isVisible) { REQUESTED_PAGE.requestedByOfferId = offerId; setIsVisible(true); } else { const nodes = getNodesByOfferId(offerId); if(!nodes || !nodes.length) return; activateNode(nodes[0], offerId); } }); }, [ isVisible, getNodesByOfferId, activateNode ]); const onCatalogPurchasedEvent = useCallback((event: CatalogPurchasedEvent) => { PlaySound(CREDITS); }, []); useUiEvent(CatalogPurchasedEvent.PURCHASE_SUCCESS, onCatalogPurchasedEvent); const linkReceived = useCallback((url: string) => { const parts = url.split('/'); if(parts.length < 2) return; switch(parts[1]) { case 'show': setIsVisible(true); return; case 'hide': setIsVisible(false); return; case 'toggle': setIsVisible(prevValue => !prevValue); return; case 'open': if(parts.length > 2) { if(parts.length === 4) { switch(parts[2]) { case 'offerId': openPageByOfferId(parseInt(parts[3])); return; } } else { openPageByName(parts[2]); } } else { setIsVisible(true); } return; } }, [ openPageByOfferId, openPageByName ]); useEffect(() => { const linkTracker: ILinkEventTracker = { linkReceived, eventUrlPrefix: 'catalog/' }; AddEventLinkTracker(linkTracker); return () => RemoveLinkEventTracker(linkTracker); }, [ linkReceived ]); useEffect(() => { setRoomPreviewer(new RoomPreviewer(GetRoomEngine(), ++RoomPreviewer.PREVIEW_COUNTER)); return () => { setRoomPreviewer(prevValue => { prevValue.dispose(); return null; }); } }, []); useEffect(() => { if(!isVisible || rootNode) return; SendMessageHook(new GetMarketplaceConfigurationMessageComposer()); SendMessageHook(new GetGiftWrappingConfigurationComposer()); SendMessageHook(new GetClubGiftInfo()); SendMessageHook(new GetCatalogIndexComposer(currentType)); }, [ isVisible, rootNode, currentType ]); useEffect(() => { if(!isVisible || !rootNode) return; switch(REQUESTED_PAGE.requestType) { case RequestedPage.REQUEST_TYPE_NONE: if(activeNodes && activeNodes.length) return; if(rootNode.isBranch) { for(const child of rootNode.children) { if(child && child.isVisible) { activateNode(child); return; } } } return; case RequestedPage.REQUEST_TYPE_ID: openPageById(REQUESTED_PAGE.requestById); REQUESTED_PAGE.resetRequest(); return; case RequestedPage.REQUEST_TYPE_OFFER: openPageByOfferId(REQUESTED_PAGE.requestedByOfferId); REQUESTED_PAGE.resetRequest(); return; case RequestedPage.REQUEST_TYPE_NAME: openPageByName(REQUESTED_PAGE.requestByName); REQUESTED_PAGE.resetRequest(); return; } }, [ isVisible, rootNode, activeNodes, activateNode, openPageById, openPageByOfferId, openPageByName ]); useEffect(() => { if(!searchResult && currentPage && (currentPage.pageId === -1)) openPageById(previousPageId); }, [ searchResult, currentPage, previousPageId, openPageById ]); useEffect(() => { return () => setCurrentOffer(null); }, [ currentPage ]); return ( { isVisible && { setIsVisible(false); } } /> { rootNode && (rootNode.children.length > 0) && rootNode.children.map(child => { if(!child.isVisible) return null; return ( { if(searchResult) setSearchResult(null); activateNode(child); } }> { child.localization } ); }) } { !navigationHidden && { activeNodes && (activeNodes.length > 0) && } } { GetCatalogLayout(currentPage, () => setNavigationHidden(true)) } } ); }