mirror of
https://github.com/billsonnn/nitro-react.git
synced 2025-02-17 01:12:37 +01:00
Start catalog
This commit is contained in:
parent
0218743efa
commit
1cf02c53d7
6
src/api/catalog/GetCatalogPageComposer.ts
Normal file
6
src/api/catalog/GetCatalogPageComposer.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { CatalogPageComposer } from 'nitro-renderer';
|
||||
|
||||
export function GetCatalogPageComposer(...args: ConstructorParameters<typeof CatalogPageComposer>): CatalogPageComposer
|
||||
{
|
||||
return new CatalogPageComposer(...args);
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
@import './avatar-image/AvatarImage';
|
||||
@import './badge-image/BadgeImage';
|
||||
@import './catalog/CatalogView';
|
||||
@import './catalog-icon/CatalogIconView';
|
||||
@import './hotel-view/HotelView';
|
||||
@import './inventory/InventoryView';
|
||||
@import './friend-list/FriendListView';
|
||||
|
8
src/views/catalog-icon/CatalogIconView.scss
Normal file
8
src/views/catalog-icon/CatalogIconView.scss
Normal file
@ -0,0 +1,8 @@
|
||||
.catalog-icon-image {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
17
src/views/catalog-icon/CatalogIconView.tsx
Normal file
17
src/views/catalog-icon/CatalogIconView.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { FC } from 'react';
|
||||
import { GetConfiguration } from '../../utils/GetConfiguration';
|
||||
import { CatalogIconViewProps } from './CatalogIconView.types';
|
||||
|
||||
export const CatalogIconView: FC<CatalogIconViewProps> = props =>
|
||||
{
|
||||
const { icon = 0 } = props;
|
||||
|
||||
function getIconUrl(): string
|
||||
{
|
||||
return ((GetConfiguration<string>('catalog.asset.icon.url')).replace('%name%', icon.toString()));
|
||||
}
|
||||
|
||||
const url = `url('${ getIconUrl() }')`;
|
||||
|
||||
return <div className="catalog-icon-image" style={ (url && url.length) ? { backgroundImage: url } : {} }></div>;
|
||||
}
|
4
src/views/catalog-icon/CatalogIconView.types.ts
Normal file
4
src/views/catalog-icon/CatalogIconView.types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface CatalogIconViewProps
|
||||
{
|
||||
icon: number;
|
||||
}
|
@ -1,10 +1,40 @@
|
||||
import { FC } from 'react';
|
||||
import { CatalogPageEvent, CatalogPagesEvent } from 'nitro-renderer';
|
||||
import { FC, useCallback } from 'react';
|
||||
import { CreateMessageHook } from '../../hooks/messages/message-event';
|
||||
import { CatalogMessageHandlerProps } from './CatalogMessageHandler.types';
|
||||
import { useCatalogContext } from './context/CatalogContext';
|
||||
import { CatalogActions } from './reducers/CatalogReducer';
|
||||
|
||||
export const CatalogMessageHandler: FC<CatalogMessageHandlerProps> = props =>
|
||||
{
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
);
|
||||
const { dispatchCatalogState = null } = useCatalogContext();
|
||||
|
||||
const onCatalogPagesEvent = useCallback((event: CatalogPagesEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
dispatchCatalogState({
|
||||
type: CatalogActions.SET_CATALOG_ROOT,
|
||||
payload: {
|
||||
root: parser.root
|
||||
}
|
||||
});
|
||||
}, [ dispatchCatalogState ]);
|
||||
|
||||
const onCatalogPageEvent = useCallback((event: CatalogPageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
dispatchCatalogState({
|
||||
type: CatalogActions.SET_CATALOG_PAGE_PARSER,
|
||||
payload: {
|
||||
pageParser: parser
|
||||
}
|
||||
});
|
||||
}, [ dispatchCatalogState ]);
|
||||
|
||||
CreateMessageHook(CatalogPagesEvent, onCatalogPagesEvent);
|
||||
CreateMessageHook(CatalogPageEvent, onCatalogPageEvent);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
.nitro-catalog {
|
||||
width: 450px;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
@import './views/CatalogViews';
|
||||
|
@ -1,14 +1,23 @@
|
||||
import { FC, useCallback, useState } from 'react';
|
||||
import { CatalogModeComposer, ICatalogPageData } from 'nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useReducer, useState } from 'react';
|
||||
import { CatalogEvent } from '../../events';
|
||||
import { DraggableWindow } from '../../hooks/draggable-window/DraggableWindow';
|
||||
import { useUiEvent } from '../../hooks/events/ui/ui-event';
|
||||
import { SendMessageHook } from '../../hooks/messages/message-event';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout';
|
||||
import { LocalizeText } from '../../utils/LocalizeText';
|
||||
import { CatalogMessageHandler } from './CatalogMessageHandler';
|
||||
import { CatalogViewProps } from './CatalogView.types';
|
||||
import { CatalogMode, CatalogViewProps } from './CatalogView.types';
|
||||
import { CatalogContextProvider } from './context/CatalogContext';
|
||||
import { CatalogReducer, initialCatalog } from './reducers/CatalogReducer';
|
||||
import { CatalogNavigationView } from './views/navigation/CatalogNavigationView';
|
||||
import { CatalogPageView } from './views/page/CatalogPageView';
|
||||
|
||||
export const CatalogView: FC<CatalogViewProps> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const [ currentTab, setCurrentTab ] = useState<ICatalogPageData>(null);
|
||||
const [ catalogState, dispatchCatalogState ] = useReducer(CatalogReducer, initialCatalog);
|
||||
const { root = null } = catalogState;
|
||||
|
||||
const onCatalogEvent = useCallback((event: CatalogEvent) =>
|
||||
{
|
||||
@ -26,27 +35,43 @@ export const CatalogView: FC<CatalogViewProps> = props =>
|
||||
useUiEvent(CatalogEvent.SHOW_CATALOG, onCatalogEvent);
|
||||
useUiEvent(CatalogEvent.TOGGLE_CATALOG, onCatalogEvent);
|
||||
|
||||
function hideCatalog(): void
|
||||
useEffect(() =>
|
||||
{
|
||||
setIsVisible(false);
|
||||
}
|
||||
if(!isVisible) return;
|
||||
|
||||
if(!catalogState.root)
|
||||
{
|
||||
SendMessageHook(new CatalogModeComposer(CatalogMode.MODE_NORMAL));
|
||||
}
|
||||
else
|
||||
{
|
||||
setCurrentTab(catalogState.root.children[0]);
|
||||
}
|
||||
}, [ isVisible, catalogState.root ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CatalogContextProvider value={ { catalogState, dispatchCatalogState } }>
|
||||
<CatalogMessageHandler />
|
||||
{ isVisible && <DraggableWindow handle=".drag-handler">
|
||||
<div className="nitro-catalog d-flex flex-column bg-primary border border-black shadow rounded">
|
||||
<div className="drag-handler d-flex justify-content-between align-items-center px-3 pt-3">
|
||||
<div className="h6 m-0">{ LocalizeText('catalog.title') }</div>
|
||||
<button type="button" className="close" onClick={ hideCatalog }>
|
||||
<i className="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="d-flex p-3">
|
||||
content
|
||||
</div>
|
||||
</div>
|
||||
</DraggableWindow> }
|
||||
</>
|
||||
{ isVisible &&
|
||||
<NitroCardView className="nitro-catalog">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('catalog.title') } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardTabsView>
|
||||
{ root && root.children.length && root.children.map((page, index) =>
|
||||
{
|
||||
return <NitroCardTabsItemView key={ index } tabText={ page.localization } isActive={ (currentTab === page) } onClick={ event => setCurrentTab(page) } />
|
||||
}) }
|
||||
</NitroCardTabsView>
|
||||
<NitroCardContentView>
|
||||
<div className="row">
|
||||
<div className="col-3">
|
||||
<CatalogNavigationView page={ currentTab } />
|
||||
</div>
|
||||
<div className="col">
|
||||
<CatalogPageView />
|
||||
</div>
|
||||
</div>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView> }
|
||||
</CatalogContextProvider>
|
||||
);
|
||||
}
|
||||
|
@ -2,3 +2,8 @@ export interface CatalogViewProps
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
export class CatalogMode
|
||||
{
|
||||
public static MODE_NORMAL: string = 'NORMAL';
|
||||
}
|
||||
|
14
src/views/catalog/context/CatalogContext.tsx
Normal file
14
src/views/catalog/context/CatalogContext.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { createContext, FC, useContext } from 'react';
|
||||
import { CatalogContextProps, ICatalogContext } from './CatalogContext.types';
|
||||
|
||||
const CatalogContext = createContext<ICatalogContext>({
|
||||
catalogState: null,
|
||||
dispatchCatalogState: null
|
||||
});
|
||||
|
||||
export const CatalogContextProvider: FC<CatalogContextProps> = props =>
|
||||
{
|
||||
return <CatalogContext.Provider value={ props.value }>{ props.children }</CatalogContext.Provider>
|
||||
}
|
||||
|
||||
export const useCatalogContext = () => useContext(CatalogContext);
|
13
src/views/catalog/context/CatalogContext.types.ts
Normal file
13
src/views/catalog/context/CatalogContext.types.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Dispatch, ProviderProps } from 'react';
|
||||
import { ICatalogAction, ICatalogState } from '../reducers/CatalogReducer';
|
||||
|
||||
export interface ICatalogContext
|
||||
{
|
||||
catalogState: ICatalogState;
|
||||
dispatchCatalogState: Dispatch<ICatalogAction>;
|
||||
}
|
||||
|
||||
export interface CatalogContextProps extends ProviderProps<ICatalogContext>
|
||||
{
|
||||
|
||||
}
|
51
src/views/catalog/reducers/CatalogReducer.tsx
Normal file
51
src/views/catalog/reducers/CatalogReducer.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { ICatalogPageData, ICatalogPageParser } from 'nitro-renderer';
|
||||
import { Reducer } from 'react';
|
||||
|
||||
export interface ICatalogState
|
||||
{
|
||||
needsCatalogUpdate: boolean;
|
||||
root: ICatalogPageData;
|
||||
pageParser: ICatalogPageParser;
|
||||
}
|
||||
|
||||
export interface ICatalogAction
|
||||
{
|
||||
type: string;
|
||||
payload: {
|
||||
flag?: boolean;
|
||||
root?: ICatalogPageData;
|
||||
pageParser?: ICatalogPageParser;
|
||||
}
|
||||
}
|
||||
|
||||
export class CatalogActions
|
||||
{
|
||||
public static SET_NEEDS_UPDATE: string = 'CA_SET_NEEDS_UPDATE';
|
||||
public static SET_CATALOG_ROOT: string = 'CA_SET_CATALOG_ROOT';
|
||||
public static SET_CATALOG_PAGE_PARSER: string = 'CA_SET_CATALOG_PAGE';
|
||||
}
|
||||
|
||||
export const initialCatalog: ICatalogState = {
|
||||
needsCatalogUpdate: true,
|
||||
root: null,
|
||||
pageParser: null
|
||||
}
|
||||
|
||||
export const CatalogReducer: Reducer<ICatalogState, ICatalogAction> = (state, action) =>
|
||||
{
|
||||
switch(action.type)
|
||||
{
|
||||
case CatalogActions.SET_CATALOG_ROOT: {
|
||||
const root = (action.payload.root || state.root || null);
|
||||
|
||||
return { ...state, root };
|
||||
}
|
||||
case CatalogActions.SET_CATALOG_PAGE_PARSER: {
|
||||
const pageParser = (action.payload.pageParser || state.pageParser || null);
|
||||
|
||||
return { ...state, pageParser };
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
6
src/views/catalog/views/CatalogViews.scss
Normal file
6
src/views/catalog/views/CatalogViews.scss
Normal file
@ -0,0 +1,6 @@
|
||||
.nitro-catalog {
|
||||
|
||||
}
|
||||
|
||||
@import './navigation/CatalogNavigationView';
|
||||
@import './page/CatalogPageView';
|
@ -0,0 +1,13 @@
|
||||
.nitro-catalog-navigation {
|
||||
border-color: $grid-border-color !important;
|
||||
background-color: $grid-bg-color !important;
|
||||
|
||||
.navigation-container {
|
||||
height: 275px;
|
||||
max-height: 275px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@import './item/CatalogNavigationItemView';
|
||||
@import './set/CatalogNavigationSetView';
|
16
src/views/catalog/views/navigation/CatalogNavigationView.tsx
Normal file
16
src/views/catalog/views/navigation/CatalogNavigationView.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { FC } from 'react';
|
||||
import { CatalogNavigationViewProps } from './CatalogNavigationView.types';
|
||||
import { CatalogNavigationSetView } from './set/CatalogNavigationSetView';
|
||||
|
||||
export const CatalogNavigationView: FC<CatalogNavigationViewProps> = props =>
|
||||
{
|
||||
const { page = null } = props;
|
||||
|
||||
return (
|
||||
<div className="nitro-catalog-navigation border border-2 rounded overflow-hidden">
|
||||
<div className="navigation-container m-1">
|
||||
<CatalogNavigationSetView page={ page } isFirstSet={ true } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { ICatalogPageData } from 'nitro-renderer';
|
||||
|
||||
export interface CatalogNavigationViewProps
|
||||
{
|
||||
page: ICatalogPageData;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
.catalog-navigation-item-container {
|
||||
|
||||
.catalog-navigation-item {
|
||||
font-size: $font-size-sm;
|
||||
|
||||
i.fas {
|
||||
color: $black;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import { FC, useEffect } from 'react';
|
||||
import { GetCatalogPageComposer } from '../../../../../api/catalog/GetCatalogPageComposer';
|
||||
import { SendMessageHook } from '../../../../../hooks/messages/message-event';
|
||||
import { CatalogIconView } from '../../../../catalog-icon/CatalogIconView';
|
||||
import { CatalogMode } from '../../../CatalogView.types';
|
||||
import { CatalogNavigationSetView } from '../set/CatalogNavigationSetView';
|
||||
import { CatalogNavigationItemViewProps } from './CatalogNavigationItemView.types';
|
||||
|
||||
export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = props =>
|
||||
{
|
||||
const { page = null, isActive = false, setActiveChild = null } = props;
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isActive) return;
|
||||
|
||||
SendMessageHook(GetCatalogPageComposer(page.pageId, -1, CatalogMode.MODE_NORMAL));
|
||||
}, [ isActive, page ]);
|
||||
|
||||
function select(): void
|
||||
{
|
||||
setActiveChild(prevValue =>
|
||||
{
|
||||
if(prevValue === page) return null;
|
||||
|
||||
return page;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="col pe-1 pb-1 catalog-navigation-item-container">
|
||||
<div className="d-flex align-items-center cursor-pointer catalog-navigation-item" onClick={ select }>
|
||||
<CatalogIconView icon={ page.icon } />
|
||||
<div className="flex-grow-1 text-black text-nowrap text-truncate overflow-hidden px-1">{ page.localization }</div>
|
||||
{ (page.children.length > 0) && <i className={ 'fas fa-caret-' + (isActive ? 'up' : 'down') } /> }
|
||||
</div>
|
||||
{ isActive && page.children && (page.children.length > 0) &&
|
||||
<div className="d-flex flex-column mt-1">
|
||||
<CatalogNavigationSetView page={ page } />
|
||||
</div> }
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import { ICatalogPageData } from 'nitro-renderer';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
|
||||
export interface CatalogNavigationItemViewProps
|
||||
{
|
||||
page: ICatalogPageData;
|
||||
isActive: boolean;
|
||||
setActiveChild: Dispatch<SetStateAction<ICatalogPageData>>;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
.catalog-navigation-set-container {
|
||||
|
||||
.catalog-navigation-set-container {
|
||||
padding-left: 5px;
|
||||
|
||||
.catalog-navigation-item-container {
|
||||
padding-right: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
padding-bottom: 0px !important;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { ICatalogPageData } from 'nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { CatalogNavigationItemView } from '../item/CatalogNavigationItemView';
|
||||
import { CatalogNavigationSetViewProps } from './CatalogNavigationSetView.types';
|
||||
|
||||
export const CatalogNavigationSetView: FC<CatalogNavigationSetViewProps> = props =>
|
||||
{
|
||||
const { page = null, isFirstSet = false } = props;
|
||||
const [ activeChild, setActiveChild ] = useState<ICatalogPageData>(null);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isFirstSet) return;
|
||||
|
||||
if(page && page.children.length) setActiveChild(page.children[0]);
|
||||
}, [ page, isFirstSet ]);
|
||||
|
||||
return (
|
||||
<div className="row row-cols-1 g-0 catalog-navigation-set-container w-100">
|
||||
{ page && (page.children.length > 0) && page.children.map((page, index) =>
|
||||
{
|
||||
if(!page.visible) return null;
|
||||
|
||||
return <CatalogNavigationItemView key={ index } page={ page } isActive={ (activeChild === page) } setActiveChild={ setActiveChild } />
|
||||
}) }
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { ICatalogPageData } from 'nitro-renderer';
|
||||
|
||||
export interface CatalogNavigationSetViewProps
|
||||
{
|
||||
page: ICatalogPageData;
|
||||
isFirstSet?: boolean;
|
||||
}
|
1
src/views/catalog/views/page/CatalogPageView.scss
Normal file
1
src/views/catalog/views/page/CatalogPageView.scss
Normal file
@ -0,0 +1 @@
|
||||
@import './layout/CatalogLayout';
|
21
src/views/catalog/views/page/CatalogPageView.tsx
Normal file
21
src/views/catalog/views/page/CatalogPageView.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { FC } from 'react';
|
||||
import { useCatalogContext } from '../../context/CatalogContext';
|
||||
import { CatalogPageViewProps } from './CatalogPageView.types';
|
||||
import { GetCatalogLayout } from './layout/GetCatalogLayout';
|
||||
|
||||
export const CatalogPageView: FC<CatalogPageViewProps> = props =>
|
||||
{
|
||||
const { catalogState = null } = useCatalogContext();
|
||||
const { pageParser = null } = catalogState;
|
||||
|
||||
return (
|
||||
<div className="row h-100">
|
||||
<div className="col-7">
|
||||
{ pageParser && GetCatalogLayout(pageParser) }
|
||||
</div>
|
||||
<div className="col">
|
||||
preview area
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
4
src/views/catalog/views/page/CatalogPageView.types.ts
Normal file
4
src/views/catalog/views/page/CatalogPageView.types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface CatalogPageViewProps
|
||||
{
|
||||
|
||||
}
|
1
src/views/catalog/views/page/layout/CatalogLayout.scss
Normal file
1
src/views/catalog/views/page/layout/CatalogLayout.scss
Normal file
@ -0,0 +1 @@
|
||||
@import './default/CatalogLayoutDefaultView';
|
@ -0,0 +1,6 @@
|
||||
import { ICatalogPageParser } from 'nitro-renderer';
|
||||
|
||||
export interface CatalogLayoutProps
|
||||
{
|
||||
pageParser: ICatalogPageParser;
|
||||
}
|
42
src/views/catalog/views/page/layout/GetCatalogLayout.tsx
Normal file
42
src/views/catalog/views/page/layout/GetCatalogLayout.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { ICatalogPageParser } from 'nitro-renderer';
|
||||
import { CatalogLayoutDefaultView } from './default/CatalogLayoutDefaultView';
|
||||
|
||||
export function GetCatalogLayout(pageParser: ICatalogPageParser): JSX.Element
|
||||
{
|
||||
switch(pageParser.catalogType)
|
||||
{
|
||||
case 'frontpage_featured':
|
||||
return null;
|
||||
case 'frontpage4':
|
||||
return null;
|
||||
case 'bots':
|
||||
return null;
|
||||
case 'pets':
|
||||
return null;
|
||||
case 'pets2':
|
||||
return null;
|
||||
case 'pets3':
|
||||
return null;
|
||||
case 'spaces_new':
|
||||
return null;
|
||||
case 'vip_buy':
|
||||
return null;
|
||||
case 'guild_frontpage':
|
||||
return null;
|
||||
case 'guild_custom_furni':
|
||||
return null;
|
||||
case 'trophies':
|
||||
return null;
|
||||
case 'search_results':
|
||||
return null;
|
||||
case 'club_gifts':
|
||||
return null;
|
||||
case 'marketplace_own_items':
|
||||
return null;
|
||||
case 'marketplace':
|
||||
return null;
|
||||
case 'default_3x3':
|
||||
default:
|
||||
return <CatalogLayoutDefaultView pageParser={ pageParser } />
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { FC } from 'react';
|
||||
import { CatalogLayoutDefaultViewProps } from './CatalogLayoutDefaultView.types';
|
||||
|
||||
export const CatalogLayoutDefaultView: FC<CatalogLayoutDefaultViewProps> = props =>
|
||||
{
|
||||
const { pageParser = null } = props;
|
||||
|
||||
return (
|
||||
<div className="d-flex">
|
||||
{ pageParser && pageParser.localization.texts[0] }
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { CatalogLayoutProps } from '../CatalogLayout.types';
|
||||
|
||||
export interface CatalogLayoutDefaultViewProps extends CatalogLayoutProps
|
||||
{
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user