This commit is contained in:
Bill 2021-04-20 23:36:39 -04:00
parent 44e06a44b6
commit 26ed7a44c8
37 changed files with 633 additions and 250 deletions

5
package-lock.json generated
View File

@ -2803,6 +2803,11 @@
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
}, },
"animate.css": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz",
"integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ=="
},
"ansi-colors": { "ansi-colors": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",

View File

@ -10,6 +10,7 @@
"@types/node": "^12.20.7", "@types/node": "^12.20.7",
"@types/react": "^17.0.3", "@types/react": "^17.0.3",
"@types/react-dom": "^17.0.3", "@types/react-dom": "^17.0.3",
"animate.css": "^4.1.1",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"immutable": "^4.0.0-rc.12", "immutable": "^4.0.0-rc.12",
"nitro-renderer": "file:../nitro-renderer", "nitro-renderer": "file:../nitro-renderer",

View File

@ -10,35 +10,12 @@
content="Web site created using create-react-app" content="Web site created using create-react-app"
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title> <title>React App</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" class="w-100 h-100"></div> <div id="root" class="w-100 h-100"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script> <script>
var NitroConfig = { var NitroConfig = {
configurationUrl: '/configuration.json', configurationUrl: '/configuration.json',

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

View File

@ -4,7 +4,7 @@ import { DraggableWindowProps } from './DraggableWindow.types';
export function DraggableWindow(props: DraggableWindowProps): JSX.Element export function DraggableWindow(props: DraggableWindowProps): JSX.Element
{ {
return ( return (
<Draggable handle={ props.handle } bounds="parent" { ...props.draggableOptions }> <Draggable handle={ props.handle } { ...props.draggableOptions }>
{ props.children } { props.children }
</Draggable> </Draggable>
); );

View File

@ -1,4 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;500&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;500&display=swap');
@import '../node_modules/animate.css/animate.css';
@import './assets/styles'; @import './assets/styles';
html, html,
@ -26,7 +27,6 @@ $infostand-zindex: 30;
$chat-zindex: 20; $chat-zindex: 20;
$highscore-zindex: 19; $highscore-zindex: 19;
@import './transitions/TransitionStyles';
@import './utils/Styles'; @import './utils/Styles';
@import './App'; @import './App';
@import './views/Styles'; @import './views/Styles';

View File

@ -1,23 +0,0 @@
import { Transition } from 'react-transition-group';
import { FadeTransitionProps } from './FadeTransition.types';
import { getTransitionStyle, transitionTypes } from './TransitionStyles';
export function FadeTransition(props: FadeTransitionProps): JSX.Element
{
const { inProp = false, timeout = 300 } = props;
const style = getTransitionStyle('ease-in-out', timeout);
return (
<Transition in={ inProp } timeout={ timeout }>
{state => (
<div style={{
...style,
...transitionTypes[state]
}}>
{ props.children }
</div>
)}
</Transition>
);
}

View File

@ -1,8 +0,0 @@
import { ReactNode } from 'react';
export interface FadeTransitionProps
{
inProp: boolean;
timeout?: number;
children?: ReactNode;
}

View File

@ -0,0 +1,18 @@
import { Transition } from 'react-transition-group';
import { TransitionAnimationProps } from './TransitionAnimation.types';
import { getTransitionAnimationStyle } from './TransitionAnimationStyles';
export function TransitionAnimation(props: TransitionAnimationProps): JSX.Element
{
const { type = null, inProp = false, timeout = 300, className = null, children = null } = props;
return (
<Transition in={ inProp } timeout={ timeout }>
{state => (
<div className={ className + " animate__animated" } style={ { ...getTransitionAnimationStyle(type, state, timeout) } }>
{ children }
</div>
)}
</Transition>
);
}

View File

@ -0,0 +1,20 @@
import { ReactNode } from 'react';
export interface TransitionAnimationProps
{
type: string;
inProp: boolean;
timeout?: number;
className?: string;
children?: ReactNode;
}
export class TransitionAnimationTypes
{
public static BOUNCE: string = 'bounce';
public static SLIDE_LEFT: string = 'slideLeft';
public static FLIP_X: string = 'flipX';
public static FADE_DOWN: string = 'fadeDown';
public static FADE_UP: string = 'fadeUp';
public static HEAD_SHAKE: string = 'headShake';
}

View File

@ -0,0 +1,98 @@
import { CSSProperties } from 'react';
import { TransitionStatus } from 'react-transition-group';
import { ENTERING, EXITING } from 'react-transition-group/Transition';
import { TransitionAnimationTypes } from './TransitionAnimation.types';
export function getTransitionAnimationStyle(type: string, transition: TransitionStatus, timeout: number = 300): Partial<CSSProperties>
{
switch(type)
{
case TransitionAnimationTypes.BOUNCE:
switch(transition)
{
default:
case ENTERING:
return {
animationName: `bounceIn`,
animationDuration: `${ timeout }ms`
}
case EXITING:
return {
animationName: `bounceOut`,
animationDuration: `${ timeout }ms`
}
}
case TransitionAnimationTypes.SLIDE_LEFT:
switch(transition)
{
default:
case ENTERING:
return {
animationName: `slideInLeft`,
animationDuration: `${ timeout }ms`
}
case EXITING:
return {
animationName: `slideOutLeft`,
animationDuration: `${ timeout }ms`
}
}
case TransitionAnimationTypes.FLIP_X:
switch(transition)
{
default:
case ENTERING:
return {
animationName: `flipInX`,
animationDuration: `${ timeout }ms`
}
case EXITING:
return {
animationName: `flipOutX`,
animationDuration: `${ timeout }ms`
}
}
case TransitionAnimationTypes.FADE_UP:
switch(transition)
{
default:
case ENTERING:
return {
animationName: `fadeInUp`,
animationDuration: `${ timeout }ms`
}
case EXITING:
return {
animationName: `fadeOutDown`,
animationDuration: `${ timeout }ms`
}
}
case TransitionAnimationTypes.FADE_DOWN:
switch(transition)
{
default:
case ENTERING:
return {
animationName: `fadeInDown`,
animationDuration: `${ timeout }ms`
}
case EXITING:
return {
animationName: `fadeOutUp`,
animationDuration: `${ timeout }ms`
}
}
case TransitionAnimationTypes.HEAD_SHAKE:
switch(transition)
{
default:
case ENTERING:
return {
animationName: `headShake`,
animationDuration: `${ timeout }ms`
}
}
}
return null;
}

View File

@ -1,26 +0,0 @@
@keyframes slideDown {
from {
top: -100%;
}
to {
top: 0;
}
}
@keyframes slideUp {
from {
bottom: -100%;
}
to {
bottom: 0;
}
}
@keyframes slideRight {
from {
right: -100%;
}
to {
right: 0;
}
}

View File

@ -1,18 +0,0 @@
export const transitionTypes = {
entering: { opacity: 1 },
entered: { opacity: 1 },
exiting: { opacity: 0 },
exited: { opacity: 0 },
};
export function getTransitionStyle(type: string, timeout: number = 300): Partial<CSSStyleDeclaration>
{
switch(type)
{
case 'ease-in-out':
return {
transition: `opacity ${ timeout }ms ease-in-out`,
opacity: '0'
}
}
}

7
src/utils/ColorUtils.ts Normal file
View File

@ -0,0 +1,7 @@
export class ColorUtils
{
public static makeColorHex(color: string): string
{
return ('#' + color);
}
}

View File

@ -1,7 +1,7 @@
import { Nitro, RoomSessionEvent } from 'nitro-renderer'; import { Nitro, RoomSessionEvent } from 'nitro-renderer';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event'; import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event';
import { FadeTransition } from '../../transitions/FadeTransition'; import { TransitionAnimation } from '../../transitions/TransitionAnimation';
import { HotelView } from '../hotel-view/HotelView'; import { HotelView } from '../hotel-view/HotelView';
import { NavigatorView } from '../navigator/NavigatorView'; import { NavigatorView } from '../navigator/NavigatorView';
import { RightSideView } from '../right-side/RightSideView'; import { RightSideView } from '../right-side/RightSideView';
@ -41,9 +41,9 @@ export function MainView(props: MainViewProps): JSX.Element
<div className="nitro-main"> <div className="nitro-main">
{ landingViewVisible && <HotelView /> } { landingViewVisible && <HotelView /> }
<RoomHostView /> <RoomHostView />
<FadeTransition inProp={ isReady } timeout={ 300 }> <TransitionAnimation className="nitro-toolbar" type={ 'fadeUp' } inProp={ isReady } timeout={ 300 }>
<ToolbarView isInRoom={ !landingViewVisible } /> <ToolbarView isInRoom={ !landingViewVisible } />
</FadeTransition> </TransitionAnimation>
<NavigatorView /> <NavigatorView />
<RightSideView /> <RightSideView />
</div> </div>

View File

@ -0,0 +1,103 @@
import { NavigatorCategoriesComposer, NavigatorMetadataEvent, NavigatorSearchEvent, NavigatorSettingsComposer, RoomDataParser, RoomForwardEvent, RoomInfoComposer, RoomInfoEvent, RoomInfoOwnerEvent, UserInfoEvent } from 'nitro-renderer';
import { useCallback } from 'react';
import { GetRoomSessionManager, GetSessionDataManager } from '../../api';
import { CreateMessageHook, SendMessageHook } from '../../hooks/messages/message-event';
import { NavigatorMessageHandlerProps } from './NavigatorMessageHandler.types';
export function NavigatorMessageHandler(props: NavigatorMessageHandlerProps): JSX.Element
{
const { setTopLevelContext = null, setTopLevelContexts = null, setSearchResults = null } = props;
const onUserInfoEvent = useCallback((event: UserInfoEvent) =>
{
const parser = event.getParser();
SendMessageHook(new NavigatorCategoriesComposer());
SendMessageHook(new NavigatorSettingsComposer());
}, []);
const onRoomForwardEvent = useCallback((event: RoomForwardEvent) =>
{
const parser = event.getParser();
SendMessageHook(new RoomInfoComposer(parser.roomId, false, true));
}, []);
const onRoomInfoOwnerEvent = useCallback((event: RoomInfoOwnerEvent) =>
{
const parser = event.getParser();
SendMessageHook(new RoomInfoComposer(parser.roomId, true, false));
}, []);
const onRoomInfoEvent = useCallback((event: RoomInfoEvent) =>
{
const parser = event.getParser();
if(parser.roomEnter)
{
// this._data.enteredGuestRoom = parser.data;
// this._data.staffPick = parser.data.roomPicker;
// const isCreatedRoom = (this._data.createdRoomId === parser.data.roomId);
// if(!isCreatedRoom && parser.data.displayRoomEntryAd)
// {
// // display ad
// }
// this._data.createdRoomId = 0;
}
else if(parser.roomForward)
{
if((parser.data.ownerName !== GetSessionDataManager().userName) && !parser.isGroupMember)
{
switch(parser.data.doorMode)
{
case RoomDataParser.DOORBELL_STATE:
console.log('open doorbell');
return;
case RoomDataParser.PASSWORD_STATE:
console.log('open password');
return;
}
}
GetRoomSessionManager().createSession(parser.data.roomId);
}
else
{
// this._data.enteredGuestRoom = parser.data;
// this._data.staffPick = parser.data.roomPicker;
}
}, []);
const onNavigatorMetadataEvent = useCallback((event: NavigatorMetadataEvent) =>
{
const parser = event.getParser();
setTopLevelContexts(parser.topLevelContexts);
if(parser.topLevelContexts.length > 0) setTopLevelContext(parser.topLevelContexts[0]);
// clear search
}, [ setTopLevelContext, setTopLevelContexts ]);
const onNavigatorSearchEvent = useCallback((event: NavigatorSearchEvent) =>
{
const parser = event.getParser();
setSearchResults(parser.result.results);
}, [ setSearchResults ]);
CreateMessageHook(new UserInfoEvent(onUserInfoEvent));
CreateMessageHook(new RoomForwardEvent(onRoomForwardEvent));
CreateMessageHook(new RoomInfoOwnerEvent(onRoomInfoOwnerEvent));
CreateMessageHook(new RoomInfoEvent(onRoomInfoEvent));
CreateMessageHook(new NavigatorMetadataEvent(onNavigatorMetadataEvent));
CreateMessageHook(new NavigatorSearchEvent(onNavigatorSearchEvent));
return null;
}

View File

@ -0,0 +1,8 @@
import { NavigatorSearchResultList, NavigatorTopLevelContext } from 'nitro-renderer';
export interface NavigatorMessageHandlerProps
{
setTopLevelContext: (context: NavigatorTopLevelContext) => void;
setTopLevelContexts: (contexts: NavigatorTopLevelContext[]) => void;
setSearchResults: (results: NavigatorSearchResultList[]) => void;
}

View File

@ -1,12 +1,12 @@
import { NavigatorCategoriesComposer, NavigatorInitComposer, NavigatorMetadataEvent, NavigatorSearchComposer, NavigatorSettingsComposer, NavigatorTopLevelContext, RoomDataParser, RoomForwardEvent, RoomInfoComposer, RoomInfoEvent, RoomInfoOwnerEvent, RoomSessionEvent, UserInfoEvent } from 'nitro-renderer'; import { NavigatorInitComposer, NavigatorSearchComposer, NavigatorSearchResultList, NavigatorTopLevelContext, RoomSessionEvent } from 'nitro-renderer';
import { MouseEvent, useCallback, useEffect, useState } from 'react'; import { MouseEvent, useCallback, useEffect, useState } from 'react';
import { GetRoomSessionManager, GetSessionDataManager } from '../../api';
import { NavigatorEvent } from '../../events'; import { NavigatorEvent } from '../../events';
import { DraggableWindow } from '../../hooks/draggable-window/DraggableWindow'; import { DraggableWindow } from '../../hooks/draggable-window/DraggableWindow';
import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event'; import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event';
import { useUiEvent } from '../../hooks/events/ui/ui-event'; import { useUiEvent } from '../../hooks/events/ui/ui-event';
import { CreateMessageHook, SendMessageHook } from '../../hooks/messages/message-event'; import { SendMessageHook } from '../../hooks/messages/message-event';
import { LocalizeText } from '../../utils/LocalizeText'; import { LocalizeText } from '../../utils/LocalizeText';
import { NavigatorMessageHandler } from './NavigatorMessageHandler';
import { NavigatorViewProps } from './NavigatorView.types'; import { NavigatorViewProps } from './NavigatorView.types';
import { NavigatorTabsView } from './tabs/NavigatorTabsView'; import { NavigatorTabsView } from './tabs/NavigatorTabsView';
@ -19,6 +19,7 @@ export function NavigatorView(props: NavigatorViewProps): JSX.Element
const [ topLevelContexts, setTopLevelContexts ] = useState<NavigatorTopLevelContext[]>(null); const [ topLevelContexts, setTopLevelContexts ] = useState<NavigatorTopLevelContext[]>(null);
const [ topLevelContext, setTopLevelContext ] = useState<NavigatorTopLevelContext>(null); const [ topLevelContext, setTopLevelContext ] = useState<NavigatorTopLevelContext>(null);
const [ searchResults, setSearchResults ] = useState<NavigatorSearchResultList[]>(null);
function hideNavigator(event: MouseEvent = null): void function hideNavigator(event: MouseEvent = null): void
{ {
@ -27,83 +28,6 @@ export function NavigatorView(props: NavigatorViewProps): JSX.Element
setIsVisible(false); setIsVisible(false);
} }
const onUserInfoEvent = useCallback((event: UserInfoEvent) =>
{
const parser = event.getParser();
SendMessageHook(new NavigatorCategoriesComposer());
SendMessageHook(new NavigatorSettingsComposer());
}, []);
const onRoomForwardEvent = useCallback((event: RoomForwardEvent) =>
{
const parser = event.getParser();
SendMessageHook(new RoomInfoComposer(parser.roomId, false, true));
}, []);
const onRoomInfoOwnerEvent = useCallback((event: RoomInfoOwnerEvent) =>
{
const parser = event.getParser();
SendMessageHook(new RoomInfoComposer(parser.roomId, true, false));
}, []);
const onRoomInfoEvent = useCallback((event: RoomInfoEvent) =>
{
const parser = event.getParser();
if(parser.roomEnter)
{
// this._data.enteredGuestRoom = parser.data;
// this._data.staffPick = parser.data.roomPicker;
// const isCreatedRoom = (this._data.createdRoomId === parser.data.roomId);
// if(!isCreatedRoom && parser.data.displayRoomEntryAd)
// {
// // display ad
// }
// this._data.createdRoomId = 0;
}
else if(parser.roomForward)
{
if((parser.data.ownerName !== GetSessionDataManager().userName) && !parser.isGroupMember)
{
switch(parser.data.doorMode)
{
case RoomDataParser.DOORBELL_STATE:
console.log('open doorbell');
return;
case RoomDataParser.PASSWORD_STATE:
console.log('open password');
return;
}
}
GetRoomSessionManager().createSession(parser.data.roomId);
}
else
{
// this._data.enteredGuestRoom = parser.data;
// this._data.staffPick = parser.data.roomPicker;
}
}, []);
const onNavigatorMetadataEvent = useCallback((event: NavigatorMetadataEvent) =>
{
const parser = event.getParser();
setTopLevelContexts(parser.topLevelContexts);
if(parser.topLevelContexts.length > 0) setTopLevelContext(parser.topLevelContexts[0]);
// clear search
}, []);
const onNavigatorEvent = useCallback((event: NavigatorEvent) => const onNavigatorEvent = useCallback((event: NavigatorEvent) =>
{ {
switch(event.type) switch(event.type)
@ -131,6 +55,8 @@ export function NavigatorView(props: NavigatorViewProps): JSX.Element
{ {
if(!topLevelContext) return; if(!topLevelContext) return;
setIsSearching(true);
sendSearch(topLevelContext.code, ''); sendSearch(topLevelContext.code, '');
}, [ topLevelContext ]); }, [ topLevelContext ]);
@ -155,30 +81,30 @@ export function NavigatorView(props: NavigatorViewProps): JSX.Element
} }
}, [ isVisible, isLoaded, search ]); }, [ isVisible, isLoaded, search ]);
useEffect(() =>
{
setIsSearching(false);
}, [ searchResults ]);
useUiEvent(NavigatorEvent.SHOW_NAVIGATOR, onNavigatorEvent); useUiEvent(NavigatorEvent.SHOW_NAVIGATOR, onNavigatorEvent);
useUiEvent(NavigatorEvent.TOGGLE_NAVIGATOR, onNavigatorEvent); useUiEvent(NavigatorEvent.TOGGLE_NAVIGATOR, onNavigatorEvent);
useRoomSessionManagerEvent(RoomSessionEvent.CREATED, onRoomSessionEvent); useRoomSessionManagerEvent(RoomSessionEvent.CREATED, onRoomSessionEvent);
CreateMessageHook(new UserInfoEvent(onUserInfoEvent));
CreateMessageHook(new RoomForwardEvent(onRoomForwardEvent));
CreateMessageHook(new RoomInfoOwnerEvent(onRoomInfoOwnerEvent));
CreateMessageHook(new RoomInfoEvent(onRoomInfoEvent));
CreateMessageHook(new NavigatorMetadataEvent(onNavigatorMetadataEvent));
if(!isVisible) return null;
return ( return (
<DraggableWindow handle=".drag-handler"> <>
<div className="nitro-navigator d-flex flex-column bg-primary border border-black shadow rounded"> <NavigatorMessageHandler setTopLevelContext={ setTopLevelContext } setTopLevelContexts={ setTopLevelContexts } setSearchResults={ setSearchResults } />
<div className="drag-handler d-flex justify-content-between align-items-center px-3 pt-3"> { isVisible && <DraggableWindow handle=".drag-handler">
<div className="h6 m-0">{ LocalizeText(isLoading ? 'navigator.title.is.busy' : 'navigator.title') }</div> <div className="nitro-navigator d-flex flex-column bg-primary border border-black shadow rounded">
<button type="button" className="close" onClick={ hideNavigator }> <div className="drag-handler d-flex justify-content-between align-items-center px-3 pt-3">
<i className="fas fa-times"></i> <div className="h6 m-0">{ LocalizeText((isLoading || isSearching) ? 'navigator.title.is.busy' : 'navigator.title') }</div>
</button> <button type="button" className="close" onClick={ hideNavigator }>
<i className="fas fa-times"></i>
</button>
</div>
<NavigatorTabsView topLevelContext={ topLevelContext } topLevelContexts={ topLevelContexts } setTopLevelContext={ setTopLevelContext } />
</div> </div>
<NavigatorTabsView topLevelContext={ topLevelContext } topLevelContexts={ topLevelContexts } setTopLevelContext={ setTopLevelContext } /> </DraggableWindow> }
</div> </>
</DraggableWindow>
); );
} }

