Initial commit

This commit is contained in:
MyNameIsBatman 2021-06-28 00:47:55 -03:00
parent 9a94f59844
commit 51a9e34199
18 changed files with 378 additions and 1 deletions

View File

@ -0,0 +1,8 @@
import { NitroEvent } from 'nitro-renderer';
export class AchievementsEvent extends NitroEvent
{
public static SHOW_ACHIEVEMENTS: string = 'AE_SHOW_ACHIEVEMENTS';
public static HIDE_ACHIEVEMENTS: string = 'AE_HIDE_ACHIEVEMENTS';
public static TOGGLE_ACHIEVEMENTS: string = 'AE_TOGGLE_ACHIEVEMENTS';
}

View File

@ -0,0 +1 @@
export * from './AchievementsEvent';

View File

@ -14,3 +14,4 @@
@import './room/RoomView'; @import './room/RoomView';
@import './room-host/RoomHostView'; @import './room-host/RoomHostView';
@import './toolbar/ToolbarView'; @import './toolbar/ToolbarView';
@import './achievements/AchievementsView';

View File

@ -0,0 +1,70 @@
import { AchievementEvent, AchievementsEvent, AchievementsScoreEvent } from 'nitro-renderer';
import { FC, useCallback } from 'react';
import { CreateMessageHook } from '../../hooks/messages';
import { IAchievementsMessageHandlerProps } from './AchievementsMessageHandler.types';
import { useAchievementsContext } from './context/AchievementsContext';
import { AchievementsActions } from './reducers/AchievementsReducer';
import { AchievementCategory } from './utils/AchievementCategory';
export const AchievementsMessageHandler: FC<IAchievementsMessageHandlerProps> = props =>
{
const { achievementsState = null, dispatchAchievementsState = null } = useAchievementsContext();
const onAchievementEvent = useCallback((event: AchievementEvent) =>
{
const parser = event.getParser();
console.log(parser);
}, [ dispatchAchievementsState ]);
const onAchievementsEvent = useCallback((event: AchievementsEvent) =>
{
const parser = event.getParser();
const categories: AchievementCategory[] = [];
for(const achievement of parser.achievements)
{
const categoryName = achievement.category;
const existing = categories.find(category => category.name === categoryName);
if(existing)
{
existing.achievements.push(achievement);
continue;
}
const category = new AchievementCategory(categoryName);
category.achievements.push(achievement);
categories.push(category);
}
dispatchAchievementsState({
type: AchievementsActions.SET_CATEGORIES,
payload: {
categories: categories
}
});
}, [ dispatchAchievementsState ]);
const onAchievementsScoreEvent = useCallback((event: AchievementsScoreEvent) =>
{
const parser = event.getParser();
dispatchAchievementsState({
type: AchievementsActions.SET_SCORE,
payload: {
score: parser.score
}
});
}, [ dispatchAchievementsState ]);
CreateMessageHook(AchievementEvent, onAchievementEvent);
CreateMessageHook(AchievementsEvent, onAchievementsEvent);
CreateMessageHook(AchievementsScoreEvent, onAchievementsScoreEvent);
return null;
};

View File

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

View File

@ -0,0 +1,24 @@
.nitro-achievements {
width: 600px;
.score {
border-color: $grid-border-color !important;
background-color: $grid-bg-color;
}
.category {
border-color: $grid-border-color !important;
background-color: $grid-bg-color;
cursor: pointer;
&.active {
border-color: $grid-active-border-color !important;
background-color: $grid-active-bg-color;
}
.category-score {
margin-top: 43.5px;
}
}
}

View File

