Merge pull request #1 from billsonnn/updates

Updates
This commit is contained in:
Bill 2021-06-12 01:17:33 -04:00 committed by GitHub
commit a64c01b2db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 934 additions and 213 deletions

View File

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

View File

@ -4,11 +4,14 @@ import { GetRoomEngine } from './GetRoomEngine';
let didMouseMove = false; let didMouseMove = false;
let lastClick = 0; let lastClick = 0;
let clickCount = 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; let eventType = event.type;
if(longTouch) eventType = TouchEventType.TOUCH_LONG;
if(eventType === TouchEventType.TOUCH_END && !didMouseMove) if(eventType === TouchEventType.TOUCH_END && !didMouseMove)
{ {
eventType = MouseEventType.MOUSE_CLICK; eventType = MouseEventType.MOUSE_CLICK;
@ -17,7 +20,7 @@ export function DispatchTouchEvent(roomId: number, canvasId: number, event: Touc
{ {
clickCount = 1; clickCount = 1;
if(lastClick >= Date.now() - 300) clickCount++; if(lastClick >= (Date.now() - 300)) clickCount++;
} }
lastClick = Date.now(); lastClick = Date.now();
@ -31,24 +34,7 @@ export function DispatchTouchEvent(roomId: number, canvasId: number, event: Touc
} }
} }
switch(eventType) if(touchTimer) clearTimeout(touchTimer);
{
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;
}
let x = 0; let x = 0;
let y = 0; let y = 0;
@ -66,5 +52,38 @@ export function DispatchTouchEvent(roomId: number, canvasId: number, event: Touc
} }
GetRoomEngine().setActiveRoomId(roomId); 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 { .cursor-pointer {
&:hover {
cursor: pointer; cursor: pointer;
}
} }
.pointer-events-none { .pointer-events-none {

View File

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

View File

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

View File

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

View File

@ -1,24 +1,17 @@
import { IEventDispatcher, NitroEvent } from 'nitro-renderer'; 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 export function CreateEventDispatcherHook(type: string, eventDispatcher: IEventDispatcher, handler: (event: NitroEvent) => void): void
{ {
const handlerRef = useRef<(event: NitroEvent) => void>();
useEffect(() => useEffect(() =>
{ {
handlerRef.current = handler; eventDispatcher.addEventListener(type, handler);
}, [ handler ]);
useEffect(() =>
{
eventDispatcher.addEventListener(type, handlerRef.current);
return () => return () =>
{ {
eventDispatcher.removeEventListener(type, handlerRef.current); eventDispatcher.removeEventListener(type, handler);
} }
}, [ type, eventDispatcher ]); }, [ type, eventDispatcher, handler ]);
} }
export function DispatchEventHook(eventDispatcher: IEventDispatcher, event: NitroEvent): void 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'; @import './tabs/NitroCardTabsView';
} }
@include media-breakpoint-down(md) { @include media-breakpoint-down(lg) {
.draggable-window { .draggable-window {
top: 0 !important; top: 0 !important;
@ -31,6 +31,7 @@ $nitro-card-top-height: $nitro-card-header-height + $nitro-card-tabs-height;
.nitro-card { .nitro-card {
width: 100%; width: 100%;
height: 100%;
&.rounded { &.rounded {
border-radius: 0 !important; border-radius: 0 !important;

View File

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

View File

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

View File

@ -2,7 +2,6 @@ import { MouseEventHandler } from 'react';
export interface NitroCardTabsItemViewProps export interface NitroCardTabsItemViewProps
{ {
tabText?: string;
isActive?: boolean; isActive?: boolean;
onClick?: MouseEventHandler<HTMLLIElement>; 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 './badge-image/BadgeImage';
@import './catalog/CatalogView'; @import './catalog/CatalogView';
@import './catalog-icon/CatalogIconView'; @import './catalog-icon/CatalogIconView';
@import './currency-icon/CurrencyIcon';
@import './friend-list/FriendListView'; @import './friend-list/FriendListView';
@import './furni-image/FurniImageView'; @import './furni-image/FurniImageView';
@import './hotel-view/HotelView'; @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 { FC, useCallback } from 'react';
import { CatalogNameResultEvent, CatalogPurchaseFailureEvent } from '../../events'; import { CatalogNameResultEvent, CatalogPurchaseFailureEvent } from '../../events';
import { CatalogPurchasedEvent } from '../../events/catalog/CatalogPurchasedEvent'; import { CatalogPurchasedEvent } from '../../events/catalog/CatalogPurchasedEvent';
@ -9,6 +9,7 @@ import { CatalogMessageHandlerProps } from './CatalogMessageHandler.types';
import { useCatalogContext } from './context/CatalogContext'; import { useCatalogContext } from './context/CatalogContext';
import { CatalogActions } from './reducers/CatalogReducer'; import { CatalogActions } from './reducers/CatalogReducer';
import { CatalogPetPalette } from './utils/CatalogPetPalette'; import { CatalogPetPalette } from './utils/CatalogPetPalette';
import { SubscriptionInfo } from './utils/SubscriptionInfo';
export const CatalogMessageHandler: FC<CatalogMessageHandlerProps> = props => export const CatalogMessageHandler: FC<CatalogMessageHandlerProps> = props =>
{ {
@ -106,6 +107,32 @@ export const CatalogMessageHandler: FC<CatalogMessageHandlerProps> = props =>
}); });
}, [ dispatchCatalogState ]); }, [ 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(CatalogPagesEvent, onCatalogPagesEvent);
CreateMessageHook(CatalogPageEvent, onCatalogPageEvent); CreateMessageHook(CatalogPageEvent, onCatalogPageEvent);
CreateMessageHook(CatalogPurchaseEvent, onCatalogPurchaseEvent); CreateMessageHook(CatalogPurchaseEvent, onCatalogPurchaseEvent);
@ -116,6 +143,8 @@ export const CatalogMessageHandler: FC<CatalogMessageHandlerProps> = props =>
CreateMessageHook(SellablePetPalettesEvent, onSellablePetPalettesEvent); CreateMessageHook(SellablePetPalettesEvent, onSellablePetPalettesEvent);
CreateMessageHook(CatalogApproveNameResultEvent, onCatalogApproveNameResultEvent); CreateMessageHook(CatalogApproveNameResultEvent, onCatalogApproveNameResultEvent);
CreateMessageHook(CatalogClubEvent, onCatalogClubEvent); CreateMessageHook(CatalogClubEvent, onCatalogClubEvent);
CreateMessageHook(UserSubscriptionEvent, onUserSubscriptionEvent);
CreateMessageHook(CatalogUpdatedEvent, onCatalogUpdatedEvent);
return null; return null;
} }

View File

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

View File

@ -2,17 +2,20 @@ import { CatalogClubOfferData, CatalogPageOfferData, ICatalogPageData, ICatalogP
import { Reducer } from 'react'; import { Reducer } from 'react';
import { CatalogPetPalette } from '../utils/CatalogPetPalette'; import { CatalogPetPalette } from '../utils/CatalogPetPalette';
import { ICatalogOffers, ICatalogSearchResult, SetOffersToNodes } from '../utils/CatalogUtilities'; import { ICatalogOffers, ICatalogSearchResult, SetOffersToNodes } from '../utils/CatalogUtilities';
import { SubscriptionInfo } from '../utils/SubscriptionInfo';
export interface ICatalogState export interface ICatalogState
{ {
root: ICatalogPageData; root: ICatalogPageData;
offerRoot: ICatalogOffers; offerRoot: ICatalogOffers;
currentTab: ICatalogPageData; currentTab: ICatalogPageData;
currentPage: ICatalogPageData;
pageParser: ICatalogPageParser; pageParser: ICatalogPageParser;
activeOffer: CatalogPageOfferData; activeOffer: CatalogPageOfferData;
searchResult: ICatalogSearchResult; searchResult: ICatalogSearchResult;
petPalettes: CatalogPetPalette[]; petPalettes: CatalogPetPalette[];
clubOffers: CatalogClubOfferData[]; clubOffers: CatalogClubOfferData[];
subscriptionInfo: SubscriptionInfo;
} }
export interface ICatalogAction export interface ICatalogAction
@ -22,11 +25,13 @@ export interface ICatalogAction
root?: ICatalogPageData; root?: ICatalogPageData;
offerRoot?: ICatalogOffers; offerRoot?: ICatalogOffers;
currentTab?: ICatalogPageData; currentTab?: ICatalogPageData;
currentPage?: ICatalogPageData;
pageParser?: ICatalogPageParser; pageParser?: ICatalogPageParser;
activeOffer?: CatalogPageOfferData; activeOffer?: CatalogPageOfferData;
searchResult?: ICatalogSearchResult; searchResult?: ICatalogSearchResult;
petPalette?: CatalogPetPalette; petPalette?: CatalogPetPalette;
clubOffers?: CatalogClubOfferData[]; 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_ROOT: string = 'CA_SET_CATALOG_ROOT';
public static SET_CATALOG_CURRENT_TAB: string = 'CA_SET_CATALOG_CURRENT_TAB'; 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_PAGE_PARSER: string = 'CA_SET_CATALOG_PAGE';
public static SET_CATALOG_ACTIVE_OFFER: string = 'CA_SET_ACTIVE_OFFER'; public static SET_CATALOG_ACTIVE_OFFER: string = 'CA_SET_ACTIVE_OFFER';
public static SET_SEARCH_RESULT: string = 'CA_SET_SEARCH_RESULT'; public static SET_SEARCH_RESULT: string = 'CA_SET_SEARCH_RESULT';
public static SET_PET_PALETTE: string = 'CA_SET_PET_PALETTE'; public static SET_PET_PALETTE: string = 'CA_SET_PET_PALETTE';
public static SET_CLUB_OFFERS: string = 'CA_SET_CLUB_OFFERS'; 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 = { export const initialCatalog: ICatalogState = {
root: null, root: null,
offerRoot: null, offerRoot: null,
currentTab: null, currentTab: null,
currentPage: null,
pageParser: null, pageParser: null,
activeOffer: null, activeOffer: null,
searchResult: null, searchResult: null,
petPalettes: [], petPalettes: [],
clubOffers: null clubOffers: null,
subscriptionInfo: new SubscriptionInfo()
} }
export const CatalogReducer: Reducer<ICatalogState, ICatalogAction> = (state, action) => export const CatalogReducer: Reducer<ICatalogState, ICatalogAction> = (state, action) =>
@ -72,8 +82,13 @@ export const CatalogReducer: Reducer<ICatalogState, ICatalogAction> = (state, ac
return { ...state, currentTab, searchResult }; 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: { 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; let activeOffer = null;
if(pageParser.layoutCode === 'single_bundle') if(pageParser.layoutCode === 'single_bundle')
@ -126,6 +141,14 @@ export const CatalogReducer: Reducer<ICatalogState, ICatalogAction> = (state, ac
return { ...state, clubOffers }; return { ...state, clubOffers };
} }
case CatalogActions.SET_SUBSCRIPTION_INFO: {
const subscriptionInfo = (action.payload.subscriptionInfo || null);
return { ...state, subscriptionInfo };
}
case CatalogActions.RESET_STATE: {
return { ...initialCatalog };
}
default: default:
return state; 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 { ICatalogPageData } from 'nitro-renderer';
import { FC, useEffect } from 'react'; import { FC, useEffect } from 'react';
import { CatalogSearchView } from '../search/CatalogSearchView';
import { CatalogNavigationViewProps } from './CatalogNavigationView.types'; import { CatalogNavigationViewProps } from './CatalogNavigationView.types';
import { CatalogNavigationSetView } from './set/CatalogNavigationSetView'; import { CatalogNavigationSetView } from './set/CatalogNavigationSetView';
@ -17,10 +18,13 @@ export const CatalogNavigationView: FC<CatalogNavigationViewProps> = props =>
}, [ page ]); }, [ page ]);
return ( return (
<>
<CatalogSearchView />
<div className="border border-2 rounded overflow-hidden nitro-catalog-navigation"> <div className="border border-2 rounded overflow-hidden nitro-catalog-navigation">
<div className="navigation-container m-1"> <div className="navigation-container m-1">
<CatalogNavigationSetView page={ page } isFirstSet={ true } /> <CatalogNavigationSetView page={ page } isFirstSet={ true } />
</div> </div>
</div> </div>
</>
); );
} }