View File

@ -1,44 +1,69 @@
import { Map } from 'immutable';
import { Nitro, UserCreditsEvent, UserCurrencyComposer, UserCurrencyEvent, UserCurrencyUpdateEvent } from 'nitro-renderer'; import { Nitro, UserCreditsEvent, UserCurrencyComposer, UserCurrencyEvent, UserCurrencyUpdateEvent } from 'nitro-renderer';
import { useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { CreateMessageHook, SendMessageHook } from '../../hooks/messages/message-event'; import { CreateMessageHook, SendMessageHook } from '../../hooks/messages/message-event';
import { FadeTransition } from '../../transitions/FadeTransition'; import { TransitionAnimation } from '../../transitions/TransitionAnimation';
import { TransitionAnimationTypes } from '../../transitions/TransitionAnimation.types';
import { CurrencySet } from './currency/CurrencySet';
import { CurrencyView } from './currency/CurrencyView'; import { CurrencyView } from './currency/CurrencyView';
import { PurseViewProps } from './PurseView.types'; import { PurseViewProps } from './PurseView.types';
export function PurseView(props: PurseViewProps): JSX.Element export function PurseView(props: PurseViewProps): JSX.Element
{ {
const [ currencies, setCurrencies ] = useState(Map({ '-1': 0 })); const [ currencies, setCurrencies ] = useState<CurrencySet[]>([ new CurrencySet(-1, 0) ]);
const [ isReady, setIsReady ] = useState(false); const [ isReady, setIsReady ] = useState(false);
const onUserCreditsEvent = (event: UserCreditsEvent) => const displayedCurrencies = Nitro.instance.getConfiguration<number[]>('system.currency.types', []);
const onUserCreditsEvent = useCallback((event: UserCreditsEvent) =>
{ {
const parser = event.getParser(); const parser = event.getParser();
setCurrencies(currencies.set('-1', parseFloat(parser.credits))); updateCurrency(-1, parseFloat(parser.credits));
}; }, []);
const onUserCurrencyEvent = (event: UserCurrencyEvent) => const onUserCurrencyEvent = useCallback((event: UserCurrencyEvent) =>
{ {
const parser = event.getParser(); const parser = event.getParser();
const map = {}; for(const [ key, value ] of parser.currencies.entries()) updateCurrency(key, value);
for(const [ key, value ] of parser.currencies.entries())
{
map[key.toString()] = value;
}
setCurrencies(currencies.merge(map));
setIsReady(true); setIsReady(true);
}; }, []);
const onUserCurrencyUpdateEvent = (event: UserCurrencyUpdateEvent) => const onUserCurrencyUpdateEvent = useCallback((event: UserCurrencyUpdateEvent) =>
{ {
const parser = event.getParser(); const parser = event.getParser();
setCurrencies(currencies.set(parser.type.toString(), parser.amount)); updateCurrency(parser.type, parser.amount)
}; }, []);
function updateCurrency(type: number, amount: number): void
{
setCurrencies(oldState =>
{
const newState: CurrencySet[] = [];
let found = false;
for(const set of oldState)
{
if(set.type !== type)
{
newState.push(set);
continue;
}
newState.push(new CurrencySet(set.type, amount));
found = true;
}
if(!found) newState.push(new CurrencySet(type, amount));
return newState;
});
}
CreateMessageHook(new UserCreditsEvent(onUserCreditsEvent)); CreateMessageHook(new UserCreditsEvent(onUserCreditsEvent));
CreateMessageHook(new UserCurrencyEvent(onUserCurrencyEvent)); CreateMessageHook(new UserCurrencyEvent(onUserCurrencyEvent));
@ -49,20 +74,16 @@ export function PurseView(props: PurseViewProps): JSX.Element
SendMessageHook(new UserCurrencyComposer()); SendMessageHook(new UserCurrencyComposer());
}, []); }, []);
const displayedCurrencies = Nitro.instance.getConfiguration<number[]>('system.currency.types', []);
return ( return (
<FadeTransition inProp={ isReady } timeout={ 300 }> <TransitionAnimation className="nitro-purse position-relative mb-1" type={ TransitionAnimationTypes.FADE_DOWN } inProp={ isReady } timeout={ 300 }>
<div className="nitro-purse position-relative mb-1"> <div className="row px-0 mx-0">
<div className="row px-0 mx-0"> { currencies && currencies.map((set, index) =>
{ currencies && currencies.entrySeq().map(([ key, value ]) => {
{ if(displayedCurrencies.indexOf(set.type) === -1) return null;
if(displayedCurrencies.indexOf(parseInt(key)) === -1) return null;
return <CurrencyView key={ key } type={ parseInt(key) } amount={ value } /> return <CurrencyView key={ index } currencySet={ set } />
}) } }) }
</div>
</div> </div>
</FadeTransition> </TransitionAnimation>
); );
} }

