This commit is contained in:
Bill 2021-06-11 22:53:56 -04:00
parent 6609935045
commit 40f9c86e2b
85 changed files with 934 additions and 213 deletions

View File

@ -53,7 +53,7 @@
"avatareditor.show.clubitems.first": true,
"chat.history.max.items": 100,
"animation.fps": 24,
"limits.fps": true,
"limits.fps": false,
"system.dispatcher.log": false,
"system.currency.types": [
-1,

View File

@ -4,11 +4,14 @@ import { GetRoomEngine } from './GetRoomEngine';
let didMouseMove = false;
let lastClick = 0;
let clickCount = 0;
let touchTimer: ReturnType<typeof setTimeout> = null;
export function DispatchTouchEvent(roomId: number, canvasId: number, event: TouchEvent)
export function DispatchTouchEvent(roomId: number, canvasId: number, event: TouchEvent, longTouch: boolean = false, altKey: boolean = false, ctrlKey: boolean = false, shiftKey: boolean = false)
{
let eventType = event.type;
if(longTouch) eventType = TouchEventType.TOUCH_LONG;
if(eventType === TouchEventType.TOUCH_END && !didMouseMove)
{
eventType = MouseEventType.MOUSE_CLICK;
@ -17,7 +20,7 @@ export function DispatchTouchEvent(roomId: number, canvasId: number, event: Touc
{
clickCount = 1;
if(lastClick >= Date.now() - 300) clickCount++;
if(lastClick >= (Date.now() - 300)) clickCount++;
}
lastClick = Date.now();
@ -31,24 +34,7 @@ export function DispatchTouchEvent(roomId: number, canvasId: number, event: Touc
}
}
switch(eventType)
{
case MouseEventType.MOUSE_CLICK:
break;
case MouseEventType.DOUBLE_CLICK:
break;
case TouchEventType.TOUCH_START:
eventType = MouseEventType.MOUSE_DOWN;
didMouseMove = false;
break;
case TouchEventType.TOUCH_MOVE:
eventType = MouseEventType.MOUSE_MOVE;
didMouseMove = true;
break;
default: return;
}
if(touchTimer) clearTimeout(touchTimer);
let x = 0;
let y = 0;
@ -66,5 +52,38 @@ export function DispatchTouchEvent(roomId: number, canvasId: number, event: Touc
}
GetRoomEngine().setActiveRoomId(roomId);
GetRoomEngine().dispatchMouseEvent(canvasId, x, y, eventType, event.altKey, (event.ctrlKey || event.metaKey), event.shiftKey, false);
switch(eventType)
{
case MouseEventType.MOUSE_CLICK:
break;
case MouseEventType.DOUBLE_CLICK:
break;
case TouchEventType.TOUCH_START:
touchTimer = setTimeout(() =>
{
if(didMouseMove) return;
DispatchTouchEvent(roomId, canvasId, event, true);
}, 300);
eventType = MouseEventType.MOUSE_DOWN;
didMouseMove = false;
break;
case TouchEventType.TOUCH_MOVE:
eventType = MouseEventType.MOUSE_MOVE;
didMouseMove = true;
break;
case TouchEventType.TOUCH_END:
eventType = MouseEventType.MOUSE_UP;
break;
case TouchEventType.TOUCH_LONG:
eventType = MouseEventType.MOUSE_DOWN_LONG;
break;
default: return;
}
GetRoomEngine().dispatchMouseEvent(canvasId, x, y, eventType, altKey, ctrlKey, shiftKey, false);
}

View File

@ -32,10 +32,7 @@ ul {
}
.cursor-pointer {
&:hover {
cursor: pointer;
}
cursor: pointer;
}
.pointer-events-none {

View File

@ -10,4 +10,5 @@ export class CatalogEvent extends NitroEvent
public static SOLD_OUT: string = 'CE_SOLD_OUT';
public static APPROVE_NAME_RESULT: string = 'CE_APPROVE_NAME_RESULT';
public static PURCHASE_APPROVED: string = 'CE_PURCHASE_APPROVED';
public static CATALOG_RESET: string = 'CE_RESET';
}

View File

@ -1,4 +1,5 @@
.draggable-window {
visibility: hidden;
.drag-handler {
cursor: move;

View File

@ -62,6 +62,8 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
element.style.top = `${ top }px`;
}
element.style.visibility = 'visible';
return () =>
{
const index = currentWindows.indexOf(element);

View File

@ -1,24 +1,17 @@
import { IEventDispatcher, NitroEvent } from 'nitro-renderer';
import { useEffect, useRef } from 'react';
import { useEffect } from 'react';
export function CreateEventDispatcherHook(type: string, eventDispatcher: IEventDispatcher, handler: (event: NitroEvent) => void): void
{
const handlerRef = useRef<(event: NitroEvent) => void>();
useEffect(() =>
{
handlerRef.current = handler;
}, [ handler ]);
useEffect(() =>
{
eventDispatcher.addEventListener(type, handlerRef.current);
eventDispatcher.addEventListener(type, handler);
return () =>
{
eventDispatcher.removeEventListener(type, handlerRef.current);
eventDispatcher.removeEventListener(type, handler);
}
}, [ type, eventDispatcher ]);
}, [ type, eventDispatcher, handler ]);
}
export function DispatchEventHook(eventDispatcher: IEventDispatcher, event: NitroEvent): void

View File

@ -19,7 +19,7 @@ $nitro-card-top-height: $nitro-card-header-height + $nitro-card-tabs-height;
@import './tabs/NitroCardTabsView';
}
@include media-breakpoint-down(md) {
@include media-breakpoint-down(lg) {
.draggable-window {
top: 0 !important;
@ -31,6 +31,7 @@ $nitro-card-top-height: $nitro-card-header-height + $nitro-card-tabs-height;
.nitro-card {
width: 100%;
height: 100%;
&.rounded {
border-radius: 0 !important;

View File

@ -3,7 +3,7 @@
padding-bottom: $container-padding-x;
}
@include media-breakpoint-down(md) {
@include media-breakpoint-down(lg) {
.content-area {
height: 100% !important;

View File

@ -4,11 +4,11 @@ import { NitroCardTabsItemViewProps } from './NitroCardTabsItemView.types';
export const NitroCardTabsItemView: FC<NitroCardTabsItemViewProps> = props =>
{
const { tabText = '', isActive = false, onClick = null } = props;
const { children = null, isActive = false, onClick = null } = props;
return (
<li className="nav-item me-1 cursor-pointer" onClick={ onClick }>
<span className={ 'nav-link ' + classNames({ 'active': isActive }) }>{ tabText }</span>
<span className={ 'nav-link ' + classNames({ 'active': isActive }) }>{ children }</span>
</li>
);
}

View File

@ -2,7 +2,6 @@ import { MouseEventHandler } from 'react';
export interface NitroCardTabsItemViewProps
{
tabText?: string;
isActive?: boolean;
onClick?: MouseEventHandler<HTMLLIElement>;
}

View File

@ -0,0 +1,32 @@
import { FC, useCallback, useEffect, useRef, useState } from 'react';
import { ScrollableAreaViewProps } from './ScrollableAreaView.types';
export const ScrollableAreaView: FC<ScrollableAreaViewProps> = props =>
{
const { className = null, children = null } = props;
const [ height, setHeight ] = useState(0);
const elementRef = useRef<HTMLDivElement>();
const resize = useCallback(() =>
{
setHeight(elementRef.current.parentElement.clientHeight);
}, [ elementRef ]);
useEffect(() =>
{
resize();
window.addEventListener('resize', resize);
return () =>
{
window.removeEventListener('resize', resize);
}
}, [ resize ]);
return (
<div ref={ elementRef } className={ className } style={ { 'overflowY': 'auto', height } }>
{ children }
</div>
);
}

View File

@ -0,0 +1,4 @@
export interface ScrollableAreaViewProps
{
className?: string;
}

View File

@ -1 +0,0 @@
@import './currency-icon/CurrencyIcon.scss';

View File

@ -3,6 +3,7 @@
@import './badge-image/BadgeImage';
@import './catalog/CatalogView';
@import './catalog-icon/CatalogIconView';
@import './currency-icon/CurrencyIcon';
@import './friend-list/FriendListView';
@import './furni-image/FurniImageView';
@import './hotel-view/HotelView';

View File

@ -1,4 +1,4 @@
import { CatalogApproveNameResultEvent, CatalogClubEvent, CatalogPageEvent, CatalogPagesEvent, CatalogPurchaseEvent, CatalogPurchaseFailedEvent, CatalogPurchaseUnavailableEvent, CatalogSearchEvent, CatalogSoldOutEvent, SellablePetPalettesEvent } from 'nitro-renderer';
import { CatalogApproveNameResultEvent, CatalogClubEvent, CatalogPageEvent, CatalogPagesEvent, CatalogPurchaseEvent, CatalogPurchaseFailedEvent, CatalogPurchaseUnavailableEvent, CatalogSearchEvent, CatalogSoldOutEvent, CatalogUpdatedEvent, SellablePetPalettesEvent, UserSubscriptionEvent } from 'nitro-renderer';
import { FC, useCallback } from 'react';
import { CatalogNameResultEvent, CatalogPurchaseFailureEvent } from '../../events';
import { CatalogPurchasedEvent } from '../../events/catalog/CatalogPurchasedEvent';
@ -9,6 +9,7 @@ import { CatalogMessageHandlerProps } from './CatalogMessageHandler.types';
import { useCatalogContext } from './context/CatalogContext';
import { CatalogActions } from './reducers/CatalogReducer';
import { CatalogPetPalette } from './utils/CatalogPetPalette';
import { SubscriptionInfo } from './utils/SubscriptionInfo';
export const CatalogMessageHandler: FC<CatalogMessageHandlerProps> = props =>
{
@ -106,6 +107,32 @@ export const CatalogMessageHandler: FC<CatalogMessageHandlerProps> = props =>
});
}, [ dispatchCatalogState ]);
const onUserSubscriptionEvent = useCallback((event: UserSubscriptionEvent) =>
{
const parser = event.getParser();
dispatchCatalogState({
type: CatalogActions.SET_SUBSCRIPTION_INFO,
payload: {
subscriptionInfo: new SubscriptionInfo(
Math.max(0, parser.days),
Math.max(0, parser.months),
parser.isVip,
parser.pastClubDays,
parser.pastVIPDays
)
}
});
}, [ dispatchCatalogState ]);
const onCatalogUpdatedEvent = useCallback((event: CatalogUpdatedEvent) =>
{
dispatchCatalogState({
type: CatalogActions.RESET_STATE,
payload: {}
});
}, [ dispatchCatalogState ]);
CreateMessageHook(CatalogPagesEvent, onCatalogPagesEvent);
CreateMessageHook(CatalogPageEvent, onCatalogPageEvent);
CreateMessageHook(CatalogPurchaseEvent, onCatalogPurchaseEvent);
@ -116,6 +143,8 @@ export const CatalogMessageHandler: FC<CatalogMessageHandlerProps> = props =>
CreateMessageHook(SellablePetPalettesEvent, onSellablePetPalettesEvent);
CreateMessageHook(CatalogApproveNameResultEvent, onCatalogApproveNameResultEvent);
CreateMessageHook(CatalogClubEvent, onCatalogClubEvent);
CreateMessageHook(UserSubscriptionEvent, onUserSubscriptionEvent);
CreateMessageHook(CatalogUpdatedEvent, onCatalogUpdatedEvent);
return null;
}

View File

@ -1,6 +1,7 @@
import { CatalogModeComposer, ICatalogPageData, RoomPreviewer } from 'nitro-renderer';
import { FC, useCallback, useEffect, useReducer, useState } from 'react';
import { GetRoomEngine } from '../../api';
import { GetCatalogPageComposer } from '../../api/catalog/GetCatalogPageComposer';
import { CatalogEvent } from '../../events';
import { useUiEvent } from '../../hooks/events/ui/ui-event';
import { SendMessageHook } from '../../hooks/messages/message-event';
@ -12,14 +13,13 @@ import { CatalogContextProvider } from './context/CatalogContext';
import { CatalogActions, CatalogReducer, initialCatalog } from './reducers/CatalogReducer';
import { CatalogNavigationView } from './views/navigation/CatalogNavigationView';
import { CatalogPageView } from './views/page/CatalogPageView';
import { CatalogSearchView } from './views/search/CatalogSearchView';
export const CatalogView: FC<CatalogViewProps> = props =>
{
const [ isVisible, setIsVisible ] = useState(false);
const [ roomPreviewer, setRoomPreviewer ] = useState<RoomPreviewer>(null);
const [ catalogState, dispatchCatalogState ] = useReducer(CatalogReducer, initialCatalog);
const { root = null, currentTab = null, activeOffer = null, searchResult = null } = catalogState;
const { root = null, currentTab = null, pageParser = null, activeOffer = null, searchResult = null } = catalogState;
const onCatalogEvent = useCallback((event: CatalogEvent) =>
{
@ -40,6 +40,7 @@ export const CatalogView: FC<CatalogViewProps> = props =>
useUiEvent(CatalogEvent.SHOW_CATALOG, onCatalogEvent);
useUiEvent(CatalogEvent.HIDE_CATALOG, onCatalogEvent);
useUiEvent(CatalogEvent.TOGGLE_CATALOG, onCatalogEvent);
useUiEvent(CatalogEvent.CATALOG_RESET, onCatalogEvent);
useEffect(() =>
{
@ -51,6 +52,13 @@ export const CatalogView: FC<CatalogViewProps> = props =>
}
}, [ isVisible, catalogState.root ]);
useEffect(() =>
{
if(!currentTab) return;
SendMessageHook(GetCatalogPageComposer(currentTab.pageId, -1, CatalogMode.MODE_NORMAL));
}, [ currentTab ]);
useEffect(() =>
{
setRoomPreviewer(new RoomPreviewer(GetRoomEngine(), ++RoomPreviewer.PREVIEW_COUNTER));
@ -66,7 +74,7 @@ export const CatalogView: FC<CatalogViewProps> = props =>
}
}, []);
function setCurrentTab(page: ICatalogPageData): void
const setCurrentTab = useCallback((page: ICatalogPageData) =>
{
dispatchCatalogState({
type: CatalogActions.SET_CATALOG_CURRENT_TAB,
@ -74,28 +82,32 @@ export const CatalogView: FC<CatalogViewProps> = props =>
currentTab: page
}
});
}
}, [ dispatchCatalogState ]);
const currentNavigationPage = ((searchResult && searchResult.page) || currentTab);
return (
<CatalogContextProvider value={ { catalogState, dispatchCatalogState } }>
<CatalogMessageHandler />
<CatalogMessageHandler />
{ 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) } />
return (
<NitroCardTabsItemView key={ index } isActive={ (currentTab === page) } onClick={ event => setCurrentTab(page) }>
{ page.localization }
</NitroCardTabsItemView>
);
}) }
</NitroCardTabsView>
<NitroCardContentView>
<div className="row h-100">
<div className="col-3">
<CatalogSearchView />
<CatalogNavigationView page={ currentNavigationPage } />
</div>
{ pageParser && !pageParser.frontPageItems.length &&
<div className="col-3">
<CatalogNavigationView page={ currentNavigationPage } />
</div> }
<div className="col">
<CatalogPageView roomPreviewer={ roomPreviewer } />
</div>