View File

@ -13,7 +13,12 @@ export const CatalogNavigationSetView: FC<CatalogNavigationSetViewProps> = props
{ {
if(!isFirstSet || !page || (page.pageId === -1)) return; 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 ]); }, [ page, isFirstSet ]);
useEffect(() => useEffect(() =>

View File

@ -1,3 +1,4 @@
@import './header/CatalogPageHeaderView';
@import './layout/CatalogLayout'; @import './layout/CatalogLayout';
@import './offer/CatalogPageOfferView'; @import './offer/CatalogPageOfferView';
@import './offers/CatalogPageOffersView'; @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 './default/CatalogLayoutDefaultView';
@import './frontpage4/CatalogLayoutFrontpage4View';
@import './pets/CatalogLayoutPetView'; @import './pets/CatalogLayoutPetView';
@import './pets3/CatalogLayoutPets3View';
@import './single-bundle/CatalogLayoutSingleBundleView'; @import './single-bundle/CatalogLayoutSingleBundleView';
@import './spaces-new/CatalogLayoutSpacesView'; @import './spaces-new/CatalogLayoutSpacesView';
@import './trophies/CatalogLayoutTrophiesView'; @import './trophies/CatalogLayoutTrophiesView';

View File

@ -1,6 +1,8 @@
import { ICatalogPageParser, RoomPreviewer } from 'nitro-renderer'; import { ICatalogPageParser, RoomPreviewer } from 'nitro-renderer';
import { CatalogLayoutDefaultView } from './default/CatalogLayoutDefaultView'; import { CatalogLayoutDefaultView } from './default/CatalogLayoutDefaultView';
import { CatalogLayoutFrontpage4View } from './frontpage4/CatalogLayoutFrontpage4View';
import { CatalogLayoutPetView } from './pets/CatalogLayoutPetView'; import { CatalogLayoutPetView } from './pets/CatalogLayoutPetView';
import { CatalogLayoutPets3View } from './pets3/CatalogLayoutPets3View';
import { CatalogLayoutSingleBundleView } from './single-bundle/CatalogLayoutSingleBundleView'; import { CatalogLayoutSingleBundleView } from './single-bundle/CatalogLayoutSingleBundleView';
import { CatalogLayoutSpacesView } from './spaces-new/CatalogLayoutSpacesView'; import { CatalogLayoutSpacesView } from './spaces-new/CatalogLayoutSpacesView';
import { CatalogLayoutTrophiesView } from './trophies/CatalogLayoutTrophiesView'; import { CatalogLayoutTrophiesView } from './trophies/CatalogLayoutTrophiesView';
@ -13,13 +15,13 @@ export function GetCatalogLayout(pageParser: ICatalogPageParser, roomPreviewer:
case 'frontpage_featured': case 'frontpage_featured':
return null; return null;
case 'frontpage4': case 'frontpage4':
return null; return <CatalogLayoutFrontpage4View roomPreviewer={ roomPreviewer } pageParser={ pageParser } />;
case 'pets': case 'pets':
return <CatalogLayoutPetView roomPreviewer={ roomPreviewer } pageParser={ pageParser } />; return <CatalogLayoutPetView roomPreviewer={ roomPreviewer } pageParser={ pageParser } />;
case 'pets2': case 'pets2':
return null; return null;
case 'pets3': case 'pets3':
return null; return <CatalogLayoutPets3View roomPreviewer={ roomPreviewer } pageParser={ pageParser } />;
case 'vip_buy': case 'vip_buy':
return <CatalogLayoutVipBuyView roomPreviewer={ roomPreviewer } pageParser={ pageParser } />; return <CatalogLayoutVipBuyView roomPreviewer={ roomPreviewer } pageParser={ pageParser } />;
case 'guild_frontpage': 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 { RoomPreviewerView } from '../../../../../room-previewer/RoomPreviewerView';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../../context/CatalogContext';
import { CatalogActions } from '../../../../reducers/CatalogReducer'; 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 { CatalogLayoutPetViewProps } from './CatalogLayoutPetView.types';
import { CatalogLayoutPetPurchaseView } from './purchase/CatalogLayoutPetPurchaseView'; import { CatalogLayoutPetPurchaseView } from './purchase/CatalogLayoutPetPurchaseView';
@ -160,6 +160,14 @@ export const CatalogLayoutPetView: FC<CatalogLayoutPetViewProps> = props =>
}) } }) }
</div> </div>
</div> </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"> <div className="position-relative d-flex flex-column col-5">
<RoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 }> <RoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 }>
{ (petIndex > -1 && petIndex <= 7) && { (petIndex > -1 && petIndex <= 7) &&
@ -169,7 +177,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutPetViewProps> = props =>
</RoomPreviewerView> </RoomPreviewerView>
<div className="fs-6 text-black mt-1 overflow-hidden">{ petBreedName }</div> <div className="fs-6 text-black mt-1 overflow-hidden">{ petBreedName }</div>
<CatalogLayoutPetPurchaseView offer={ activeOffer } pageId={ pageParser.pageId } extra={ petPurchaseString } /> <CatalogLayoutPetPurchaseView offer={ activeOffer } pageId={ pageParser.pageId } extra={ petPurchaseString } />
</div> </div> }
</div> </div>
); );
} }