View File

@ -0,0 +1,9 @@
export class CurrencySet
{
constructor(
public type: number,
public amount: number)
{
}
}

View File

@ -1,14 +1,27 @@
import { useEffect, useState } from 'react';
import { TransitionAnimation } from '../../../transitions/TransitionAnimation';
import { TransitionAnimationTypes } from '../../../transitions/TransitionAnimation.types';
import { CurrencyIcon } from '../../../utils/currency-icon/CurrencyIcon'; import { CurrencyIcon } from '../../../utils/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
{ {
const { type = -1, amount = 0 } = props; const { currencySet = null } = props;
const [ isAnimating, setIsAnimating ] = useState(false);
useEffect(() =>
{
setIsAnimating(true);
const timeout = setTimeout(() => setIsAnimating(false), 1000);
return () => clearTimeout(timeout);
}, []);
return ( return (
<div className="d-flex bg-primary rounded shadow border border-black mb-1 p-1 nitro-currency"> <TransitionAnimation className="d-flex bg-primary rounded shadow border border-black mb-1 p-1 nitro-currency" type={ TransitionAnimationTypes.HEAD_SHAKE } inProp={ isAnimating } timeout={ 300 }>
<div className="d-flex flex-grow-1 align-items-center justify-content-end">{ amount }</div> <div className="d-flex flex-grow-1 align-items-center justify-content-end">{ currencySet.amount }</div>
<div className="bg-secondary rounded ml-1"><CurrencyIcon type={ type } /></div> <div className="bg-secondary rounded ml-1"><CurrencyIcon type={ currencySet.type } /></div>
</div> </TransitionAnimation>
); );
} }