View File

@ -2,17 +2,20 @@ import { CatalogClubOfferData, CatalogPageOfferData, ICatalogPageData, ICatalogP
import { Reducer } from 'react';
import { CatalogPetPalette } from '../utils/CatalogPetPalette';
import { ICatalogOffers, ICatalogSearchResult, SetOffersToNodes } from '../utils/CatalogUtilities';
import { SubscriptionInfo } from '../utils/SubscriptionInfo';
export interface ICatalogState
{
root: ICatalogPageData;
offerRoot: ICatalogOffers;
currentTab: ICatalogPageData;
currentPage: ICatalogPageData;
pageParser: ICatalogPageParser;
activeOffer: CatalogPageOfferData;
searchResult: ICatalogSearchResult;
petPalettes: CatalogPetPalette[];
clubOffers: CatalogClubOfferData[];
subscriptionInfo: SubscriptionInfo;
}
export interface ICatalogAction
@ -22,11 +25,13 @@ export interface ICatalogAction
root?: ICatalogPageData;
offerRoot?: ICatalogOffers;
currentTab?: ICatalogPageData;
currentPage?: ICatalogPageData;
pageParser?: ICatalogPageParser;
activeOffer?: CatalogPageOfferData;
searchResult?: ICatalogSearchResult;
petPalette?: CatalogPetPalette;
clubOffers?: CatalogClubOfferData[];
subscriptionInfo?: SubscriptionInfo;
}
}
@ -34,22 +39,27 @@ export class CatalogActions
{
public static SET_CATALOG_ROOT: string = 'CA_SET_CATALOG_ROOT';
public static SET_CATALOG_CURRENT_TAB: string = 'CA_SET_CATALOG_CURRENT_TAB';
public static SET_CATALOG_CURRENT_PAGE: string = 'CA_SET_CATALOG_CURRENT_PAGE';
public static SET_CATALOG_PAGE_PARSER: string = 'CA_SET_CATALOG_PAGE';
public static SET_CATALOG_ACTIVE_OFFER: string = 'CA_SET_ACTIVE_OFFER';
public static SET_SEARCH_RESULT: string = 'CA_SET_SEARCH_RESULT';
public static SET_PET_PALETTE: string = 'CA_SET_PET_PALETTE';
public static SET_CLUB_OFFERS: string = 'CA_SET_CLUB_OFFERS';
public static SET_SUBSCRIPTION_INFO: string = 'CA_SET_SUBSCRIPTION_INFO';
public static RESET_STATE = 'CA_RESET_STATE';
}
export const initialCatalog: ICatalogState = {
root: null,
offerRoot: null,
currentTab: null,
currentPage: null,
pageParser: null,
activeOffer: null,
searchResult: null,
petPalettes: [],
clubOffers: null
clubOffers: null,
subscriptionInfo: new SubscriptionInfo()
}
export const CatalogReducer: Reducer<ICatalogState, ICatalogAction> = (state, action) =>
@ -72,8 +82,13 @@ export const CatalogReducer: Reducer<ICatalogState, ICatalogAction> = (state, ac
return { ...state, currentTab, searchResult };
}
case CatalogActions.SET_CATALOG_CURRENT_PAGE: {
const currentPage = (action.payload.currentPage || state.currentPage || null);
return { ...state, currentPage };
}
case CatalogActions.SET_CATALOG_PAGE_PARSER: {
let pageParser = Object.create(action.payload.pageParser);
let pageParser = (Object.create(action.payload.pageParser) as ICatalogPageParser);
let activeOffer = null;
if(pageParser.layoutCode === 'single_bundle')
@ -126,6 +141,14 @@ export const CatalogReducer: Reducer<ICatalogState, ICatalogAction> = (state, ac
return { ...state, clubOffers };
}
case CatalogActions.SET_SUBSCRIPTION_INFO: {
const subscriptionInfo = (action.payload.subscriptionInfo || null);
return { ...state, subscriptionInfo };
}
case CatalogActions.RESET_STATE: {
return { ...initialCatalog };
}
default:
return state;
}

