More catalog updates

This commit is contained in:
Bill 2022-01-20 03:03:21 -05:00
parent 4c1130ba0b
commit 2fc709511c
5 changed files with 163 additions and 104 deletions

View File

@ -8,7 +8,7 @@ import { CatalogEvent } from '../../events';
import { BatchUpdates } from '../../hooks';
import { useUiEvent } from '../../hooks/events/ui/ui-event';
import { SendMessageHook } from '../../hooks/messages/message-event';
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsView, NitroCardView } from '../../layout';
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout';
import { CatalogMessageHandler } from './CatalogMessageHandler';
import { CatalogPage } from './common/CatalogPage';
import { CatalogType } from './common/CatalogType';
@ -24,7 +24,6 @@ import { CatalogGiftView } from './views/gift/CatalogGiftView';
import { CatalogNavigationView } from './views/navigation/CatalogNavigationView';
import { CatalogPageView } from './views/page/CatalogPageView';
import { MarketplacePostOfferView } from './views/page/layout/marketplace/MarketplacePostOfferView';
import { CatalogTabsViews } from './views/tabs/CatalogTabsView';
const DUMMY_PAGE_ID_FOR_OFFER_SEARCH: number = -12345678;
const REQUESTED_PAGE = new RequestedPage();
@ -32,14 +31,14 @@ const REQUESTED_PAGE = new RequestedPage();
export const CatalogView: FC<{}> = props =>
{
const [ isVisible, setIsVisible ] = useState(false);
const [ isInitialized, setIsInitialized ] = useState(false);
const [ isBusy, setIsBusy ] = useState(false);
const [ forceRefresh, setForceRefresh ] = useState(false);
const [ pageId, setPageId ] = useState(-1);
const [ previousPageId, setPreviousPageId ] = useState(-1);
const [ currentType, setCurrentType ] = useState(CatalogType.NORMAL);
const [ rootNode, setRootNode ] = useState<ICatalogNode>(null);
const [ currentOffers, setCurrentOffers ] = useState<Map<number, ICatalogNode[]>>(null);
const [ offersToNodes, setOffersToNodes ] = useState<Map<number, ICatalogNode[]>>(null);
const [ currentPage, setCurrentPage ] = useState<ICatalogPage>(null);
const [ currentOffer, setCurrentOffer ] = useState<IPurchasableOffer>(null);
const [ purchasableOffer, setPurchasableOffer ] = useState<IPurchasableOffer>(null);
@ -57,7 +56,7 @@ export const CatalogView: FC<{}> = props =>
setPageId(-1);
setPreviousPageId(-1);
setRootNode(null);
setCurrentOffers(null);
setOffersToNodes(null);
setCurrentPage(null);
setCurrentOffer(null);
setPurchasableOffer(null);
@ -65,11 +64,27 @@ export const CatalogView: FC<{}> = props =>
setActiveNodes([]);
setSearchResult(null);
setFrontPageItems([]);
setIsInitialized(false);
setIsVisible(true);
});
}, []);
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;
@ -80,7 +95,7 @@ export const CatalogView: FC<{}> = props =>
setPageId(pageId);
});
SendMessageHook(new GetCatalogPageComposer(pageId, offerId, currentType));
if(pageId > -1) SendMessageHook(new GetCatalogPageComposer(pageId, offerId, currentType));
}, [ currentType ]);
const selectOffer = useCallback((offerId: number) =>
@ -123,39 +138,85 @@ export const CatalogView: FC<{}> = props =>
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;
const newNodes: ICatalogNode[] = [];
for(const n of prevValue)
for(const existing of prevValue)
{
n.deactivate();
existing.deactivate();
if(n.depth < targetNode.depth)
{
newNodes.push(n);
}
else
{
n.close();
}
if(nodes.indexOf(existing) === -1) existing.close();
}
targetNode.activate();
for(const n of nodes)
{
n.activate();
if(n === targetNode.parent) n.open();
}
if(isActive && isOpen) targetNode.close();
else targetNode.open();
if(newNodes.indexOf(targetNode) < 0) newNodes.push(targetNode);
return newNodes;
return nodes;
});
if(targetNode.pageId > -1) loadCatalogPage(targetNode.pageId, offerId);
}, [ setActiveNodes, loadCatalogPage ]);
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 onCatalogEvent = useCallback((event: CatalogEvent) =>
{
switch(event.type)
@ -193,7 +254,7 @@ export const CatalogView: FC<{}> = props =>
switch(parts[2])
{
case 'offerId':
openPageByOfferId(parseInt(parts[3]));
return;
}
}
@ -205,7 +266,7 @@ export const CatalogView: FC<{}> = props =>
return;
}
}, []);
}, [ openPageByOfferId ]);
useEffect(() =>
{
@ -236,73 +297,48 @@ export const CatalogView: FC<{}> = props =>
useEffect(() =>
{
if(!isVisible) return;
if(!isVisible || rootNode) return;
if(!isInitialized)
{
SendMessageHook(new GetMarketplaceConfigurationMessageComposer());
SendMessageHook(new GetGiftWrappingConfigurationComposer());
SendMessageHook(new GetClubGiftInfo());
SendMessageHook(new GetCatalogIndexComposer(currentType));
}
}, [ isVisible, isInitialized, currentType ]);
SendMessageHook(new GetMarketplaceConfigurationMessageComposer());
SendMessageHook(new GetGiftWrappingConfigurationComposer());
SendMessageHook(new GetClubGiftInfo());
SendMessageHook(new GetCatalogIndexComposer(currentType));
}, [ isVisible, rootNode, currentType ]);
useEffect(() =>
{
if(!rootNode) return;
setIsInitialized(true);
if(!isVisible || !rootNode) return;
switch(REQUESTED_PAGE.requestType)
{
case RequestedPage.REQUEST_TYPE_NONE:
BatchUpdates(() =>
if(activeNodes && activeNodes.length) return;
if(rootNode.isBranch)
{
setIsInitialized(true);
if(rootNode.isBranch)
for(const child of rootNode.children)
{
for(const child of rootNode.children)
if(child && child.isVisible)
{
if(child && child.isVisible)
{
setCurrentTab(child);
activateNode(child);
return;
}
return;
}
}
});
}
return;
case RequestedPage.REQUEST_TYPE_ID:
REQUESTED_PAGE.resetRequest();
return;
case RequestedPage.REQUEST_TYPE_OFFER:
openPageByOfferId(REQUESTED_PAGE.requestedByOfferId);
REQUESTED_PAGE.resetRequest();
return;
case RequestedPage.REQUEST_TYPE_NAME:
REQUESTED_PAGE.resetRequest();
return;
}
}, [ rootNode ]);
useEffect(() =>
{
if(!currentTab) return;
if(currentTab.children.length)
{
for(const child of currentTab.children)
{
if(!child.isVisible) continue;
activateNode(child);
return;
}
}
else
{
loadCatalogPage(currentTab.pageId, -1);
}
}, [ currentTab, activateNode, loadCatalogPage ]);
}, [ isVisible, rootNode, activeNodes, activateNode, openPageByOfferId ]);
useEffect(() =>
{
@ -312,18 +348,33 @@ export const CatalogView: FC<{}> = props =>
}, [ currentPage ]);
return (
<CatalogContextProvider value={ { isVisible, isBusy, setIsBusy, pageId, currentType, setCurrentType, rootNode, setRootNode, currentOffers, setCurrentOffers, currentPage, setCurrentPage, currentOffer, setCurrentOffer, purchasableOffer, setPurchasableOffer, activeNodes, setActiveNodes, searchResult, setSearchResult, frontPageItems, setFrontPageItems, roomPreviewer, resetState, loadCatalogPage, showCatalogPage, activateNode, catalogState, dispatchCatalogState } }>
<CatalogContextProvider value={ { isVisible, isBusy, setIsBusy, pageId, currentType, setCurrentType, rootNode, setRootNode, currentOffers: offersToNodes, setCurrentOffers: setOffersToNodes, currentPage, setCurrentPage, currentOffer, setCurrentOffer, purchasableOffer, setPurchasableOffer, activeNodes, setActiveNodes, searchResult, setSearchResult, frontPageItems, setFrontPageItems, roomPreviewer, resetState, loadCatalogPage, showCatalogPage, activateNode, catalogState, dispatchCatalogState } }>
<CatalogMessageHandler />
{ isVisible &&
<NitroCardView uniqueKey="catalog" className="nitro-catalog">
<NitroCardHeaderView headerText={ LocalizeText('catalog.title') } onCloseClick={ event => { setIsVisible(false); } } />
<NitroCardTabsView>
<CatalogTabsViews node={ rootNode } currentTab={ currentTab } setCurrentTab={ setCurrentTab } />
{ rootNode && (rootNode.children.length > 0) && rootNode.children.map(child =>
{
if(!child.isVisible) return null;
return (
<NitroCardTabsItemView key={ child.pageId } isActive={ child.isActive } onClick={ event =>
{
if(searchResult) setSearchResult(null);
activateNode(child);
} }>
{ child.localization }
</NitroCardTabsItemView>
);
}) }
</NitroCardTabsView>
<NitroCardContentView>
<Grid>
<Column size={ 3 } overflow="hidden">
<CatalogNavigationView node={ currentTab } />
{ activeNodes && (activeNodes.length > 0) &&
<CatalogNavigationView node={ activeNodes[0] } /> }
</Column>
<Column size={ 9 } overflow="hidden">
<CatalogPageView page={ currentPage } roomPreviewer={ roomPreviewer } />

View File

@ -2,11 +2,12 @@ export class RequestedPage
{
public static REQUEST_TYPE_NONE: number = 0;
public static REQUEST_TYPE_ID: number = 1;
public static REQUEST_TYPE_NAME: number = 2;
public static REQUEST_TYPE_OFFER: number = 2;
public static REQUEST_TYPE_NAME: number = 3;
private _requestType: number;
private _requestById: number;
private _requestedOfferId: number;
private _requestedByOfferId: number;
private _requestByName: string;
constructor()
@ -17,7 +18,9 @@ export class RequestedPage
public resetRequest():void
{
this._requestType = RequestedPage.REQUEST_TYPE_NONE;
this._requestedOfferId = -1;
this._requestById = -1;
this._requestedByOfferId = -1;
this._requestByName = null;
}
public get requestType(): number
@ -36,6 +39,17 @@ export class RequestedPage
this._requestById = id;
}
public get requestedByOfferId(): number
{
return this._requestedByOfferId;
}
public set requestedByOfferId(offerId: number)
{
this._requestType = RequestedPage.REQUEST_TYPE_OFFER;
this._requestedByOfferId = offerId;
}
public get requestByName(): string
{
return this._requestByName;
@ -46,14 +60,4 @@ export class RequestedPage
this._requestType = RequestedPage.REQUEST_TYPE_NAME;
this._requestByName = name;
}
public get requestedOfferId(): number
{
return this._requestedOfferId;
}
public set requestedOfferId(offerId: number)
{
this._requestedOfferId = offerId;
}
}

View File

@ -1,5 +1,6 @@
import { FC } from 'react';
import { Column } from '../../../../../common/Column';
import { Flex } from '../../../../../common/Flex';
import { Grid } from '../../../../../common/Grid';
import { Text } from '../../../../../common/Text';
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
@ -10,24 +11,31 @@ import { CatalogLayoutProps } from './CatalogLayout.types';
export const CatalogLayoutSingleBundleView: FC<CatalogLayoutProps> = props =>
{
const { page = null, roomPreviewer = null } = props;
const { page = null } = props;
const imageUrl = page.localization.getImage(1);
return (
<>
<Grid>
<Column justifyContent="center" size={ 7 } overflow="hidden">
<Column justifyContent="between" size={ 7 } overflow="hidden">
<Text dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } />
<CatalogBundleGridWidgetView />
</Column>
<Column size={ 5 } overflow="hidden">
<Column fullHeight center position="relative">
<CatalogAddOnBadgeWidgetView />
<CatalogSimplePriceWidgetView />
{ imageUrl && <img className="" alt="" src={ imageUrl } /> }
<Column fit overflow="hidden" className="bg-muted p-2 rounded">
<CatalogBundleGridWidgetView className="nitro-catalog-layout-bundle-grid" fullWidth />
</Column>
</Column>
<Column size={ 5 } overflow="hidden" gap={ 1 }>
<Text center overflow="auto">{ page.localization.getText(1) }</Text>
<Flex fullHeight center position="relative" overflow="hidden">
{ imageUrl && <img className="" alt="" src={ imageUrl } /> }
<CatalogAddOnBadgeWidgetView position="absolute" className="bottom-0 start-0" />
</Flex>
<Column gap={ 1 } position="relative">
<Flex center>
<CatalogSimplePriceWidgetView />
</Flex>
<CatalogPurchaseWidgetView />
</Column>
<CatalogPurchaseWidgetView />
</Column>
</Grid>
</>

View File

@ -79,19 +79,14 @@ export const CatalogSearchView: FC<{}> = props =>
{
setCurrentPage((new CatalogPage(-1, 'default_3x3', new PageLocalization([], []), offers, false, 1) as ICatalogPage));
setSearchResult(new SearchResult(search, offers, nodes.filter(node => (node.isVisible))));
setActiveNodes([]);
setActiveNodes(prevValue => prevValue.slice(0, 1));
});
}, [ currentOffers, currentType, rootNode, setCurrentPage, setSearchResult, setActiveNodes ]);
useEffect(() =>
{
if(!searchValue)
{
processSearch(searchValue);
return;
}
if(!searchValue) return;
const timeout = setTimeout(() => processSearch(searchValue), 300);
return () =>
@ -102,7 +97,7 @@ export const CatalogSearchView: FC<{}> = props =>
return (
<Flex gap={ 1 }>
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } value={ searchValue } onChange={ event => setSearchValue(event.target.value) } />
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } value={ searchValue } onChange={ event => setSearchValue(event.target.value) } onKeyDown={ event => (event.code === 'Enter') && processSearch(searchValue) } />
<Button variant="primary" size="sm">
<FontAwesomeIcon icon="search" />
</Button>

View File

@ -13,4 +13,5 @@ export class CatalogEvent extends NitroEvent
public static PURCHASE_APPROVED: string = 'CE_PURCHASE_APPROVED';
public static INIT_GIFT: string = 'CE_INIT_GIFT';
public static CATALOG_RESET: string = 'CE_RESET';
public static CATALOG_INVISIBLE_PAGE_VISITED: string = 'CE_CATALOG_INVISIBLE_PAGE_VISITED';
}