View File

@ -1,5 +1,6 @@
import { CurrencySet } from './CurrencySet';
export interface CurrencyViewProps export interface CurrencyViewProps
{ {
type: number; currencySet: CurrencySet;
amount: number;
} }

View File

@ -10,3 +10,5 @@
} }
} }
} }
@import './widgets/Widgets';

View File

@ -0,0 +1 @@
@import './furniture/FurnitureWidgets';

View File

@ -0,0 +1 @@
@import './stickie/FurnitureStickieView';

View File

@ -0,0 +1,9 @@
export class FurnitureStickieData
{
constructor(
public objectId: number,
public category: number,
public color: string,
public text: string,
public canModify: boolean) {}
}

View File

@ -0,0 +1,11 @@
export const STICKIE_COLORS = ['9CCEFF','FF9CFF', '9CFF9C','FFFF33'];
export const STICKIE_COLOR_NAMES = [ 'blue', 'pink', 'green', 'yellow' ];
export function getStickieColorName(color: string): string
{
let index = STICKIE_COLORS.indexOf(color);
if(index === -1) index = 0;
return STICKIE_COLOR_NAMES[index];
}

View File

@ -0,0 +1,91 @@
.nitro-stickie {
width: 185px;
height: 178px;
padding: 1px;
.stickie-header {
width: 183px;
height: 18px;
padding: 0 7px;
.header-trash,
.header-close {
cursor: pointer;
}
.stickie-color {
width: 10px;
height: 10px;
cursor: pointer;
}
}
.stickie-context {
width: 183px;
height: 145px;
padding: 2px 7px;
font-size: 12px;
color: $black;
.context-text {
width: 100%;
height: 100%;
overflow-wrap: break-word;
}
textarea {
background: transparent;
border: 0;
outline: none;
box-shadow: none;
resize: none;
&:active {
border: 0;
outline: none;
box-shadow: none;
}
}
}
}
.nitro-stickie-image {
background-image: url('../../../../../assets/images/room-widgets/stickie-widget/stickie-spritesheet.png');
&.stickie-blue,
&.stickie-yellow,
&.stickie-green,
&.stickie-pink {
width: 185px;
height: 178px;
}
&.stickie-blue {
background-position: -2px -2px;
}
&.stickie-yellow {
background-image: url('../../../../../assets/images/room-widgets/stickie-widget/stickie-yellow.png');
//background-position: -191px -184px;
}
&.stickie-green {
background-position: -191px -2px;
}
&.stickie-pink {
background-position: -2px -184px;
}
&.stickie-close {
width: 10px;
height: 10px;
background-position: -2px -366px;
}
&.stickie-trash {
width: 9px;
height: 10px;
background-position: -16px -366px;
}
}