View File

@ -0,0 +1,14 @@
export interface IPurse
{
_Str_14389: boolean;
_Str_4458: number;
credits: number;
clubDays: number;
clubPeriods: number;
_Str_13571: boolean;
_Str_3738: boolean;
_Str_6288: number;
_Str_4605: number;
_Str_6312: number;
_Str_5590(_arg_1: number): number;
}

View File

@ -0,0 +1,144 @@
import { Nitro } from 'nitro-renderer';
import { IPurse } from './IPurse';
export class Purse implements IPurse
{
private _credits: number = 0;
private _activityPoints: Map<number, number>;
private _clubDays: number = 0;
private _clubPeriods: number = 0;
private _isVIP: boolean = false;
private _pastClubDays: number = 0;
private _pastVipDays: number = 0;
private _isExpiring: boolean = false;
private _minutesUntilExpiration: number = 0;
private _minutesSinceLastModified: number;
private _lastUpdated: number;
public get credits(): number
{
return this._credits;
}
public set credits(k: number)
{
this._lastUpdated = Nitro.instance.time;
this._credits = k;
}
public get clubDays(): number
{
return this._clubDays;
}
public set clubDays(k: number)
{
this._lastUpdated = Nitro.instance.time;
this._clubDays = k;
}
public get clubPeriods(): number
{
return this._clubPeriods;
}
public set clubPeriods(k: number)
{
this._lastUpdated = Nitro.instance.time;
this._clubPeriods = k;
}
public get _Str_13571(): boolean
{
return (this._clubDays > 0) || (this._clubPeriods > 0);
}
public get _Str_3738(): boolean
{
return this._isVIP;
}
public get _Str_14389(): boolean
{
return this._isExpiring;
}
public set _Str_14389(k: boolean)
{
this._isExpiring = k;
}
public set _Str_3738(k: boolean)
{
this._isVIP = k;
}
public get _Str_6288(): number
{
return this._pastClubDays;
}
public set _Str_6288(k: number)
{
this._lastUpdated = Nitro.instance.time;
this._pastClubDays = k;
}
public get _Str_4605(): number
{
return this._pastVipDays;
}
public set _Str_4605(k: number)
{
this._lastUpdated = Nitro.instance.time;
this._pastVipDays = k;
}
public get _Str_18527(): Map<number, number>
{
return this._activityPoints;
}
public set _Str_18527(k: Map<number, number>)
{
this._lastUpdated = Nitro.instance.time;
this._activityPoints = k;
}
public _Str_5590(k: number): number
{
return this._activityPoints[k];
}
public set _Str_4458(k: number)
{
this._lastUpdated = Nitro.instance.time;
this._minutesUntilExpiration = k;
}
public get _Str_4458(): number
{
const k = ((Nitro.instance.time - this._lastUpdated) / (1000 * 60));
const _local_2 = (this._minutesUntilExpiration - k);
return (_local_2 > 0) ? _local_2 : 0;
}
public set _Str_6312(k: number)
{
this._lastUpdated = Nitro.instance.time;
this._minutesSinceLastModified = k;
}
public get _Str_6312(): number
{
return this._minutesSinceLastModified;
}
public get _Str_26225(): number
{
return this._lastUpdated;
}
}

View File

@ -0,0 +1,17 @@
export class SubscriptionInfo
{
private _lastUpdated: number;
constructor(
public clubDays: number = 0,
public clubPeriods: number = 0,
public isVip: boolean = false,
public pastDays: number = 0,
public pastVipDays: number = 0) {}
public get lastUpdated(): number
{
return this._lastUpdated;
}
}

View File

@ -1,5 +1,6 @@
import { ICatalogPageData } from 'nitro-renderer';
import { FC, useEffect } from 'react';
import { CatalogSearchView } from '../search/CatalogSearchView';
import { CatalogNavigationViewProps } from './CatalogNavigationView.types';
import { CatalogNavigationSetView } from './set/CatalogNavigationSetView';
@ -17,10 +18,13 @@ export const CatalogNavigationView: FC<CatalogNavigationViewProps> = props =>
}, [ page ]);
return (
<div className="border border-2 rounded overflow-hidden nitro-catalog-navigation">
<div className="navigation-container m-1">
<CatalogNavigationSetView page={ page } isFirstSet={ true } />
<>
<CatalogSearchView />
<div className="border border-2 rounded overflow-hidden nitro-catalog-navigation">
<div className="navigation-container m-1">
<CatalogNavigationSetView page={ page } isFirstSet={ true } />
</div>
</div>
</div>
</>
);
}

View File