@ -0,0 +1,66 @@
import { FC, useCallback, useEffect, useReducer, useState } from 'react';
import { AchievementsEvent } from '../../events/achievements';
import { useUiEvent } from '../../hooks/events';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
import { LocalizeText } from '../../utils/LocalizeText';
import { AchievementsMessageHandler } from './AchievementsMessageHandler';
import { AchievementsViewProps } from './AchievementsView.types';
import { AchievementsContextProvider } from './context/AchievementsContext';
import { AchievementsReducer, initialAchievements } from './reducers/AchievementsReducer';
import { AchievementsListView } from './views/list/AchievementsListView';
export const AchievementsView: FC<AchievementsViewProps> = props =>
{
const [ isVisible, setIsVisible ] = useState(false);
const [ achievementsState, dispatchAchievementsState ] = useReducer(AchievementsReducer, initialAchievements);
const { categories = null, score = null, selectedCategoryName = null } = achievementsState;
const onAchievementsEvent = useCallback((event: AchievementsEvent) =>
{
switch(event.type)
{
case AchievementsEvent.SHOW_ACHIEVEMENTS:
setIsVisible(true);
return;
case AchievementsEvent.HIDE_ACHIEVEMENTS:
setIsVisible(false);
return;
case AchievementsEvent.TOGGLE_ACHIEVEMENTS:
setIsVisible(value => !value);
return;
}
}, []);
useUiEvent(AchievementsEvent.SHOW_ACHIEVEMENTS, onAchievementsEvent);
useUiEvent(AchievementsEvent.HIDE_ACHIEVEMENTS, onAchievementsEvent);
useUiEvent(AchievementsEvent.TOGGLE_ACHIEVEMENTS, onAchievementsEvent);
useEffect(() =>
{
if(!isVisible) return;
}, [ isVisible ]);
return (
<AchievementsContextProvider value={ { achievementsState, dispatchAchievementsState } }>
<AchievementsMessageHandler />
{ isVisible &&
<NitroCardView className="nitro-achievements">
<NitroCardHeaderView headerText={ LocalizeText('inventory.achievements') } onCloseClick={ event => setIsVisible(false) } />
<NitroCardContentView>
<div className="row">
<div className="col-6">
<AchievementsListView />
<div className="score border border-2 text-black text-center rounded">
{ LocalizeText('achievements.categories.score', ['score'], [score.toString()]) }
</div>
</div>
<div className="col-6">
</div>
</div>
</NitroCardContentView>
</NitroCardView> }
</AchievementsContextProvider>
);
};

View File

@ -0,0 +1,2 @@
export class AchievementsViewProps
{}

View File

@ -0,0 +1,14 @@
import { createContext, FC, useContext } from 'react';
import { AchievementsContextProps, IAchievementsContext } from './AchievementsContext.types';
const AchievementsContext = createContext<IAchievementsContext>({
achievementsState: null,
dispatchAchievementsState: null
});
export const AchievementsContextProvider: FC<AchievementsContextProps> = props =>
{
return <AchievementsContext.Provider value={ props.value }>{ props.children }</AchievementsContext.Provider>
}
export const useAchievementsContext = () => useContext(AchievementsContext);

View File

@ -0,0 +1,13 @@
import { Dispatch, ProviderProps } from 'react';
import { IAchievementsAction, IAchievementsState } from '../reducers/AchievementsReducer';
export interface IAchievementsContext
{
achievementsState: IAchievementsState;
dispatchAchievementsState: Dispatch<IAchievementsAction>;
}
export interface AchievementsContextProps extends ProviderProps<IAchievementsContext>
{
}

View File

@ -0,0 +1,63 @@
import { Reducer } from 'react';
import { AchievementCategory } from '../utils/AchievementCategory';
export interface IAchievementsState
{
categories: AchievementCategory[],
score: number,
selectedCategoryName: string
}
export interface IAchievementsAction
{
type: string;
payload: {
categories?: AchievementCategory[],
score?: number,
selectedCategoryName?: string
}
}
export class AchievementsActions
{
public static SET_CATEGORIES: string = 'AA_SET_CATEGORIES';
public static SET_SCORE: string = 'AA_SET_SCORE';
public static SELECT_CATEGORY: string = 'AA_SELECT_CATEGORY';
}
export const initialAchievements: IAchievementsState = {
categories: null,
score: null,
selectedCategoryName: null
}
export const AchievementsReducer: Reducer<IAchievementsState, IAchievementsAction> = (state, action) =>
{
switch(action.type)
{
case AchievementsActions.SET_CATEGORIES: {
const categories = (action.payload.categories || state.categories || null);
let selectedCategoryName = null;
if(categories.length > 0)
{
selectedCategoryName = categories[0].name;
}
return { ...state, categories, selectedCategoryName };
}
case AchievementsActions.SET_SCORE: {
const score = (action.payload.score || state.score || null);
return { ...state, score };
}
case AchievementsActions.SELECT_CATEGORY: {
const selectedCategoryName = (action.payload.selectedCategoryName || state.selectedCategoryName || null);
return { ...state, selectedCategoryName };
}
default:
return state;
}
}