View File

@ -1,22 +1,158 @@
import { RoomEngineObjectEvent, RoomEngineTriggerWidgetEvent } from 'nitro-renderer'; import { MouseEventType, NitroEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from 'nitro-renderer';
import { useState } from 'react'; import { createRef, useCallback, useEffect, useState } from 'react';
import { GetRoomEngine, GetRoomSession, GetSessionDataManager } from '../../../../../api';
import { DraggableWindow } from '../../../../../hooks/draggable-window/DraggableWindow';
import { CreateEventDispatcherHook } from '../../../../../hooks/events/event-dispatcher.base';
import { useRoomEngineEvent } from '../../../../../hooks/events/nitro/room/room-engine-event'; import { useRoomEngineEvent } from '../../../../../hooks/events/nitro/room/room-engine-event';
import { ColorUtils } from '../../../../../utils/ColorUtils';
import { RoomWidgetRoomObjectUpdateEvent } from '../../events';
import { FurnitureStickieData } from './FurnitureStickieData';
import { getStickieColorName, STICKIE_COLORS } from './FurnitureStickieUtils';
import { FurnitureStickieViewProps } from './FurnitureStickieView.types'; import { FurnitureStickieViewProps } from './FurnitureStickieView.types';
export function FurnitureStickieView(props: FurnitureStickieViewProps): JSX.Element export function FurnitureStickieView(props: FurnitureStickieViewProps): JSX.Element
{ {
const [ isVisible, setIsVisible ] = useState(false); const { events = null } = props;
const onRoomEngineObjectEvent = (event: RoomEngineObjectEvent) => const [ stickieData, setStickieData ] = useState<FurnitureStickieData>(null);
const [ isEditing, setIsEditing ] = useState(false);
const textAreaRef = createRef<HTMLTextAreaElement>();
const onNitroEvent = useCallback((event: NitroEvent) =>
{ {
setIsVisible(true); switch(event.type)
}; {
case RoomEngineTriggerWidgetEvent.REQUEST_STICKIE: {
const widgetEvent = (event as RoomEngineTriggerWidgetEvent);
useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_STICKIE, onRoomEngineObjectEvent); const roomObject = GetRoomEngine().getRoomObject(widgetEvent.roomId, widgetEvent.objectId, widgetEvent.category);
if(!isVisible) return null; if(!roomObject) return;
const data = roomObject.model.getValue<string>(RoomObjectVariable.FURNITURE_ITEMDATA);
if(data.length < 6) return;
let color: string = null;
let text: string = null;
if(data.indexOf(' ') > 0)
{
color = data.slice(0, data.indexOf(' '));
text = data.slice((data.indexOf(' ') + 1), data.length);
}
else
{
color = data;
}
setStickieData(new FurnitureStickieData(widgetEvent.objectId, widgetEvent.category, color, text, (GetRoomSession(widgetEvent.roomId).isRoomOwner || GetSessionDataManager().isModerator)));
setIsEditing(false);
return;
}
case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: {
const widgetEvent = (event as RoomWidgetRoomObjectUpdateEvent);
setIsEditing(false);
setStickieData(prevState =>
{
if(!prevState || (widgetEvent.id !== prevState.objectId) || (widgetEvent.category !== prevState.category)) return prevState;
return null;
});
return;
}
}
}, []);
const processAction = useCallback((type: string, value: string = null) =>
{
switch(type)
{
case 'close':
setStickieData(null);
return;
case 'trash':
setStickieData(prevState =>
{
if(!prevState) return null;
GetRoomEngine().deleteRoomObject(prevState.objectId, prevState.category);
return null;
});
return;
case 'changeColor':
setStickieData(prevState =>
{
const newStickieData = new FurnitureStickieData(prevState.objectId, prevState.category, value, prevState.text, prevState.canModify);
GetRoomEngine().modifyRoomObjectData(newStickieData.objectId, newStickieData.category, newStickieData.color, newStickieData.text);
return newStickieData;
});
return;
case 'changeText': {
setIsEditing(false);
setStickieData(prevState =>
{
const newStickieData = new FurnitureStickieData(prevState.objectId, prevState.category, prevState.color, value, prevState.canModify);
GetRoomEngine().modifyRoomObjectData(newStickieData.objectId, newStickieData.category, newStickieData.color, newStickieData.text);
return newStickieData;
});
return;
}
}
}, []);
useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_STICKIE, onNitroEvent);
CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, events, onNitroEvent);
useEffect(() =>
{
if(!isEditing) return;
const event = (mouseEvent: MouseEvent) =>
{
if(mouseEvent.target === textAreaRef.current) return;
console.log('change it');
processAction('changeText', textAreaRef.current.value);
}
document.addEventListener(MouseEventType.MOUSE_DOWN, event);
return () => document.removeEventListener(MouseEventType.MOUSE_DOWN, event);
}, [ isEditing, textAreaRef, processAction ]);
if(!stickieData) return null;
console.log('rerender stickie')
return ( return (
<div>stickie is visible</div> <DraggableWindow handle=".drag-handler">
<div className={ "nitro-stickie nitro-stickie-image stickie-" + getStickieColorName(stickieData.color) }>
<div className="d-flex align-items-center stickie-header drag-handler">
<div className="d-flex align-items-center flex-grow-1 h-100">
{ stickieData.canModify &&
<>
<div className="nitro-stickie-image stickie-trash header-trash" onClick={ event => processAction('trash') }></div>
{ STICKIE_COLORS.map((color, index) =>
{
return <div className="stickie-color ml-1" key={ index } onClick={ event => processAction('changeColor', color) } style={ {backgroundColor: ColorUtils.makeColorHex(color) } } />
})}
</> }
</div>
<div className="d-flex align-items-center nitro-stickie-image stickie-close header-close" onClick={ event => processAction('close') }></div>
</div>
<div className="stickie-context">
{ !isEditing ? <div className="context-text" onClick={ event => stickieData.canModify && setIsEditing(true) }>{ stickieData.text }</div> : <textarea className="context-text" ref={ textAreaRef } defaultValue={ stickieData.text || '' } autoFocus></textarea> }
</div>
</div>
</DraggableWindow>
); );
} }

View File

@ -51,7 +51,7 @@ export function ToolbarView(props: ToolbarViewProps): JSX.Element
CreateMessageHook(new UserInfoEvent(onUserInfoEvent)); CreateMessageHook(new UserInfoEvent(onUserInfoEvent));
return ( return (
<div className="nitro-toolbar"> <>
<div className="card p-0 overflow-hidden"> <div className="card p-0 overflow-hidden">
<ul className="list-group list-group-horizontal p-1"> <ul className="list-group list-group-horizontal p-1">
{ isInRoom && ( { isInRoom && (
@ -88,6 +88,6 @@ export function ToolbarView(props: ToolbarViewProps): JSX.Element
</li> </li>
</ul> </ul>
</div> </div>
</div> </>
); );
} }