@ -13,7 +13,12 @@ export const CatalogNavigationSetView: FC<CatalogNavigationSetViewProps> = props
{
if(!isFirstSet || !page || (page.pageId === -1)) return;
if(page && page.children.length) setActiveChild(page.children[0]);
if(page && page.children.length)
{
const child = page.children[0];
setActiveChild(child);
}
}, [ page, isFirstSet ]);
useEffect(() =>

View File

@ -1,3 +1,4 @@
@import './header/CatalogPageHeaderView';
@import './layout/CatalogLayout';
@import './offer/CatalogPageOfferView';
@import './offers/CatalogPageOffersView';

View File

@ -0,0 +1,3 @@
.nitro-catalog-page-header {
padding-top: $container-padding-x;
}

View File

@ -0,0 +1,28 @@
import { FC } from 'react';
import { CatalogIconView } from '../../../../catalog-icon/CatalogIconView';
import { useCatalogContext } from '../../../context/CatalogContext';
import { CatalogPageHeaderViewProps } from './CatalogPageHeaderView.types';
export const CatalogPageHeaderView: FC<CatalogPageHeaderViewProps> = props =>
{
const { catalogState = null } = useCatalogContext();
const { currentPage = null, pageParser = null } = catalogState;
return (
<div className="container-fluid nitro-catalog-page-header bg-light">
<div className="row h-100">
<div className="col-2">
<CatalogIconView icon={ currentPage.icon } />
</div>
<div className="d-flex col-10 flex-column">
<div className="d-block">
{ currentPage.localization }
</div>
{ pageParser && <div className="d-block">
{ pageParser.localization.texts[0] }
</div> }
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,6 @@
import { ICatalogPageParser } from 'nitro-renderer';
export interface CatalogPageHeaderViewProps
{
pageParser: ICatalogPageParser;
}

View File

@ -1,5 +1,7 @@
@import './default/CatalogLayoutDefaultView';
@import './frontpage4/CatalogLayoutFrontpage4View';
@import './pets/CatalogLayoutPetView';
@import './pets3/CatalogLayoutPets3View';
@import './single-bundle/CatalogLayoutSingleBundleView';
@import './spaces-new/CatalogLayoutSpacesView';
@import './trophies/CatalogLayoutTrophiesView';

View File

@ -1,6 +1,8 @@
import { ICatalogPageParser, RoomPreviewer } from 'nitro-renderer';
import { CatalogLayoutDefaultView } from './default/CatalogLayoutDefaultView';
import { CatalogLayoutFrontpage4View } from './frontpage4/CatalogLayoutFrontpage4View';
import { CatalogLayoutPetView } from './pets/CatalogLayoutPetView';
import { CatalogLayoutPets3View } from './pets3/CatalogLayoutPets3View';
import { CatalogLayoutSingleBundleView } from './single-bundle/CatalogLayoutSingleBundleView';
import { CatalogLayoutSpacesView } from './spaces-new/CatalogLayoutSpacesView';
import { CatalogLayoutTrophiesView } from './trophies/CatalogLayoutTrophiesView';
@ -13,13 +15,13 @@ export function GetCatalogLayout(pageParser: ICatalogPageParser, roomPreviewer:
case 'frontpage_featured':
return null;
case 'frontpage4':
return null;
return <CatalogLayoutFrontpage4View roomPreviewer={ roomPreviewer } pageParser={ pageParser } />;
case 'pets':
return <CatalogLayoutPetView roomPreviewer={ roomPreviewer } pageParser={ pageParser } />;
case 'pets2':
return null;
case 'pets3':
return null;
return <CatalogLayoutPets3View roomPreviewer={ roomPreviewer } pageParser={ pageParser } />;
case 'vip_buy':
return <CatalogLayoutVipBuyView roomPreviewer={ roomPreviewer } pageParser={ pageParser } />;
case 'guild_frontpage':

View File

@ -0,0 +1,22 @@
.nitro-catalog-layout-frontpage4 {
.front-page-item {
position: relative;
border-radius: $border-radius;
background-position: center;
background-repeat: no-repeat;
cursor: pointer;
.front-page-item-caption {
position: absolute;
background: rgba(0, 0, 0, .5);
color: #fff;
font-size: 16px;
border-radius: 5px;
margin: 10px;
padding: 5px 15px;
bottom: 0;
text-shadow: 2px 2px rgba(0, 0, 0, .2);
}
}
}

View File

@ -0,0 +1,59 @@
import { FC, useCallback, useMemo } from 'react';
import { GetConfiguration } from '../../../../../../utils/GetConfiguration';
import { CatalogLayoutFrontpage4ViewProps } from './CatalogLayoutFrontpage4View.types';
export const CatalogLayoutFrontpage4View: FC<CatalogLayoutFrontpage4ViewProps> = props =>
{
const { pageParser = null } = props;
const getFrontPageItem = useCallback((position: number) =>
{
for(const item of pageParser.frontPageItems)
{
if(item.position !== position) continue;
return item;
}
}, [ pageParser ]);
const getFrontPageItemImage = useCallback((position: number) =>
{
const item = getFrontPageItem(position);
if(!item) return null;
return item.itemPromoImage;
}, [ getFrontPageItem ]);
const imageLibraryUrl = useMemo(() =>
{
return GetConfiguration<string>('image.library.url');
}, []);
if(!pageParser) return null;
return (
<div className="row h-100 nitro-catalog-layout-frontpage4">
<div className="col-4">
{ pageParser.frontPageItems[0] &&
<div className="front-page-item h-100" style={ { backgroundImage: `url('${ imageLibraryUrl }${ pageParser.frontPageItems[0].itemPromoImage }')`}}>
<div className="front-page-item-caption">{ pageParser.frontPageItems[0].itemName }</div>
</div> }
</div>
<div className="d-flex col-8 flex-column">
{ pageParser.frontPageItems[1] &&
<div className="front-page-item h-100 mb-2" style={ { backgroundImage: `url('${ imageLibraryUrl }${ pageParser.frontPageItems[1].itemPromoImage }')`}}>
<div className="front-page-item-caption">{ pageParser.frontPageItems[1].itemName }</div>
</div> }
{ pageParser.frontPageItems[2] &&
<div className="front-page-item h-100 mb-2" style={ { backgroundImage: `url('${ imageLibraryUrl }${ pageParser.frontPageItems[2].itemPromoImage }')`}}>
<div className="front-page-item-caption">{ pageParser.frontPageItems[2].itemName }</div>
</div> }
{ pageParser.frontPageItems[3] &&
<div className="front-page-item h-100" style={ { backgroundImage: `url('${ imageLibraryUrl }${ pageParser.frontPageItems[3].itemPromoImage }')`}}>
<div className="front-page-item-caption">{ pageParser.frontPageItems[3].itemName }</div>
</div> }
</div>
</div>
);
}

View File

@ -0,0 +1,6 @@
import { CatalogLayoutProps } from '../CatalogLayout.types';
export interface CatalogLayoutFrontpage4ViewProps extends CatalogLayoutProps
{
}

View File

@ -7,7 +7,7 @@ import { PetImageView } from '../../../../../pet-image/PetImageView';
import { RoomPreviewerView } from '../../../../../room-previewer/RoomPreviewerView';
import { useCatalogContext } from '../../../../context/CatalogContext';
import { CatalogActions } from '../../../../reducers/CatalogReducer';
import { GetPetAvailableColors, GetPetIndexFromLocalization } from '../../../../utils/CatalogUtilities';
import { GetCatalogPageImage, GetCatalogPageText, GetPetAvailableColors, GetPetIndexFromLocalization } from '../../../../utils/CatalogUtilities';
import { CatalogLayoutPetViewProps } from './CatalogLayoutPetView.types';
import { CatalogLayoutPetPurchaseView } from './purchase/CatalogLayoutPetPurchaseView';
@ -160,16 +160,24 @@ export const CatalogLayoutPetView: FC<CatalogLayoutPetViewProps> = props =>
}) }
</div>
</div>
<div className="position-relative d-flex flex-column col-5">
<RoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 }>
{ (petIndex > -1 && petIndex <= 7) &&
<button type="button" className= { 'btn btn-primary btn-sm color-button ' + (colorsShowing ? 'active ' : '') } onClick={ event => setColorsShowing(!colorsShowing) }>
<i className="fas fa-fill-drip" />
</button> }
</RoomPreviewerView>
<div className="fs-6 text-black mt-1 overflow-hidden">{ petBreedName }</div>
<CatalogLayoutPetPurchaseView offer={ activeOffer } pageId={ pageParser.pageId } extra={ petPurchaseString } />
</div>
{ (petIndex === -1) &&
<div className="position-relative d-flex flex-column col-5 justify-content-center align-items-center">
<div className="d-block mb-2">
<img alt="" src={ GetCatalogPageImage(pageParser, 1) } />
</div>
<div className="fs-6 text-center text-black lh-sm overflow-hidden">{ GetCatalogPageText(pageParser, 0) }</div>
</div> }
{ (petIndex >= 0) &&
<div className="position-relative d-flex flex-column col-5">
<RoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 }>
{ (petIndex > -1 && petIndex <= 7) &&
<button type="button" className= { 'btn btn-primary btn-sm color-button ' + (colorsShowing ? 'active ' : '') } onClick={ event => setColorsShowing(!colorsShowing) }>
<i className="fas fa-fill-drip" />
</button> }
</RoomPreviewerView>
<div className="fs-6 text-black mt-1 overflow-hidden">{ petBreedName }</div>
<CatalogLayoutPetPurchaseView offer={ activeOffer } pageId={ pageParser.pageId } extra={ petPurchaseString } />
</div> }
</div>
);
}

View File

@ -3,8 +3,8 @@ import { FC, useCallback, useState } from 'react';
import { CatalogEvent } from '../../../../../../../events';
import { useUiEvent } from '../../../../../../../hooks/events/ui/ui-event';
import { SendMessageHook } from '../../../../../../../hooks/messages/message-event';
import { CurrencyIcon } from '../../../../../../../utils/currency-icon/CurrencyIcon';
import { LocalizeText } from '../../../../../../../utils/LocalizeText';
import { CurrencyIcon } from '../../../../../../currency-icon/CurrencyIcon';
import { CatalogPurchaseButtonView } from '../../../purchase/purchase-button/CatalogPurchaseButtonView';
import { CatalogPetNameApprovalView } from '../name-approval/CatalogPetNameApprovalView';
import { CatalogLayoutPetPurchaseViewProps } from './CatalogLayoutPetPurchaseView.types';

View File

@ -0,0 +1,2 @@
.nitro-catalog-layout-pets3 {
}

View File

@ -0,0 +1,24 @@
import { FC } from 'react';
import { GetCatalogPageImage, GetCatalogPageText } from '../../../../utils/CatalogUtilities';
import { CatalogLayoutPets3ViewProps } from './CatalogLayoutPets3View.types';
export const CatalogLayoutPets3View: FC<CatalogLayoutPets3ViewProps> = props =>
{
const { pageParser = null } = props;
return (
<div className="row h-100 nitro-catalog-layout-pets3">
<div className="col-7">
<div className="" dangerouslySetInnerHTML={ {__html: GetCatalogPageText(pageParser, 1) } } />
<div className="" dangerouslySetInnerHTML={ {__html: GetCatalogPageText(pageParser, 2) } } />
<div className="" dangerouslySetInnerHTML={ {__html: GetCatalogPageText(pageParser, 3) } } />
</div>
<div className="position-relative d-flex flex-column col-5 justify-content-center align-items-center">
<div className="d-block mb-2">
<img alt="" src={ GetCatalogPageImage(pageParser, 1) } />
</div>
<div className="fs-6 text-center text-black lh-sm overflow-hidden">{ GetCatalogPageText(pageParser, 0) }</div>
</div>
</div>
);
}