View File

@ -3,8 +3,8 @@ import { FC, useCallback, useState } from 'react';
import { CatalogEvent } from '../../../../../../../events'; import { CatalogEvent } from '../../../../../../../events';
import { useUiEvent } from '../../../../../../../hooks/events/ui/ui-event'; import { useUiEvent } from '../../../../../../../hooks/events/ui/ui-event';
import { SendMessageHook } from '../../../../../../../hooks/messages/message-event'; import { SendMessageHook } from '../../../../../../../hooks/messages/message-event';
import { CurrencyIcon } from '../../../../../../../utils/currency-icon/CurrencyIcon';
import { LocalizeText } from '../../../../../../../utils/LocalizeText'; import { LocalizeText } from '../../../../../../../utils/LocalizeText';
import { CurrencyIcon } from '../../../../../../currency-icon/CurrencyIcon';
import { CatalogPurchaseButtonView } from '../../../purchase/purchase-button/CatalogPurchaseButtonView'; import { CatalogPurchaseButtonView } from '../../../purchase/purchase-button/CatalogPurchaseButtonView';
import { CatalogPetNameApprovalView } from '../name-approval/CatalogPetNameApprovalView'; import { CatalogPetNameApprovalView } from '../name-approval/CatalogPetNameApprovalView';
import { CatalogLayoutPetPurchaseViewProps } from './CatalogLayoutPetPurchaseView.types'; 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 { RoomPreviewerView } from '../../../../../room-previewer/RoomPreviewerView';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../../context/CatalogContext';
import { ProductTypeEnum } from '../../../../enums/ProductTypeEnum'; import { ProductTypeEnum } from '../../../../enums/ProductTypeEnum';
import { GetOfferName } from '../../../../utils/CatalogUtilities'; import { GetCatalogPageImage, GetCatalogPageText, GetOfferName } from '../../../../utils/CatalogUtilities';
import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView'; import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView';
import { CatalogPurchaseView } from '../../purchase/CatalogPurchaseView'; import { CatalogPurchaseView } from '../../purchase/CatalogPurchaseView';
import { CatalogLayoutSpacesViewProps } from './CatalogLayoutSpacesView.types'; import { CatalogLayoutSpacesViewProps } from './CatalogLayoutSpacesView.types';
@ -81,6 +81,13 @@ export const CatalogLayoutSpacesView: FC<CatalogLayoutSpacesViewProps> = props =
</div> </div>
<CatalogPageOffersView offers={ groups[activeGroupIndex] } /> <CatalogPageOffersView offers={ groups[activeGroupIndex] } />
</div> </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 && { product &&
<div className="position-relative d-flex flex-column col"> <div className="position-relative d-flex flex-column col">
<RoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 } /> <RoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 } />