View File

@ -0,0 +1,33 @@
import { AchievementData } from 'nitro-renderer';
export class AchievementCategory
{
private _name: string;
private _achievements: AchievementData[];
constructor(name: string)
{
this._name = name;
this._achievements = [];
}
public get name(): string
{
return this._name;
}
public set name(name: string)
{
this._name = name;
}
public get achievements(): AchievementData[]
{
return this._achievements;
}
public set achievements(achievements: AchievementData[])
{
this._achievements = achievements;
}
}

View File

@ -0,0 +1,2 @@
export class AchievementListViewProps
{}

View File

@ -0,0 +1,70 @@
import classNames from 'classnames';
import { FC, useCallback } from 'react';
import { GetConfiguration } from '../../../../api';
import { useAchievementsContext } from '../../context/AchievementsContext';
import { AchievementsActions } from '../../reducers/AchievementsReducer';
import { AchievementCategory } from '../../utils/AchievementCategory';
import { AchievementListViewProps } from './AchievementListView.types';
export const AchievementsListView: FC<AchievementListViewProps> = props =>
{
const achievementsContext = useAchievementsContext();
const { achievementsState = null, dispatchAchievementsState = null } = achievementsContext;
const { categories = null, selectedCategoryName = null } = achievementsState;
const getCategoryImage = useCallback((category: AchievementCategory) =>
{
let level = 0;
for(const achievement of category.achievements)
{
level = (level + ((achievement.finalLevel) ? achievement.level : (achievement.level - 1)));
}
const isActive = ((level > 0) ? 'active' : 'inactive');
return GetConfiguration('achievements.images.url', GetConfiguration('achievements.images.url') + `quests/achcategory_${category.name}_${isActive}.png`).replace('%image%',`achcategory_${category.name}_${isActive}`);
}, []);
const getCategoryProgress = useCallback((category: AchievementCategory) =>
{
let completed = 0;
let total = 0;
for(const achievement of category.achievements)
{
if(!achievement) continue;
if(achievement.finalLevel) completed = completed + 1 + achievement.level;
total = (total + achievement.scoreLimit);
}
return (completed + ' / ' + total);
}, []);
const selectCategory = useCallback((name: string) =>
{
dispatchAchievementsState({
type: AchievementsActions.SELECT_CATEGORY,
payload: {
selectedCategoryName: name
}
});
}, [ dispatchAchievementsState ]);
return (
<div className="row row-cols-3">
{ categories && categories.map((category, index) =>
{
return <div key={ index } className="col mb-3">
<div className={'category border border-2 rounded d-flex flex-column justify-content-center align-items-center p-2' + classNames({' active': selectedCategoryName === category.name})} onClick={() => selectCategory(category.name)}>
<img alt="" src={getCategoryImage(category)} />
<div className="position-absolute category-score small">{ getCategoryProgress(category) }</div>
</div>
</div>
}) }
</div>
);
};

View File