View File

@ -0,0 +1,6 @@
import { CatalogLayoutProps } from '../CatalogLayout.types';
export interface CatalogLayoutPets3ViewProps extends CatalogLayoutProps
{
}

View File

@ -5,7 +5,7 @@ import { LocalizeText } from '../../../../../../utils/LocalizeText';
import { RoomPreviewerView } from '../../../../../room-previewer/RoomPreviewerView';
import { useCatalogContext } from '../../../../context/CatalogContext';
import { ProductTypeEnum } from '../../../../enums/ProductTypeEnum';
import { GetOfferName } from '../../../../utils/CatalogUtilities';
import { GetCatalogPageImage, GetCatalogPageText, GetOfferName } from '../../../../utils/CatalogUtilities';
import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView';
import { CatalogPurchaseView } from '../../purchase/CatalogPurchaseView';
import { CatalogLayoutSpacesViewProps } from './CatalogLayoutSpacesView.types';
@ -81,6 +81,13 @@ export const CatalogLayoutSpacesView: FC<CatalogLayoutSpacesViewProps> = props =
</div>
<CatalogPageOffersView offers={ groups[activeGroupIndex] } />
</div>
{ !product &&
<div className="position-relative d-flex flex-column col-5 justify-content-center align-items-center">
<div className="d-block mb-2">
<img alt="" src={ GetCatalogPageImage(pageParser, 1) } />
</div>
<div className="fs-6 text-center text-black lh-sm overflow-hidden">{ GetCatalogPageText(pageParser, 0) }</div>
</div> }
{ product &&
<div className="position-relative d-flex flex-column col">
<RoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 } />

View File

@ -1,16 +1,16 @@
import { CatalogClubOfferData, CatalogRequestVipOffersComposer } from 'nitro-renderer';
import { FC, useCallback, useEffect } from 'react';
import { FC, useCallback, useEffect, useMemo } from 'react';
import { SendMessageHook } from '../../../../../../hooks/messages/message-event';
import { CurrencyIcon } from '../../../../../../utils/currency-icon/CurrencyIcon';
import { LocalizeText } from '../../../../../../utils/LocalizeText';
import { CurrencyIcon } from '../../../../../currency-icon/CurrencyIcon';
import { useCatalogContext } from '../../../../context/CatalogContext';
import { GetCatalogPageImage, GetCatalogPageText } from '../../../../utils/CatalogUtilities';
import { GetCatalogPageImage } from '../../../../utils/CatalogUtilities';
import { CatalogLayoutVipBuyViewProps } from './CatalogLayoutVipBuyView.types';
export const CatalogLayoutVipBuyView: FC<CatalogLayoutVipBuyViewProps> = props =>
{
const { catalogState = null } = useCatalogContext();
const { pageParser = null, clubOffers = null } = catalogState;
const { pageParser = null, clubOffers = null, subscriptionInfo = null } = catalogState;
useEffect(() =>
{
@ -41,6 +41,17 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutVipBuyViewProps> = props =
return offerText;
}, []);
const getSubscriptionDetails = useMemo(() =>
{
if(!subscriptionInfo) return '';
const clubDays = subscriptionInfo.clubDays;
const clubPeriods = subscriptionInfo.clubPeriods;
const totalDays = (clubPeriods * 31) + clubDays;
return LocalizeText('catalog.vip.extend.info', [ 'days' ], [ totalDays.toString() ]);
}, [ subscriptionInfo ]);
return (
<div className="row h-100 nitro-catalog-layout-vip-buy">
<div className="col-7">
@ -74,7 +85,7 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutVipBuyViewProps> = props =
<div className="d-block mb-2">
<img alt="" src={ GetCatalogPageImage(pageParser, 1) } />
</div>
<div className="fs-6 text-center text-black lh-sm overflow-hidden">{ GetCatalogPageText(pageParser, 0) }</div>
<div className="fs-6 text-center text-black lh-sm overflow-hidden" dangerouslySetInnerHTML={ {__html: getSubscriptionDetails } }></div>
</div>
</div>
);

View File

@ -1,6 +1,6 @@
import { FC, useEffect, useState } from 'react';
import { CurrencyIcon } from '../../../../../utils/currency-icon/CurrencyIcon';
import { LocalizeText } from '../../../../../utils/LocalizeText';
import { CurrencyIcon } from '../../../../currency-icon/CurrencyIcon';
import { CatalogPurchaseViewProps } from './CatalogPurchaseView.types';
import { CatalogPurchaseButtonView } from './purchase-button/CatalogPurchaseButtonView';

View File

@ -1,7 +1,8 @@
import { GetConfiguration } from '../GetConfiguration';
import { FC } from 'react';
import { GetConfiguration } from '../../utils/GetConfiguration';
import { CurrencyIconProps } from './CurrencyIcon.types';
export function CurrencyIcon(props: CurrencyIconProps): JSX.Element
export const CurrencyIcon: FC<CurrencyIconProps> = props =>
{
let url = GetConfiguration<string>('currency.asset.icon.url', '');

View File

@ -42,8 +42,8 @@
}
.left {
width: 100%;
height: 100%;
width: 1200px;
height: 800px;
bottom: 0;
left: 0;
animation-iteration-count: 1;

View File

@ -105,7 +105,11 @@ export const InventoryView: FC<InventoryViewProps> = props =>
<NitroCardTabsView>
{ tabs.map((name, index) =>
{
return <NitroCardTabsItemView key={ index } tabText={ LocalizeText(name) } isActive={ (currentTab === name) } onClick={ event => setCurrentTab(name) } />
return (
<NitroCardTabsItemView key={ index } isActive={ (currentTab === name) } onClick={ event => setCurrentTab(name) }>
{ LocalizeText(name) }
</NitroCardTabsItemView>
);
}) }
</NitroCardTabsView>
<NitroCardContentView>

View File

@ -76,8 +76,8 @@ export const InventoryBadgeView: FC<InventoryBadgeViewProps> = props =>
return (
<>
<div className="row">
<div className="col-7">
<div className="row h-100">
<div className="d-flex flex-column col-7">
<InventoryBadgeResultsView badges={ badges } activeBadges={ activeBadges } />
</div>
<div className="col">

View File

@ -1,4 +1,5 @@
import { FC } from 'react';
import { ScrollableAreaView } from '../../../../../layout/scrollable-area/ScrollableAreaView';
import { InventoryBadgeItemView } from '../item/InventoryBadgeItemView';
import { InventoryBadgeResultsViewProps } from './InventoryBadgeResultsView.types';
@ -7,13 +8,15 @@ export const InventoryBadgeResultsView: FC<InventoryBadgeResultsViewProps> = pro
const { badges = [], activeBadges = [] } = props;
return (
<div className="row row-cols-5 align-content-start g-0 badge-item-container">
{ badges && (badges.length > 0) && badges.map((code, index) =>
{
if(activeBadges.indexOf(code) >= 0) return null;
return <InventoryBadgeItemView key={ index } badge={ code } />
}) }
<div className="d-flex flex-grow-1">
<ScrollableAreaView className="row row-cols-5 align-content-start g-0 w-100">
{ badges && (badges.length > 0) && badges.map((code, index) =>
{
if(activeBadges.indexOf(code) >= 0) return null;
return <InventoryBadgeItemView key={ index } badge={ code } />
}) }
</ScrollableAreaView>
</div>
);
}

View File

@ -81,8 +81,8 @@ export const InventoryBotView: FC<InventoryBotViewProps> = props =>
}
return (
<div className="row">
<div className="col-7">
<div className="row h-100">
<div className="d-flex flex-column col-7">
<InventoryBotResultsView botItems={ botItems } />
</div>
<div className="d-flex flex-column col-5 justify-space-between">

View File

@ -1,4 +1,5 @@
import { FC } from 'react';
import { ScrollableAreaView } from '../../../../../layout/scrollable-area/ScrollableAreaView';
import { InventoryBotItemView } from '../item/InventoryBotItemView';
import { InventoryBotResultsViewProps } from './InventoryBotResultsView.types';
@ -7,11 +8,13 @@ export const InventoryBotResultsView: FC<InventoryBotResultsViewProps> = props =
const { botItems = [] } = props;
return (
<div className="row row-cols-5 align-content-start g-0 bot-item-container">
{ botItems && (botItems.length > 0) && botItems.map((item, index) =>
{
return <InventoryBotItemView key={ index } botItem={ item } />
}) }
<div className="d-flex flex-grow-1">
<ScrollableAreaView className="row row-cols-5 align-content-start g-0 w-100">
{ botItems && (botItems.length > 0) && botItems.map((item, index) =>
{
return <InventoryBotItemView key={ index } botItem={ item } />
}) }
</ScrollableAreaView>
</div>
);
}

View File

@ -116,8 +116,8 @@ export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
}
return (
<div className="row">
<div className="col-7">
<div className="row h-100">
<div className="d-flex flex-column col-7">
<InventoryFurnitureSearchView groupItems={ groupItems } setGroupItems={ setFilteredGroupItems } />
<InventoryFurnitureResultsView groupItems={ filteredGroupItems } />
</div>

View File

@ -1,5 +1,2 @@
.furni-item-container {
height: 188px;
max-height: 188px;
overflow-y: auto;
}

View File

@ -1,4 +1,5 @@
import { FC } from 'react';
import { ScrollableAreaView } from '../../../../../layout/scrollable-area/ScrollableAreaView';
import { InventoryFurnitureItemView } from '../item/InventoryFurnitureItemView';
import { InventoryFurnitureResultsViewProps } from './InventoryFurnitureResultsView.types';
@ -7,11 +8,13 @@ export const InventoryFurnitureResultsView: FC<InventoryFurnitureResultsViewProp
const { groupItems = [] } = props;
return (
<div className="row row-cols-5 align-content-start g-0 furni-item-container">
{ groupItems && (groupItems.length > 0) && groupItems.map((item, index) =>
{
return <InventoryFurnitureItemView key={ index } groupItem={ item } />
}) }
<div className="d-flex flex-grow-1">
<ScrollableAreaView className="row row-cols-5 align-content-start g-0 w-100">
{ groupItems && (groupItems.length > 0) && groupItems.map((item, index) =>
{
return <InventoryFurnitureItemView key={ index } groupItem={ item } />
}) }
</ScrollableAreaView>
</div>
);
}