View File

@ -1,16 +1,16 @@
import { CatalogClubOfferData, CatalogRequestVipOffersComposer } from 'nitro-renderer'; 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 { SendMessageHook } from '../../../../../../hooks/messages/message-event';
import { CurrencyIcon } from '../../../../../../utils/currency-icon/CurrencyIcon';
import { LocalizeText } from '../../../../../../utils/LocalizeText'; import { LocalizeText } from '../../../../../../utils/LocalizeText';
import { CurrencyIcon } from '../../../../../currency-icon/CurrencyIcon';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../../context/CatalogContext';
import { GetCatalogPageImage, GetCatalogPageText } from '../../../../utils/CatalogUtilities'; import { GetCatalogPageImage } from '../../../../utils/CatalogUtilities';
import { CatalogLayoutVipBuyViewProps } from './CatalogLayoutVipBuyView.types'; import { CatalogLayoutVipBuyViewProps } from './CatalogLayoutVipBuyView.types';
export const CatalogLayoutVipBuyView: FC<CatalogLayoutVipBuyViewProps> = props => export const CatalogLayoutVipBuyView: FC<CatalogLayoutVipBuyViewProps> = props =>
{ {
const { catalogState = null } = useCatalogContext(); const { catalogState = null } = useCatalogContext();
const { pageParser = null, clubOffers = null } = catalogState; const { pageParser = null, clubOffers = null, subscriptionInfo = null } = catalogState;
useEffect(() => useEffect(() =>
{ {
@ -41,6 +41,17 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutVipBuyViewProps> = props =
return offerText; 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 ( return (
<div className="row h-100 nitro-catalog-layout-vip-buy"> <div className="row h-100 nitro-catalog-layout-vip-buy">
<div className="col-7"> <div className="col-7">
@ -74,7 +85,7 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutVipBuyViewProps> = props =
<div className="d-block mb-2"> <div className="d-block mb-2">
<img alt="" src={ GetCatalogPageImage(pageParser, 1) } /> <img alt="" src={ GetCatalogPageImage(pageParser, 1) } />
</div> </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>
</div> </div>
); );

View File

@ -1,6 +1,6 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { CurrencyIcon } from '../../../../../utils/currency-icon/CurrencyIcon';
import { LocalizeText } from '../../../../../utils/LocalizeText'; import { LocalizeText } from '../../../../../utils/LocalizeText';
import { CurrencyIcon } from '../../../../currency-icon/CurrencyIcon';
import { CatalogPurchaseViewProps } from './CatalogPurchaseView.types'; import { CatalogPurchaseViewProps } from './CatalogPurchaseView.types';
import { CatalogPurchaseButtonView } from './purchase-button/CatalogPurchaseButtonView'; 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'; 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', ''); let url = GetConfiguration<string>('currency.asset.icon.url', '');

View File

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

View File

@ -105,7 +105,11 @@ export const InventoryView: FC<InventoryViewProps> = props =>
<NitroCardTabsView> <NitroCardTabsView>
{ tabs.map((name, index) => { 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> </NitroCardTabsView>
<NitroCardContentView> <NitroCardContentView>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,6 +16,7 @@ import { NavigatorSearchView } from './views/search/NavigatorSearchView';
export const NavigatorView: FC<NavigatorViewProps> = props => export const NavigatorView: FC<NavigatorViewProps> = props =>
{ {
const [ isVisible, setIsVisible ] = useState(false); const [ isVisible, setIsVisible ] = useState(false);
const [ isCreatorOpen, setCreatorOpen ] = useState(false);
const [ navigatorState, dispatchNavigatorState ] = useReducer(NavigatorReducer, initialNavigator); const [ navigatorState, dispatchNavigatorState ] = useReducer(NavigatorReducer, initialNavigator);
const { needsNavigatorUpdate = false, topLevelContext = null, topLevelContexts = null } = navigatorState; const { needsNavigatorUpdate = false, topLevelContext = null, topLevelContexts = null } = navigatorState;
@ -86,8 +87,15 @@ export const NavigatorView: FC<NavigatorViewProps> = props =>
<NitroCardTabsView> <NitroCardTabsView>
{ topLevelContexts.map((context, index) => { 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> </NitroCardTabsView>
<NitroCardContentView> <NitroCardContentView>
<NavigatorSearchView sendSearch={ sendSearch } /> <NavigatorSearchView sendSearch={ sendSearch } />

View File

@ -1,3 +1,4 @@
@import './creator/NavigatorRoomCreatorView';
@import './search/NavigatorSearchView'; @import './search/NavigatorSearchView';
@import './search-result/NavigatorSearchResultView'; @import './search-result/NavigatorSearchResultView';
@import './search-result-item/NavigatorSearchResultItemView'; @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'; import { CurrencyViewProps } from './CurrencyView.types';
export function CurrencyView(props: CurrencyViewProps): JSX.Element 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 { 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 { ProcessRoomObjectOperation } from '../../api/nitro/room/ProcessRoomObjectOperation';
import { SetActiveRoomId } from '../../api/nitro/room/SetActiveRoomId'; import { SetActiveRoomId } from '../../api/nitro/room/SetActiveRoomId';
import { GetRoomSession } from '../../api/nitro/session/GetRoomSession'; import { GetRoomSession } from '../../api/nitro/session/GetRoomSession';
@ -13,7 +13,7 @@ import { RoomHostViewProps } from './RoomHostView.types';
import { CanManipulateFurniture } from './utils/CanManipulateFurniture'; import { CanManipulateFurniture } from './utils/CanManipulateFurniture';
import { IsFurnitureSelectionDisabled } from './utils/IsFurnitureSelectionDisabled'; 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 [ roomSession, setRoomSession ] = useState<IRoomSession>(null);
const [ eventDispatcher, setEventDispatcher ] = useState<IEventDispatcher>(null); const [ eventDispatcher, setEventDispatcher ] = useState<IEventDispatcher>(null);
@ -40,7 +40,7 @@ export function RoomHostView(props: RoomHostViewProps): JSX.Element
const onRoomEngineObjectEvent = useCallback((event: RoomEngineObjectEvent) => const onRoomEngineObjectEvent = useCallback((event: RoomEngineObjectEvent) =>
{ {
if(!eventDispatcher) return; if(!roomSession || !eventDispatcher) return;
const objectId = event.objectId; const objectId = event.objectId;
const category = event.category; const category = event.category;
@ -101,6 +101,10 @@ export function RoomHostView(props: RoomHostViewProps): JSX.Element
case RoomEngineObjectEvent.REQUEST_ROTATE: case RoomEngineObjectEvent.REQUEST_ROTATE:
if(CanManipulateFurniture(roomSession, objectId, category)) ProcessRoomObjectOperation(objectId, category, RoomObjectOperationType.OBJECT_ROTATE_POSITIVE); if(CanManipulateFurniture(roomSession, objectId, category)) ProcessRoomObjectOperation(objectId, category, RoomObjectOperationType.OBJECT_ROTATE_POSITIVE);
break; 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) if(updateEvent)
@ -152,6 +156,7 @@ export function RoomHostView(props: RoomHostViewProps): JSX.Element
useRoomEngineEvent(RoomEngineObjectEvent.PLACED, onRoomEngineObjectEvent); useRoomEngineEvent(RoomEngineObjectEvent.PLACED, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineObjectEvent.REQUEST_MOVE, onRoomEngineObjectEvent); useRoomEngineEvent(RoomEngineObjectEvent.REQUEST_MOVE, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineObjectEvent.REQUEST_ROTATE, onRoomEngineObjectEvent); useRoomEngineEvent(RoomEngineObjectEvent.REQUEST_ROTATE, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineObjectEvent.REQUEST_MANIPULATION, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineObjectEvent.MOUSE_ENTER, onRoomEngineObjectEvent); useRoomEngineEvent(RoomEngineObjectEvent.MOUSE_ENTER, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineObjectEvent.MOUSE_LEAVE, 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 { DispatchTouchEvent } from '../../api/nitro/room/DispatchTouchEvent';
import { GetRoomEngine } from '../../api/nitro/room/GetRoomEngine'; import { GetRoomEngine } from '../../api/nitro/room/GetRoomEngine';
import { RoomViewProps } from './RoomView.types'; import { RoomViewProps } from './RoomView.types';
import { AvatarInfoWidgetView } from './widgets/avatar-info/AvatarInfoWidgetView';
import { ChatInputView } from './widgets/chat-input/ChatInputView'; import { ChatInputView } from './widgets/chat-input/ChatInputView';
import { ChatWidgetView } from './widgets/chat/ChatWidgetView'; import { ChatWidgetView } from './widgets/chat/ChatWidgetView';
import { FurnitureWidgetsView } from './widgets/furniture/FurnitureWidgetsView'; import { FurnitureWidgetsView } from './widgets/furniture/FurnitureWidgetsView';
@ -90,9 +91,10 @@ export function RoomView(props: RoomViewProps): JSX.Element
{ roomSession && events && roomCanvas && { roomSession && events && roomCanvas &&
createPortal(props.children, document.getElementById('room-view').appendChild(roomCanvas)) && createPortal(props.children, document.getElementById('room-view').appendChild(roomCanvas)) &&
<> <>
<AvatarInfoWidgetView events={ events } />
<ChatWidgetView />
<ChatInputView /> <ChatInputView />
<FurnitureWidgetsView events={ events } /> <FurnitureWidgetsView events={ events } />
<ChatWidgetView />
</> } </> }
</div> </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 [ chatMessages, setChatMessages ] = useState<ChatBubbleMessage[]>([]);
const elementRef = useRef<HTMLDivElement>(); const elementRef = useRef<HTMLDivElement>();
const removeLastHiddenChat = useCallback(() => const removeFirstHiddenChat = useCallback(() =>
{ {
if(!chatMessages.length) return; if(!chatMessages.length) return;
const lastChat = chatMessages[chatMessages.length - 1]; const lastChat = chatMessages[0];
if((lastChat.lastTop > -(lastChat.height))) return; if((lastChat.lastTop > (-(lastChat.height) * 2))) return;
setChatMessages(prevValue => setChatMessages(prevValue =>
{ {
const newMessages = [ ...prevValue ]; const newMessages = [ ...prevValue ];
newMessages.splice((newMessages.length - 1), 1); newMessages.shift();
return newMessages; return newMessages;
}); });
@ -33,42 +33,33 @@ export function ChatWidgetView(props: ChatWidgetViewProps): JSX.Element
const moveChatUp = useCallback((chat: ChatBubbleMessage, amount: number) => const moveChatUp = useCallback((chat: ChatBubbleMessage, amount: number) =>
{ {
if(!chat.elementRef) return; chat.lastTop -= amount;
let y = chat.elementRef.offsetHeight; if(chat.elementRef) chat.elementRef.style.top = (chat.lastTop + 'px');
if(amount > 0) y = amount;
let top = (chat.elementRef.offsetTop - y);
chat.lastTop = top;
chat.elementRef.style.top = (top + 'px');
}, []); }, []);
const moveAllChatsUp = useCallback((amount: number) => const moveAllChatsUp = useCallback((amount: number) =>
{ {
chatMessages.forEach(chat => moveChatUp(chat, amount)); 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]; const lowestPoint = ((chat.lastTop + chat.height) - 1);
const requiredSpace = (chat.height + 1);
if(!lastChat) return;
const lowestPoint = ((lastChat.lastTop + lastChat.height) - 1);
const requiredSpace = ((amount || lastChat.height) + 1);
const spaceAvailable = (elementRef.current.offsetHeight - lowestPoint); const spaceAvailable = (elementRef.current.offsetHeight - lowestPoint);
if(spaceAvailable < requiredSpace) if(spaceAvailable < requiredSpace)
{ {
amount = (requiredSpace - spaceAvailable); const amount = (requiredSpace - spaceAvailable);
chatMessages.forEach((chat, index) => chatMessages.forEach((existingChat, index) =>
{ {
if(skipLast && (index === (chatMessages.length - 1))) return; if(existingChat === chat) return;
moveChatUp(chat, amount) moveChatUp(existingChat, amount)
}); });
} }
}, [ chatMessages, moveChatUp ]); }, [ chatMessages, moveChatUp ]);
@ -146,25 +137,25 @@ export function ChatWidgetView(props: ChatWidgetViewProps): JSX.Element
useRoomSessionManagerEvent(RoomSessionChatEvent.CHAT_EVENT, onRoomSessionChatEvent); useRoomSessionManagerEvent(RoomSessionChatEvent.CHAT_EVENT, onRoomSessionChatEvent);
// useEffect(() =>
// {
// const interval = setInterval(() => moveAllChatsUp(15), 500);
// return () =>
// {
// if(interval) clearInterval(interval);
// }
// }, [ chatMessages, moveAllChatsUp ]);
useEffect(() => useEffect(() =>
{ {
const interval = setInterval(() => moveAllChatsUp(15), 500); const interval = setInterval(() => removeFirstHiddenChat(), 1000);
return () => return () =>
{ {
if(interval) clearInterval(interval); if(interval) clearInterval(interval);
} }
}, [ chatMessages, moveAllChatsUp ]); }, [ removeFirstHiddenChat ]);
useEffect(() =>
{
const interval = setInterval(() => removeLastHiddenChat(), 500);
return () =>
{
if(interval) clearInterval(interval);
}
}, [ removeLastHiddenChat ]);
return ( return (
<div ref={ elementRef } className="nitro-chat-widget"> <div ref={ elementRef } className="nitro-chat-widget">

View File

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

View File

@ -10,6 +10,7 @@ export class RoomWidgetRoomObjectUpdateEvent extends RoomWidgetUpdateEvent
public static USER_ADDED: string = 'RWROUE_USER_ADDED'; public static USER_ADDED: string = 'RWROUE_USER_ADDED';
public static OBJECT_ROLL_OVER: string = 'RWROUE_OBJECT_ROLL_OVER'; public static OBJECT_ROLL_OVER: string = 'RWROUE_OBJECT_ROLL_OVER';
public static OBJECT_ROLL_OUT: string = 'RWROUE_OBJECT_ROLL_OUT'; public static OBJECT_ROLL_OUT: string = 'RWROUE_OBJECT_ROLL_OUT';
public static OBJECT_REQUEST_MANIPULATION: string = 'RWROUE_OBJECT_REQUEST_MANIPULATION';
private _id: number; private _id: number;
private _category: number; private _category: number;

View File

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

View File

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

View File

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