mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 23:16:21 +00:00
Add emulator stats dashboard and refresh classic UI views
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { AddLinkEventTracker, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect } from 'react';
|
||||
import { FaCog, FaEdit, FaEye, FaEyeSlash, FaPlus, FaTrash } from 'react-icons/fa';
|
||||
import { CatalogType, GetConfigurationValue, LocalizeText } from '../../api';
|
||||
import { Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
|
||||
import { CatalogType, LocalizeText } from '../../api';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
|
||||
import { useCatalog } from '../../hooks';
|
||||
import { CatalogAdminProvider, useCatalogAdmin } from './CatalogAdminContext';
|
||||
import { CatalogAdminOfferEditView } from './views/admin/CatalogAdminOfferEditView';
|
||||
@@ -10,7 +10,9 @@ import { CatalogAdminPageEditView } from './views/admin/CatalogAdminPageEditView
|
||||
import { CatalogBuildersClubStatusView } from './views/catalog-header/CatalogBuildersClubStatusView';
|
||||
import { CatalogIconView } from './views/catalog-icon/CatalogIconView';
|
||||
import { CatalogGiftView } from './views/gift/CatalogGiftView';
|
||||
import { CatalogBreadcrumbView } from './views/navigation/CatalogBreadcrumbView';
|
||||
import { CatalogNavigationView } from './views/navigation/CatalogNavigationView';
|
||||
import { CatalogSearchView } from './views/page/common/CatalogSearchView';
|
||||
import { GetCatalogLayout } from './views/page/layout/GetCatalogLayout';
|
||||
import { MarketplacePostOfferView } from './views/page/layout/marketplace/MarketplacePostOfferView';
|
||||
|
||||
@@ -23,7 +25,6 @@ const CatalogClassicViewInner: FC<{}> = () =>
|
||||
const hasPendingChanges = catalogAdmin?.hasPendingChanges ?? false;
|
||||
const publishCatalog = catalogAdmin?.publishCatalog ?? (() => {});
|
||||
const loading = catalogAdmin?.loading ?? false;
|
||||
|
||||
const isMod = GetSessionDataManager().isModerator;
|
||||
const buildersClubHeaderStyle = (currentType === CatalogType.BUILDER)
|
||||
? { borderColor: '#d79d2e', borderBottomColor: '#000', background: 'linear-gradient(180deg, #d89f2d 0%, #c68515 100%)' }
|
||||
@@ -113,21 +114,20 @@ const CatalogClassicViewInner: FC<{}> = () =>
|
||||
return (
|
||||
<>
|
||||
{ isVisible &&
|
||||
<NitroCardView className="w-[630px] h-[400px]" style={ GetConfigurationValue('catalog.headers') ? { width: 710 } : {} } uniqueKey="catalog">
|
||||
<NitroCardView classNames={ [ 'nitro-catalog-classic-window' ] } isResizable={ false } uniqueKey="catalog">
|
||||
<NitroCardHeaderView className={ currentType === CatalogType.BUILDER ? 'builders-club-card-header' : '' } headerText={ LocalizeText('catalog.title') } onCloseClick={ () => setIsVisible(false) } style={ buildersClubHeaderStyle } />
|
||||
{ /* Admin banner */ }
|
||||
{ adminMode &&
|
||||
<div className="flex items-center justify-between bg-warning text-dark text-[10px] font-bold px-3 py-0.5 uppercase tracking-wider" style={ { textShadow: '0 1px 0 rgba(255,255,255,0.3)' } }>
|
||||
<span>⚙ Admin Mode</span>
|
||||
<div className="nitro-catalog-classic-admin-banner flex items-center justify-between text-[10px] font-bold px-3 py-0.5 uppercase tracking-wider">
|
||||
<span>Admin Mode</span>
|
||||
<button
|
||||
className={ `px-3 py-0.5 rounded text-[10px] font-bold uppercase cursor-pointer transition-all ${ hasPendingChanges ? 'bg-success text-white animate-pulse shadow-md' : 'bg-white/50 text-dark hover:bg-success hover:text-white' }` }
|
||||
disabled={ loading }
|
||||
onClick={ () => publishCatalog() }
|
||||
>
|
||||
{ loading ? '...' : '⬆ Publish' }
|
||||
{ loading ? '...' : 'Publish' }
|
||||
</button>
|
||||
</div> }
|
||||
<NitroCardTabsView>
|
||||
<NitroCardTabsView classNames={ [ 'nitro-catalog-classic-tabs-shell' ] } justifyContent="start">
|
||||
{ rootNode && (rootNode.children.length > 0) && rootNode.children.map((child, index) =>
|
||||
{
|
||||
if(!adminMode && !child.isVisible) return null;
|
||||
@@ -140,10 +140,10 @@ const CatalogClassicViewInner: FC<{}> = () =>
|
||||
if(searchResult) setSearchResult(null);
|
||||
|
||||
activateNode(child);
|
||||
} } >
|
||||
<div className={ `flex items-center gap-${ GetConfigurationValue('catalog.tab.icons') ? 1 : 0 } ${ isHidden ? 'opacity-40' : '' }` }>
|
||||
{ GetConfigurationValue('catalog.tab.icons') && <CatalogIconView icon={ child.iconId } /> }
|
||||
{ child.localization }
|
||||
} }>
|
||||
<div className={ `flex items-center gap-1 ${ isHidden ? 'opacity-40' : '' }` }>
|
||||
<CatalogIconView icon={ child.iconId } />
|
||||
<span className="truncate">{ child.localization }</span>
|
||||
{ adminMode && isHidden && <FaEyeSlash className="text-[8px] text-danger ml-1" /> }
|
||||
{ adminMode &&
|
||||
<div className="flex items-center gap-0.5 ml-1" onClick={ e => e.stopPropagation() }>
|
||||
@@ -160,17 +160,15 @@ const CatalogClassicViewInner: FC<{}> = () =>
|
||||
</NitroCardTabsItemView>
|
||||
);
|
||||
}) }
|
||||
{ /* Admin toggle button in tabs bar */ }
|
||||
{ isMod &&
|
||||
<NitroCardTabsItemView isActive={ adminMode } onClick={ () => setAdminMode(!adminMode) }>
|
||||
<FaCog className={ `text-[10px] ${ adminMode ? 'animate-spin' : '' }` } style={ adminMode ? { animationDuration: '3s' } : {} } />
|
||||
</NitroCardTabsItemView> }
|
||||
</NitroCardTabsView>
|
||||
<CatalogBuildersClubStatusView />
|
||||
<NitroCardContentView>
|
||||
{ /* Admin: add new root category */ }
|
||||
<NitroCardContentView classNames={ [ 'nitro-catalog-classic-content-shell' ] }>
|
||||
<CatalogBuildersClubStatusView />
|
||||
{ adminMode && rootNode &&
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<div className="flex items-center gap-2 mb-1 nitro-catalog-classic-admin-actions">
|
||||
<button
|
||||
className="flex items-center gap-1 text-[9px] text-success hover:text-green-800 cursor-pointer transition-colors"
|
||||
onClick={ () => catalogAdmin.createPage({ caption: 'New Category', catalogMode: currentType, pageLayout: 'default_3x3', minRank: 1, visible: '1', enabled: '1', orderNum: 99, parentId: rootNode.pageId }) }
|
||||
@@ -186,17 +184,30 @@ const CatalogClassicViewInner: FC<{}> = () =>
|
||||
<span>{ LocalizeText('catalog.admin.root') }</span>
|
||||
</button>
|
||||
</div> }
|
||||
<Grid>
|
||||
<div className={ `nitro-catalog-classic-stage ${ navigationHidden ? 'is-navigation-hidden' : '' }` }>
|
||||
{ !navigationHidden &&
|
||||
<Column overflow="auto" size={ 3 }>
|
||||
{ activeNodes && (activeNodes.length > 0) &&
|
||||
<CatalogNavigationView node={ activeNodes[0] } /> }
|
||||
</Column> }
|
||||
<Column overflow="hidden" size={ !navigationHidden ? 9 : 12 }>
|
||||
{ adminMode && <CatalogAdminPageEditView /> }
|
||||
{ GetCatalogLayout(currentPage, () => setNavigationHidden(true)) }
|
||||
</Column>
|
||||
</Grid>
|
||||
<div className="nitro-catalog-classic-sidebar">
|
||||
<div className="nitro-catalog-classic-search-shell">
|
||||
<CatalogSearchView />
|
||||
</div>
|
||||
<div className="nitro-catalog-classic-navigation-shell">
|
||||
{ activeNodes && (activeNodes.length > 0) &&
|
||||
<CatalogNavigationView node={ activeNodes[0] } /> }
|
||||
</div>
|
||||
</div> }
|
||||
<div className="nitro-catalog-classic-layout-shell">
|
||||
<div className="nitro-catalog-classic-layout-header-shell">
|
||||
<CatalogBreadcrumbView />
|
||||
<div className="nitro-catalog-classic-layout-hero">
|
||||
{ !!currentPage?.localization?.getImage(0) && <img src={ currentPage.localization.getImage(0) } /> }
|
||||
</div>
|
||||
</div>
|
||||
<div className="nitro-catalog-classic-layout-container">
|
||||
{ adminMode && <CatalogAdminPageEditView /> }
|
||||
{ GetCatalogLayout(currentPage, () => setNavigationHidden(true)) }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView> }
|
||||
<CatalogAdminOfferEditView />
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { FC } from 'react';
|
||||
import { FaChevronRight, FaHome } from 'react-icons/fa';
|
||||
import { LocalizeText } from '../../../../api';
|
||||
import { useCatalog } from '../../../../hooks';
|
||||
|
||||
@@ -10,25 +9,20 @@ export const CatalogBreadcrumbView: FC<{}> = () =>
|
||||
if(!activeNodes || activeNodes.length === 0)
|
||||
{
|
||||
return (
|
||||
<div className="flex items-center gap-1.5 text-xs text-catalog-text-muted">
|
||||
<FaHome className="text-[10px]" />
|
||||
<div className="nitro-catalog-classic-breadcrumb">
|
||||
<span>{ LocalizeText('catalog.title') }</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 text-[11px] text-catalog-text-muted overflow-hidden min-w-0">
|
||||
<FaHome
|
||||
className="text-[10px] cursor-pointer hover:text-catalog-accent transition-colors shrink-0"
|
||||
onClick={ () => activateNode(activeNodes[0]) }
|
||||
/>
|
||||
{ activeNodes.map((node, i) => (
|
||||
<span key={ node.pageId } className="flex items-center gap-1 min-w-0">
|
||||
<FaChevronRight className="text-[7px] opacity-30 shrink-0" />
|
||||
<div className="nitro-catalog-classic-breadcrumb">
|
||||
{ activeNodes.map((node, index) => (
|
||||
<span key={ node.pageId } className="nitro-catalog-classic-breadcrumb-segment">
|
||||
<span className="nitro-catalog-classic-breadcrumb-separator">›</span>
|
||||
<span
|
||||
className={ `truncate ${ i === activeNodes.length - 1 ? 'text-catalog-text font-semibold' : 'cursor-pointer hover:text-catalog-accent transition-colors' }` }
|
||||
onClick={ i < activeNodes.length - 1 ? () => activateNode(node) : undefined }
|
||||
className={ `truncate ${ index === activeNodes.length - 1 ? 'font-semibold' : 'cursor-pointer hover:underline' }` }
|
||||
onClick={ index < activeNodes.length - 1 ? () => activateNode(node) : undefined }
|
||||
>
|
||||
{ node.localization }
|
||||
</span>
|
||||
|
||||
@@ -73,10 +73,10 @@ export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = pro
|
||||
}, [ adminMode, node, catalogAdmin ]);
|
||||
|
||||
return (
|
||||
<div className={ child ? 'pl-1.5 ml-1.5 border-l-2 border-card-grid-item-border' : '' }>
|
||||
<div className={ `nitro-catalog-classic-navigation-node ${ child ? 'is-child' : '' }` }>
|
||||
<div
|
||||
ref={ dragRef }
|
||||
className={ `group/nav flex items-center gap-1.5 px-1.5 py-[3px] mx-0.5 rounded cursor-pointer transition-all duration-100 text-[11px] ${ node.isActive ? 'bg-card-grid-item-active border border-card-grid-item-border-active shadow-inner1px font-bold' : 'border border-transparent hover:bg-card-grid-item-active' } ${ isDragOver ? 'ring-2 ring-primary ring-offset-1 bg-primary/10' : '' }` }
|
||||
className={ `nitro-catalog-classic-navigation-item group/nav ${ node.isActive ? 'is-active' : '' } ${ node.isBranch ? 'is-branch' : 'is-leaf' } ${ node.isOpen ? 'is-open' : '' } ${ isDragOver ? 'is-drag-over' : '' }` }
|
||||
draggable={ adminMode }
|
||||
onClick={ () => activateNode(node) }
|
||||
onDragLeave={ adminMode ? handleDragLeave : undefined }
|
||||
@@ -85,13 +85,13 @@ export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = pro
|
||||
onDrop={ adminMode ? handleDrop : undefined }
|
||||
>
|
||||
{ adminMode &&
|
||||
<FaArrowsAlt className="text-[7px] text-muted cursor-grab shrink-0 opacity-0 group-hover/nav:opacity-60" /> }
|
||||
<div className="w-5 h-5 flex items-center justify-center shrink-0">
|
||||
<FaArrowsAlt className="nitro-catalog-classic-navigation-drag text-[7px] text-muted cursor-grab shrink-0 opacity-0 group-hover/nav:opacity-60" /> }
|
||||
<div className="nitro-catalog-classic-navigation-icon">
|
||||
<CatalogIconView icon={ node.iconId } />
|
||||
</div>
|
||||
<span className="flex-1 truncate" title={ adminMode ? `Page ID: ${ node.pageId }` : undefined }>{ node.localization }</span>
|
||||
<span className="nitro-catalog-classic-navigation-label" title={ adminMode ? `Page ID: ${ node.pageId }` : undefined }>{ node.localization }</span>
|
||||
{ adminMode &&
|
||||
<div className="flex items-center gap-1 opacity-0 group-hover/nav:opacity-100 transition-opacity">
|
||||
<div className="nitro-catalog-classic-navigation-admin flex items-center gap-1 opacity-0 group-hover/nav:opacity-100 transition-opacity">
|
||||
<FaPlus
|
||||
className="text-[8px] text-success hover:text-green-800"
|
||||
title={ LocalizeText('catalog.admin.create.subpage') }
|
||||
@@ -125,11 +125,11 @@ export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = pro
|
||||
</div> }
|
||||
{ !adminMode && node.pageId > 0 &&
|
||||
<FaStar
|
||||
className={ `text-[8px] transition-all duration-100 cursor-pointer shrink-0 ${ isFav ? 'text-warning opacity-100' : 'text-muted opacity-0 group-hover/nav:opacity-100 hover:text-warning' }` }
|
||||
className={ `nitro-catalog-classic-navigation-favorite text-[8px] transition-all duration-100 cursor-pointer shrink-0 ${ isFav ? 'text-warning opacity-100' : 'text-muted opacity-0 group-hover/nav:opacity-100 hover:text-warning' }` }
|
||||
onClick={ e => { e.stopPropagation(); toggleFavoritePage(node.pageId); } }
|
||||
/> }
|
||||
{ node.isBranch &&
|
||||
<span className="text-[9px] text-muted shrink-0">
|
||||
<span className="nitro-catalog-classic-navigation-caret text-[9px] text-muted shrink-0">
|
||||
{ node.isOpen ? <FaCaretUp /> : <FaCaretDown /> }
|
||||
</span> }
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ export const CatalogNavigationView: FC<CatalogNavigationViewProps> = props =>
|
||||
const { searchResult = null } = useCatalog();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-px px-0.5 py-0.5">
|
||||
<div className="nitro-catalog-classic-navigation-list">
|
||||
{ searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) =>
|
||||
{
|
||||
return <CatalogNavigationItemView key={ index } node={ n } />;
|
||||
|
||||
@@ -61,10 +61,9 @@ export const CatalogGridOfferView: FC<CatalogGridOfferViewProps> = props =>
|
||||
|
||||
return (
|
||||
<LayoutGridItem
|
||||
className="group/tile relative"
|
||||
className={ `group/tile relative ${ itemActive ? 'is-active' : '' }` }
|
||||
itemActive={ itemActive }
|
||||
itemCount={ ((offer.pricingModel === Offer.PRICING_MODEL_MULTI) ? product.productCount : 1) }
|
||||
itemImage={ iconUrl }
|
||||
itemUniqueNumber={ product.uniqueLimitedItemSeriesSize }
|
||||
itemUniqueSoldout={ (product.uniqueLimitedItemSeriesSize && !product.uniqueLimitedItemsLeft) }
|
||||
title={ `ID: ${ product.productClassId } | Offer: ${ offer.offerId }` }
|
||||
@@ -73,6 +72,8 @@ export const CatalogGridOfferView: FC<CatalogGridOfferViewProps> = props =>
|
||||
onMouseUp={ onMouseEvent }
|
||||
{ ...rest }
|
||||
>
|
||||
{ iconUrl && !(offer.product.productType === ProductTypeEnum.ROBOT) &&
|
||||
<div className="nitro-catalog-classic-grid-offer-icon" style={ { backgroundImage: `url(${ iconUrl })` } } /> }
|
||||
{ (offer.product.productType === ProductTypeEnum.ROBOT) &&
|
||||
<LayoutAvatarImageView direction={ 3 } figure={ offer.product.extraParam } headOnly={ true } /> }
|
||||
<div
|
||||
|
||||
@@ -22,10 +22,10 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
|
||||
const adminMode = catalogAdmin?.adminMode ?? false;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full gap-2">
|
||||
<div className="nitro-catalog-classic-default-layout flex flex-col h-full gap-2">
|
||||
{ /* Admin: quick actions */ }
|
||||
{ adminMode && !catalogAdmin.editingPageData &&
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 nitro-catalog-classic-default-admin">
|
||||
<button
|
||||
className="flex items-center gap-1 text-[10px] text-primary hover:text-dark transition-colors cursor-pointer"
|
||||
onClick={ () => { catalogAdmin.setEditingPageNode(null); catalogAdmin.setEditingRootPage(false); catalogAdmin.setEditingPageData(true); } }
|
||||
@@ -42,9 +42,9 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
|
||||
|
||||
{ /* Product detail card */ }
|
||||
{ currentOffer &&
|
||||
<div className="flex gap-0 bg-white rounded border-2 border-card-grid-item-border overflow-hidden">
|
||||
<div className="nitro-catalog-classic-offer-panel flex gap-0 overflow-hidden">
|
||||
{ /* Preview area */ }
|
||||
<div className="w-[140px] min-w-[140px] bg-card-grid-item relative flex items-center justify-center border-r-2 border-card-grid-item-border">
|
||||
<div className="nitro-catalog-classic-offer-preview relative flex items-center justify-center">
|
||||
{ (currentOffer.product.productType !== ProductTypeEnum.BADGE) &&
|
||||
<>
|
||||
<CatalogViewProductWidgetView />
|
||||
@@ -54,7 +54,7 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
|
||||
<CatalogAddOnBadgeWidgetView className="scale-2" /> }
|
||||
</div>
|
||||
{ /* Product info + purchase */ }
|
||||
<div className="flex flex-col flex-1 min-w-0 p-2.5 gap-2">
|
||||
<div className="nitro-catalog-classic-offer-info flex flex-col flex-1 min-w-0 gap-2">
|
||||
{ /* Title row */ }
|
||||
<div>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
@@ -87,17 +87,17 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
|
||||
|
||||
{ /* Welcome/description card */ }
|
||||
{ !currentOffer &&
|
||||
<div className="flex items-center gap-3 p-2.5 bg-white rounded border-2 border-card-grid-item-border">
|
||||
<div className="nitro-catalog-classic-welcome flex items-center gap-3">
|
||||
{ !!page.localization.getImage(1) &&
|
||||
<img className="w-[70px] h-[70px] object-contain rounded shrink-0" src={ page.localization.getImage(1) } /> }
|
||||
<Text className="text-[11px]! text-muted" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||
</div> }
|
||||
|
||||
{ /* Item grid */ }
|
||||
<div className="flex-1 overflow-auto min-h-0">
|
||||
<div className="nitro-catalog-classic-grid-shell flex-1 overflow-auto min-h-0">
|
||||
{ GetConfigurationValue('catalog.headers') &&
|
||||
<CatalogHeaderView imageUrl={ currentPage.localization.getImage(0) } /> }
|
||||
<CatalogItemGridWidgetView columnCount={ 7 } columnMinHeight={ 50 } columnMinWidth={ 50 } />
|
||||
<CatalogItemGridWidgetView className="nitro-catalog-classic-grid" columnCount={ 7 } columnMinHeight={ 50 } columnMinWidth={ 50 } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user