View File

@ -1,4 +1,5 @@
import { FC, useEffect, useState } from 'react';
import { LocalizeText } from '../../../../../utils/LocalizeText';
import { InventoryFurnitureSearchViewProps } from './InventoryFurnitureSearchView.types';
export const InventoryFurnitureSearchView: FC<InventoryFurnitureSearchViewProps> = props =>
@ -31,7 +32,7 @@ export const InventoryFurnitureSearchView: FC<InventoryFurnitureSearchViewProps>
return (
<div className="d-flex mb-1">
<div className="d-flex flex-grow-1 me-1">
<input type="text" className="form-control form-control-sm" placeholder="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) } />
</div>
<div className="d-flex">
<button type="button" className="btn btn-primary btn-sm">

View File

@ -82,7 +82,7 @@ export const InventoryPetView: FC<InventoryPetViewProps> = props =>
return (
<div className="row h-100">
<div className="col-7">
<div className="d-flex flex-column col-7">
<InventoryPetResultsView petItems={ petItems } />
</div>
<div className="d-flex flex-column col-5 justify-space-between">

View File

@ -1,4 +1,5 @@
import { FC } from 'react';
import { ScrollableAreaView } from '../../../../../layout/scrollable-area/ScrollableAreaView';
import { InventoryPetItemView } from '../item/InventoryPetItemView';
import { InventoryPetResultsViewProps } from './InventoryPetResultsView.types';
@ -7,11 +8,13 @@ export const InventoryPetResultsView: FC<InventoryPetResultsViewProps> = props =
const { petItems = [] } = props;
return (
<div className="row row-cols-5 align-content-start g-0 pet-item-container">
{ petItems && (petItems.length > 0) && petItems.map((item, index) =>
{
return <InventoryPetItemView key={ index } petItem={ item } />
}) }
<div className="d-flex flex-grow-1">
<ScrollableAreaView className="row row-cols-5 align-content-start g-0 w-100">
{ petItems && (petItems.length > 0) && petItems.map((item, index) =>
{
return <InventoryPetItemView key={ index } petItem={ item } />
}) }
</ScrollableAreaView>
</div>
);
}

View File

@ -16,6 +16,7 @@ import { NavigatorSearchView } from './views/search/NavigatorSearchView';
export const NavigatorView: FC<NavigatorViewProps> = props =>
{
const [ isVisible, setIsVisible ] = useState(false);
const [ isCreatorOpen, setCreatorOpen ] = useState(false);
const [ navigatorState, dispatchNavigatorState ] = useReducer(NavigatorReducer, initialNavigator);
const { needsNavigatorUpdate = false, topLevelContext = null, topLevelContexts = null } = navigatorState;
@ -86,8 +87,15 @@ export const NavigatorView: FC<NavigatorViewProps> = props =>
<NitroCardTabsView>
{ topLevelContexts.map((context, index) =>
{
return <NitroCardTabsItemView key={ index } tabText={ LocalizeText(('navigator.toplevelview.' + context.code)) } isActive={ (topLevelContext === context) } onClick={ event => sendSearch('', context.code) } />
return (
<NitroCardTabsItemView key={ index } isActive={ ((topLevelContext === context) && !isCreatorOpen) } onClick={ event => sendSearch('', context.code) }>
{ LocalizeText(('navigator.toplevelview.' + context.code)) }
</NitroCardTabsItemView>
);
}) }
<NitroCardTabsItemView>
</NitroCardTabsItemView>
</NitroCardTabsView>
<NitroCardContentView>
<NavigatorSearchView sendSearch={ sendSearch } />

View File

@ -1,3 +1,4 @@
@import './creator/NavigatorRoomCreatorView';
@import './search/NavigatorSearchView';
@import './search-result/NavigatorSearchResultView';
@import './search-result-item/NavigatorSearchResultItemView';

View File

@ -0,0 +1,7 @@
import { FC } from 'react';
import { NavigatorRoomCreatorViewProps } from './NavigatorRoomCreatorView.types';
export const NavigatorRoomCreatorView: FC<NavigatorRoomCreatorViewProps> = props =>
{
return null;
}

View File

@ -0,0 +1,4 @@
export interface NavigatorRoomCreatorViewProps
{
}

View File

@ -1,4 +1,4 @@
import { CurrencyIcon } from '../../../utils/currency-icon/CurrencyIcon';
import { CurrencyIcon } from '../../currency-icon/CurrencyIcon';
import { CurrencyViewProps } from './CurrencyView.types';
export function CurrencyView(props: CurrencyViewProps): JSX.Element

View File

@ -1,5 +1,5 @@
import { EventDispatcher, IEventDispatcher, IRoomSession, RoomBackgroundColorEvent, RoomEngineDimmerStateEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomId, RoomObjectCategory, RoomObjectHSLColorEnabledEvent, RoomObjectOperationType, RoomSessionEvent, RoomZoomEvent } from 'nitro-renderer';
import { useCallback, useState } from 'react';
import { FC, useCallback, useState } from 'react';
import { ProcessRoomObjectOperation } from '../../api/nitro/room/ProcessRoomObjectOperation';
import { SetActiveRoomId } from '../../api/nitro/room/SetActiveRoomId';
import { GetRoomSession } from '../../api/nitro/session/GetRoomSession';
@ -13,7 +13,7 @@ import { RoomHostViewProps } from './RoomHostView.types';
import { CanManipulateFurniture } from './utils/CanManipulateFurniture';
import { IsFurnitureSelectionDisabled } from './utils/IsFurnitureSelectionDisabled';
export function RoomHostView(props: RoomHostViewProps): JSX.Element
export const RoomHostView: FC<RoomHostViewProps> = props =>
{
const [ roomSession, setRoomSession ] = useState<IRoomSession>(null);
const [ eventDispatcher, setEventDispatcher ] = useState<IEventDispatcher>(null);
@ -40,7 +40,7 @@ export function RoomHostView(props: RoomHostViewProps): JSX.Element
const onRoomEngineObjectEvent = useCallback((event: RoomEngineObjectEvent) =>
{
if(!eventDispatcher) return;
if(!roomSession || !eventDispatcher) return;
const objectId = event.objectId;
const category = event.category;
@ -101,6 +101,10 @@ export function RoomHostView(props: RoomHostViewProps): JSX.Element
case RoomEngineObjectEvent.REQUEST_ROTATE:
if(CanManipulateFurniture(roomSession, objectId, category)) ProcessRoomObjectOperation(objectId, category, RoomObjectOperationType.OBJECT_ROTATE_POSITIVE);
break;
case RoomEngineObjectEvent.REQUEST_MANIPULATION:
console.log('yaaa')
if(CanManipulateFurniture(roomSession, objectId, category)) updateEvent = new RoomWidgetRoomObjectUpdateEvent(RoomWidgetRoomObjectUpdateEvent.OBJECT_REQUEST_MANIPULATION, objectId, category, event.roomId);
break;
}
if(updateEvent)
@ -152,6 +156,7 @@ export function RoomHostView(props: RoomHostViewProps): JSX.Element
useRoomEngineEvent(RoomEngineObjectEvent.PLACED, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineObjectEvent.REQUEST_MOVE, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineObjectEvent.REQUEST_ROTATE, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineObjectEvent.REQUEST_MANIPULATION, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineObjectEvent.MOUSE_ENTER, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineObjectEvent.MOUSE_LEAVE, onRoomEngineObjectEvent);

View File

@ -1 +1 @@
@import './widgets/Widgets';
@import './widgets/RoomWidgets';

View File

@ -6,6 +6,7 @@ import { WindowResizeEvent } from '../../api/nitro/room/DispatchResizeEvent';
import { DispatchTouchEvent } from '../../api/nitro/room/DispatchTouchEvent';
import { GetRoomEngine } from '../../api/nitro/room/GetRoomEngine';
import { RoomViewProps } from './RoomView.types';
import { AvatarInfoWidgetView } from './widgets/avatar-info/AvatarInfoWidgetView';
import { ChatInputView } from './widgets/chat-input/ChatInputView';
import { ChatWidgetView } from './widgets/chat/ChatWidgetView';
import { FurnitureWidgetsView } from './widgets/furniture/FurnitureWidgetsView';
@ -90,9 +91,10 @@ export function RoomView(props: RoomViewProps): JSX.Element
{ roomSession && events && roomCanvas &&
createPortal(props.children, document.getElementById('room-view').appendChild(roomCanvas)) &&
<>
<AvatarInfoWidgetView events={ events } />
<ChatWidgetView />
<ChatInputView />
<FurnitureWidgetsView events={ events } />
<ChatWidgetView />
</> }
</div>
);

View File

@ -0,0 +1,6 @@
import { IEventDispatcher } from 'nitro-renderer';
export interface RoomWidgetProps
{
events: IEventDispatcher;
}

View File

@ -0,0 +1,91 @@
import { IFurnitureData, RoomObjectCategory, RoomObjectVariable } from 'nitro-renderer';
import { FC, useCallback } from 'react';
import { GetRoomEngine, GetRoomSession, GetSessionDataManager } from '../../../../api';
import { CreateEventDispatcherHook } from '../../../../hooks/events/event-dispatcher.base';
import { RoomObjectNameEvent, RoomWidgetRoomObjectUpdateEvent } from '../events';
import { AvatarInfoWidgetViewProps } from './AvatarInfoWidgetView.types';
export const AvatarInfoWidgetView: FC<AvatarInfoWidgetViewProps> = props =>
{
const { events = null } = props;
const processObjectName = useCallback((roomId: number, objectId: number, category: number) =>
{
let id = -1;
let name: string = null;
let type = 0;
let roomIndex = 0;
switch(category)
{
case RoomObjectCategory.FLOOR:
case RoomObjectCategory.WALL:
const roomObject = GetRoomEngine().getRoomObject(roomId, id, category);
if(!roomObject) return;
if(roomObject.type.indexOf('poster') === 0)
{
name = ('${poster_' + parseInt(roomObject.type.replace('poster', '')) + '_name}');
roomIndex = roomObject.id;
}
else
{
let furniData: IFurnitureData = null;
const typeId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_TYPE_ID);
if(category === RoomObjectCategory.FLOOR)
{
furniData = GetSessionDataManager().getFloorItemData(typeId);
}
else if(category === RoomObjectCategory.WALL)
{
furniData = GetSessionDataManager().getWallItemData(typeId);
}
if(!furniData) return;
id = furniData.id;
name = furniData.name;
roomIndex = roomObject.id;
}
break;
case RoomObjectCategory.UNIT:
const userData = GetRoomSession().userDataManager.getUserDataByIndex(id);
if(!userData) return;
id = userData.webID;
name = userData.name;
type = userData.type;
roomIndex = userData.roomIndex;
break;
}
if(!name) return;
events.dispatchEvent(new RoomObjectNameEvent(id, category, name, type, roomIndex));
}, [ events ]);
const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetRoomObjectUpdateEvent) =>
{
switch(event.type)
{
case RoomWidgetRoomObjectUpdateEvent.OBJECT_ROLL_OVER: {
processObjectName(event.roomId, event.id, event.category);
return;
}
case RoomWidgetRoomObjectUpdateEvent.OBJECT_ROLL_OUT: {
console.log('out');
return;
}
}
}, []);
CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.OBJECT_ROLL_OVER, events, onRoomWidgetRoomObjectUpdateEvent);
CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.OBJECT_ROLL_OUT, events, onRoomWidgetRoomObjectUpdateEvent);
return null;
}