@ -1,6 +1,7 @@
import { Nitro, RoomSessionEvent } from 'nitro-renderer'; import { Nitro, RoomSessionEvent } from 'nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, 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 { AchievementsView } from '../achievements/AchievementsView';
import { AvatarEditorView } from '../avatar-editor/AvatarEditorView'; import { AvatarEditorView } from '../avatar-editor/AvatarEditorView';
import { CatalogView } from '../catalog/CatalogView'; import { CatalogView } from '../catalog/CatalogView';
import { FriendListView } from '../friend-list/FriendListView'; import { FriendListView } from '../friend-list/FriendListView';
@ -47,6 +48,7 @@ export const MainView: FC<MainViewProps> = props =>
<ToolbarView isInRoom={ !landingViewVisible } /> <ToolbarView isInRoom={ !landingViewVisible } />
<RoomHostView /> <RoomHostView />
<AvatarEditorView /> <AvatarEditorView />
<AchievementsView />
<NavigatorView /> <NavigatorView />
<InventoryView /> <InventoryView />
<CatalogView /> <CatalogView />

View File

@ -2,6 +2,7 @@ import { UserInfoEvent } from 'nitro-renderer/src/nitro/communication/messages/i
import { UserInfoDataParser } from 'nitro-renderer/src/nitro/communication/messages/parser/user/data/UserInfoDataParser'; import { UserInfoDataParser } from 'nitro-renderer/src/nitro/communication/messages/parser/user/data/UserInfoDataParser';
import { FC, useCallback, useState } from 'react'; import { FC, useCallback, useState } from 'react';
import { AvatarEditorEvent, CatalogEvent, FriendListEvent, InventoryEvent, NavigatorEvent, RoomWidgetCameraEvent } from '../../events'; import { AvatarEditorEvent, CatalogEvent, FriendListEvent, InventoryEvent, NavigatorEvent, RoomWidgetCameraEvent } from '../../events';
import { AchievementsEvent } from '../../events/achievements';
import { dispatchUiEvent } from '../../hooks/events/ui/ui-event'; import { dispatchUiEvent } from '../../hooks/events/ui/ui-event';
import { CreateMessageHook } from '../../hooks/messages/message-event'; import { CreateMessageHook } from '../../hooks/messages/message-event';
import { TransitionAnimation } from '../../layout/transitions/TransitionAnimation'; import { TransitionAnimation } from '../../layout/transitions/TransitionAnimation';
@ -51,6 +52,10 @@ export const ToolbarView: FC<ToolbarViewProps> = props =>
dispatchUiEvent(new AvatarEditorEvent(AvatarEditorEvent.TOGGLE_EDITOR)); dispatchUiEvent(new AvatarEditorEvent(AvatarEditorEvent.TOGGLE_EDITOR));
setMeExpanded(false); setMeExpanded(false);
return; return;
case ToolbarViewItems.ACHIEVEMENTS_ITEM:
dispatchUiEvent(new AchievementsEvent(AchievementsEvent.TOGGLE_ACHIEVEMENTS));
setMeExpanded(false);
return;
} }
}, []); }, []);

View File

@ -11,4 +11,5 @@ export class ToolbarViewItems
public static FRIEND_LIST_ITEM: string = 'TVI_FRIEND_LIST_ITEM'; public static FRIEND_LIST_ITEM: string = 'TVI_FRIEND_LIST_ITEM';
public static CLOTHING_ITEM: string = 'TVI_CLOTHING_ITEM'; public static CLOTHING_ITEM: string = 'TVI_CLOTHING_ITEM';
public static CAMERA_ITEM: string = 'TVI_CAMERA_ITEM'; public static CAMERA_ITEM: string = 'TVI_CAMERA_ITEM';
public static ACHIEVEMENTS_ITEM: string = 'TVI_ACHIEVEMENTS_ITEM';
} }

View File

@ -38,7 +38,7 @@ export const ToolbarMeView: FC<ToolbarMeViewProps> = props =>
<div className="navigation-item"> <div className="navigation-item">
<i className="icon icon-me-helper-tool"></i> <i className="icon icon-me-helper-tool"></i>
</div> </div>
<div className="navigation-item"> <div className="navigation-item" onClick={ event => handleToolbarItemClick(ToolbarViewItems.ACHIEVEMENTS_ITEM) }>
<i className="icon icon-me-achievements"></i> <i className="icon icon-me-achievements"></i>
</div> </div>
<div className="navigation-item"> <div className="navigation-item">