View File

@ -0,0 +1,4 @@
import { RoomWidgetProps } from '../RoomWidgets.types';
export interface AvatarInfoWidgetViewProps extends RoomWidgetProps
{}

View File

@ -13,19 +13,19 @@ export function ChatWidgetView(props: ChatWidgetViewProps): JSX.Element
const [ chatMessages, setChatMessages ] = useState<ChatBubbleMessage[]>([]);
const elementRef = useRef<HTMLDivElement>();
const removeLastHiddenChat = useCallback(() =>
const removeFirstHiddenChat = useCallback(() =>
{
if(!chatMessages.length) return;
const lastChat = chatMessages[chatMessages.length - 1];
if((lastChat.lastTop > -(lastChat.height))) return;
const lastChat = chatMessages[0];
if((lastChat.lastTop > (-(lastChat.height) * 2))) return;
setChatMessages(prevValue =>
{
const newMessages = [ ...prevValue ];
newMessages.splice((newMessages.length - 1), 1);
newMessages.shift();
return newMessages;
});
@ -33,43 +33,34 @@ export function ChatWidgetView(props: ChatWidgetViewProps): JSX.Element
const moveChatUp = useCallback((chat: ChatBubbleMessage, amount: number) =>
{
if(!chat.elementRef) return;
chat.lastTop -= amount;
let y = chat.elementRef.offsetHeight;
if(amount > 0) y = amount;
let top = (chat.elementRef.offsetTop - y);
chat.lastTop = top;
chat.elementRef.style.top = (top + 'px');
if(chat.elementRef) chat.elementRef.style.top = (chat.lastTop + 'px');
}, []);
const moveAllChatsUp = useCallback((amount: number) =>
{
chatMessages.forEach(chat => moveChatUp(chat, amount));
}, [ chatMessages, moveChatUp ]);
const makeRoom = useCallback((amount: number = 0, skipLast: boolean = false) =>
removeFirstHiddenChat();
}, [ chatMessages, moveChatUp, removeFirstHiddenChat ]);
const makeRoom = useCallback((chat: ChatBubbleMessage) =>
{
const lastChat = chatMessages[chatMessages.length - 1];
if(!lastChat) return;
const lowestPoint = ((lastChat.lastTop + lastChat.height) - 1);
const requiredSpace = ((amount || lastChat.height) + 1);
const lowestPoint = ((chat.lastTop + chat.height) - 1);
const requiredSpace = (chat.height + 1);
const spaceAvailable = (elementRef.current.offsetHeight - lowestPoint);
if(spaceAvailable < requiredSpace)
{
amount = (requiredSpace - spaceAvailable);
const amount = (requiredSpace - spaceAvailable);
chatMessages.forEach((chat, index) =>
{
if(skipLast && (index === (chatMessages.length - 1))) return;
chatMessages.forEach((existingChat, index) =>
{
if(existingChat === chat) return;
moveChatUp(chat, amount)
});
moveChatUp(existingChat, amount)
});
}
}, [ chatMessages, moveChatUp ]);
@ -146,25 +137,25 @@ export function ChatWidgetView(props: ChatWidgetViewProps): JSX.Element
useRoomSessionManagerEvent(RoomSessionChatEvent.CHAT_EVENT, onRoomSessionChatEvent);
// useEffect(() =>
// {
// const interval = setInterval(() => moveAllChatsUp(15), 500);
// return () =>
// {
// if(interval) clearInterval(interval);
// }
// }, [ chatMessages, moveAllChatsUp ]);
useEffect(() =>
{
const interval = setInterval(() => moveAllChatsUp(15), 500);
const interval = setInterval(() => removeFirstHiddenChat(), 1000);
return () =>
{
if(interval) clearInterval(interval);
}
}, [ chatMessages, moveAllChatsUp ]);
useEffect(() =>
{
const interval = setInterval(() => removeLastHiddenChat(), 500);
return () =>
{
if(interval) clearInterval(interval);
}
}, [ removeLastHiddenChat ]);
}, [ removeFirstHiddenChat ]);
return (
<div ref={ elementRef } className="nitro-chat-widget">

View File

@ -24,8 +24,6 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props =>
chat.width = width;
chat.height = height;
chat.elementRef = element;
if(isVisible || !chat) return;
let left = chat.lastLeft;
let top = chat.lastTop;
@ -34,22 +32,27 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props =>
{
left = (chat.location.x - (width / 2));
top = (element.parentElement.offsetHeight - height);
chat.lastLeft = left;
chat.lastTop = top;
}
chat.lastLeft = left;
chat.lastTop = top;
element.style.left = (left + 'px');
element.style.top = (top + 'px');
makeRoom(0, true);
setIsVisible(true);
if(!chat.visible)
{
makeRoom(chat);
}
chat.visible = true;
//setIsVisible(true);
return () =>
{
chat.elementRef = null;
}
}, [ isVisible, elementRef, chat, makeRoom ]);
}, [ elementRef, isVisible, chat, makeRoom ]);
return (
<div ref={ elementRef } className="bubble-container" style={ { visibility: (isVisible ? 'visible' : 'hidden') } }>

View File

@ -3,5 +3,5 @@ import { ChatBubbleMessage } from '../utils/ChatBubbleMessage';
export interface ChatWidgetMessageViewProps
{
chat: ChatBubbleMessage;
makeRoom: (amount?: number, skipLast?: boolean) => void;
makeRoom: (chat: ChatBubbleMessage) => void;
}

View File

@ -1,10 +0,0 @@
import { ChatMessagesWidgetViewProps } from './ChatMessagesWidgetView.types';
export function ChatMessagesWidgetView(props: ChatMessagesWidgetViewProps): JSX.Element
{
const {} = props;
return (
<></>
);
}

View File

@ -1,2 +0,0 @@
export interface ChatMessagesWidgetViewProps
{}

View File

@ -1,10 +0,0 @@
import { ChatMessageWidgetViewProps } from './ChatMessageWidgetView.types';
export function ChatMessageWidgetView(props: ChatMessageWidgetViewProps): JSX.Element
{
const {} = props;
return (
<></>
);
}

View File

@ -1,2 +0,0 @@
export interface ChatMessageWidgetViewProps
{}

View File

@ -10,6 +10,7 @@ export class ChatBubbleMessage
public lastTop: number = 0;
public lastLeft: number = 0;
public elementRef: HTMLDivElement = null;
public visible: boolean = false;
constructor(
public text: string = '',

View File

@ -2,14 +2,15 @@ import { RoomWidgetUpdateEvent } from 'nitro-renderer';
export class RoomWidgetRoomObjectUpdateEvent extends RoomWidgetUpdateEvent
{
public static OBJECT_SELECTED: string = 'RWROUE_OBJECT_SELECTED';
public static OBJECT_DESELECTED: string = 'RWROUE_OBJECT_DESELECTED';
public static USER_REMOVED: string = 'RWROUE_USER_REMOVED';
public static FURNI_REMOVED: string = 'RWROUE_FURNI_REMOVED';
public static FURNI_ADDED: string = 'RWROUE_FURNI_ADDED';
public static USER_ADDED: string = 'RWROUE_USER_ADDED';
public static OBJECT_ROLL_OVER: string = 'RWROUE_OBJECT_ROLL_OVER';
public static OBJECT_ROLL_OUT: string = 'RWROUE_OBJECT_ROLL_OUT';
public static OBJECT_SELECTED: string = 'RWROUE_OBJECT_SELECTED';
public static OBJECT_DESELECTED: string = 'RWROUE_OBJECT_DESELECTED';
public static USER_REMOVED: string = 'RWROUE_USER_REMOVED';
public static FURNI_REMOVED: string = 'RWROUE_FURNI_REMOVED';
public static FURNI_ADDED: string = 'RWROUE_FURNI_ADDED';
public static USER_ADDED: string = 'RWROUE_USER_ADDED';
public static OBJECT_ROLL_OVER: string = 'RWROUE_OBJECT_ROLL_OVER';
public static OBJECT_ROLL_OUT: string = 'RWROUE_OBJECT_ROLL_OUT';
public static OBJECT_REQUEST_MANIPULATION: string = 'RWROUE_OBJECT_REQUEST_MANIPULATION';
private _id: number;
private _category: number;

View File

@ -1,6 +1,4 @@
import { IEventDispatcher } from 'nitro-renderer';
import { RoomWidgetProps } from '../RoomWidgets.types';
export interface FurnitureWidgetProps
{
events: IEventDispatcher;
}
export interface FurnitureWidgetProps extends RoomWidgetProps
{}

View File

@ -1 +1,2 @@
@import './manipulation-menu/FurnitureManipulationMenuView';
@import './stickie/FurnitureStickieView';

View File

@ -1,5 +1,6 @@
import { FurnitureWidgetsViewProps } from './FurnitureWidgetsView.types';
import { FurnitureHighScoreView } from './high-score/FurnitureHighScoreView';
import { FurnitureManipulationMenuView } from './manipulation-menu/FurnitureManipulationMenuView';
import { FurnitureMannequinView } from './mannequin/FurnitureMannequinView';
import { FurniturePresentView } from './present/FurniturePresentView';
import { FurnitureStickieView } from './stickie/FurnitureStickieView';
@ -11,6 +12,7 @@ export function FurnitureWidgetsView(props: FurnitureWidgetsViewProps): JSX.Elem
return (
<div className="position-absolute nitro-room-widgets t-0 l-0">
<FurnitureHighScoreView events={ events } />
<FurnitureManipulationMenuView events={ events } />
<FurnitureMannequinView events={ events } />
<FurniturePresentView events={ events } />
<FurnitureStickieView events={ events } />

View File

@ -0,0 +1,71 @@
import { RoomObjectOperationType } from 'nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import { ProcessRoomObjectOperation } from '../../../../../api';
import { CreateEventDispatcherHook } from '../../../../../hooks/events/event-dispatcher.base';
import { RoomWidgetRoomObjectUpdateEvent } from '../../events';
import { ObjectLocationView } from '../../object-location/ObjectLocationView';
import { FurnitureManipulationMenuViewProps } from './FurnitureManipulationMenuView.types';
export const FurnitureManipulationMenuView: FC<FurnitureManipulationMenuViewProps> = props =>
{
const { events = null } = props;
const [ isVisible, setIsVisible ] = useState(false);
const [ objectId, setObjectId ] = useState(-1);
const [ objectType, setObjectType ] = useState(-1);
const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetRoomObjectUpdateEvent) =>
{
switch(event.type)
{
case RoomWidgetRoomObjectUpdateEvent.OBJECT_REQUEST_MANIPULATION: {
setIsVisible(true);
setObjectId(event.id);
setObjectType(event.category);
return;
}
case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: {
return;
}
case RoomWidgetRoomObjectUpdateEvent.OBJECT_DESELECTED: {
console.log('tru')
setIsVisible(false);
return;
}
}
}, []);
const rotateFurniture = useCallback(() =>
{
ProcessRoomObjectOperation(objectId, objectType, RoomObjectOperationType.OBJECT_ROTATE_POSITIVE);
}, [ objectId, objectType ]);
const moveFurniture = useCallback(() =>
{
ProcessRoomObjectOperation(objectId, objectType, RoomObjectOperationType.OBJECT_MOVE);
}, [ objectId, objectType ]);
useEffect(() =>
{
if(!isVisible) return;
moveFurniture();
}, [ isVisible, moveFurniture ]);
CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.OBJECT_REQUEST_MANIPULATION, events, onRoomWidgetRoomObjectUpdateEvent);
CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.OBJECT_DESELECTED, events, onRoomWidgetRoomObjectUpdateEvent);
if(!isVisible) return null;
return (
<ObjectLocationView objectId={ objectId } objectType={ objectType }>
<div className="btn-group">
<button type="button" className="btn btn-primary btn-sm">
<i className="fas fa-times" />
</button>
<button type="button" className="btn btn-primary btn-sm" onClick={ rotateFurniture }>
<i className="fas fa-undo" />
</button>
</div>
</ObjectLocationView>
);
}

View File

@ -0,0 +1,6 @@
import { FurnitureWidgetProps } from '../FurnitureWidget.types';
export interface FurnitureManipulationMenuViewProps extends FurnitureWidgetProps
{
}

View File

@ -0,0 +1,39 @@
import { Nitro } from 'nitro-renderer';
import { FC, useCallback, useEffect, useRef, useState } from 'react';
import { GetRoomEngine, GetRoomSession } from '../../../../api';
import { ObjectLocationViewProps } from './ObjectLocationView.types';
export const ObjectLocationView: FC<ObjectLocationViewProps> = props =>
{
const { objectId = -1, objectType = -1, children = null } = props;
const [ posX, setPosX ] = useState(0);
const [ posY, setPosY ] = useState(0);
const elementRef = useRef<HTMLDivElement>();
const updatePosition = useCallback(() =>
{
const roomSession = GetRoomSession();
const objectBounds = GetRoomEngine().getRoomObjectBoundingRectangle(roomSession.roomId, objectId, objectType, 1);
if(!objectBounds) return;
setPosX(Math.round(((objectBounds.left + (objectBounds.width / 2)) - (elementRef.current.offsetWidth / 2))));
setPosY(Math.round((objectBounds.top - elementRef.current.offsetHeight) + 10));
}, [ objectId, objectType ]);
useEffect(() =>
{
Nitro.instance.ticker.add(updatePosition);
return () =>
{
Nitro.instance.ticker.remove(updatePosition);
}
}, [ updatePosition ]);
return (
<div ref={ elementRef } className="position-absolute w-100" style={ { left: posX, top: posY } }>
{ children }
</div>
);
}

View File

@ -0,0 +1,5 @@
export interface ObjectLocationViewProps
{
objectId: number;
objectType: number;
}