From 51a9e34199981c7d8d72cafc25f191e16e1c423d Mon Sep 17 00:00:00 2001 From: MyNameIsBatman Date: Mon, 28 Jun 2021 00:47:55 -0300 Subject: [PATCH] Initial commit --- src/events/achievements/AchievementsEvent.ts | 8 +++ src/events/achievements/index.ts | 1 + src/views/Styles.scss | 1 + .../AchievementsMessageHandler.tsx | 70 +++++++++++++++++++ .../AchievementsMessageHandler.types.ts | 2 + src/views/achievements/AchievementsView.scss | 24 +++++++ src/views/achievements/AchievementsView.tsx | 66 +++++++++++++++++ .../achievements/AchievementsView.types.ts | 2 + .../context/AchievementsContext.tsx | 14 ++++ .../context/AchievementsContext.types.ts | 13 ++++ .../reducers/AchievementsReducer.tsx | 63 +++++++++++++++++ .../achievements/utils/AchievementCategory.ts | 33 +++++++++ .../views/list/AchievementListView.types.ts | 2 + .../views/list/AchievementsListView.tsx | 70 +++++++++++++++++++ src/views/main/MainView.tsx | 2 + src/views/toolbar/ToolbarView.tsx | 5 ++ src/views/toolbar/ToolbarView.types.ts | 1 + src/views/toolbar/me/ToolbarMeView.tsx | 2 +- 18 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 src/events/achievements/AchievementsEvent.ts create mode 100644 src/events/achievements/index.ts create mode 100644 src/views/achievements/AchievementsMessageHandler.tsx create mode 100644 src/views/achievements/AchievementsMessageHandler.types.ts create mode 100644 src/views/achievements/AchievementsView.scss create mode 100644 src/views/achievements/AchievementsView.tsx create mode 100644 src/views/achievements/AchievementsView.types.ts create mode 100644 src/views/achievements/context/AchievementsContext.tsx create mode 100644 src/views/achievements/context/AchievementsContext.types.ts create mode 100644 src/views/achievements/reducers/AchievementsReducer.tsx create mode 100644 src/views/achievements/utils/AchievementCategory.ts create mode 100644 src/views/achievements/views/list/AchievementListView.types.ts create mode 100644 src/views/achievements/views/list/AchievementsListView.tsx diff --git a/src/events/achievements/AchievementsEvent.ts b/src/events/achievements/AchievementsEvent.ts new file mode 100644 index 00000000..79733bae --- /dev/null +++ b/src/events/achievements/AchievementsEvent.ts @@ -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'; +} diff --git a/src/events/achievements/index.ts b/src/events/achievements/index.ts new file mode 100644 index 00000000..5f03e568 --- /dev/null +++ b/src/events/achievements/index.ts @@ -0,0 +1 @@ +export * from './AchievementsEvent'; diff --git a/src/views/Styles.scss b/src/views/Styles.scss index c6117f4c..cba0a476 100644 --- a/src/views/Styles.scss +++ b/src/views/Styles.scss @@ -14,3 +14,4 @@ @import './room/RoomView'; @import './room-host/RoomHostView'; @import './toolbar/ToolbarView'; +@import './achievements/AchievementsView'; diff --git a/src/views/achievements/AchievementsMessageHandler.tsx b/src/views/achievements/AchievementsMessageHandler.tsx new file mode 100644 index 00000000..d29009f0 --- /dev/null +++ b/src/views/achievements/AchievementsMessageHandler.tsx @@ -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 = 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; +}; diff --git a/src/views/achievements/AchievementsMessageHandler.types.ts b/src/views/achievements/AchievementsMessageHandler.types.ts new file mode 100644 index 00000000..3de1326b --- /dev/null +++ b/src/views/achievements/AchievementsMessageHandler.types.ts @@ -0,0 +1,2 @@ +export interface IAchievementsMessageHandlerProps +{} diff --git a/src/views/achievements/AchievementsView.scss b/src/views/achievements/AchievementsView.scss new file mode 100644 index 00000000..c3901243 --- /dev/null +++ b/src/views/achievements/AchievementsView.scss @@ -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; + } + } + +} diff --git a/src/views/achievements/AchievementsView.tsx b/src/views/achievements/AchievementsView.tsx new file mode 100644 index 00000000..7741941d --- /dev/null +++ b/src/views/achievements/AchievementsView.tsx @@ -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 = 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 ( + + + { isVisible && + + setIsVisible(false) } /> + +
+
+ +
+ { LocalizeText('achievements.categories.score', ['score'], [score.toString()]) } +
+
+
+ +
+
+
+
} +
+ ); +}; diff --git a/src/views/achievements/AchievementsView.types.ts b/src/views/achievements/AchievementsView.types.ts new file mode 100644 index 00000000..821d5c20 --- /dev/null +++ b/src/views/achievements/AchievementsView.types.ts @@ -0,0 +1,2 @@ +export class AchievementsViewProps +{} diff --git a/src/views/achievements/context/AchievementsContext.tsx b/src/views/achievements/context/AchievementsContext.tsx new file mode 100644 index 00000000..c1162f4d --- /dev/null +++ b/src/views/achievements/context/AchievementsContext.tsx @@ -0,0 +1,14 @@ +import { createContext, FC, useContext } from 'react'; +import { AchievementsContextProps, IAchievementsContext } from './AchievementsContext.types'; + +const AchievementsContext = createContext({ + achievementsState: null, + dispatchAchievementsState: null +}); + +export const AchievementsContextProvider: FC = props => +{ + return { props.children } +} + +export const useAchievementsContext = () => useContext(AchievementsContext); diff --git a/src/views/achievements/context/AchievementsContext.types.ts b/src/views/achievements/context/AchievementsContext.types.ts new file mode 100644 index 00000000..320cd787 --- /dev/null +++ b/src/views/achievements/context/AchievementsContext.types.ts @@ -0,0 +1,13 @@ +import { Dispatch, ProviderProps } from 'react'; +import { IAchievementsAction, IAchievementsState } from '../reducers/AchievementsReducer'; + +export interface IAchievementsContext +{ + achievementsState: IAchievementsState; + dispatchAchievementsState: Dispatch; +} + +export interface AchievementsContextProps extends ProviderProps +{ + +} diff --git a/src/views/achievements/reducers/AchievementsReducer.tsx b/src/views/achievements/reducers/AchievementsReducer.tsx new file mode 100644 index 00000000..4b4ddbf1 --- /dev/null +++ b/src/views/achievements/reducers/AchievementsReducer.tsx @@ -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 = (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; + } +} diff --git a/src/views/achievements/utils/AchievementCategory.ts b/src/views/achievements/utils/AchievementCategory.ts new file mode 100644 index 00000000..bcf4f7f6 --- /dev/null +++ b/src/views/achievements/utils/AchievementCategory.ts @@ -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; + } +} diff --git a/src/views/achievements/views/list/AchievementListView.types.ts b/src/views/achievements/views/list/AchievementListView.types.ts new file mode 100644 index 00000000..047bd157 --- /dev/null +++ b/src/views/achievements/views/list/AchievementListView.types.ts @@ -0,0 +1,2 @@ +export class AchievementListViewProps +{} diff --git a/src/views/achievements/views/list/AchievementsListView.tsx b/src/views/achievements/views/list/AchievementsListView.tsx new file mode 100644 index 00000000..a2615ab8 --- /dev/null +++ b/src/views/achievements/views/list/AchievementsListView.tsx @@ -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 = 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 ( +
+ { categories && categories.map((category, index) => + { + return
+
selectCategory(category.name)}> + +
{ getCategoryProgress(category) }
+
+
+ }) } +
+ ); +}; diff --git a/src/views/main/MainView.tsx b/src/views/main/MainView.tsx index 3d06d83d..8b8a2346 100644 --- a/src/views/main/MainView.tsx +++ b/src/views/main/MainView.tsx @@ -1,6 +1,7 @@ import { Nitro, RoomSessionEvent } from 'nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event'; +import { AchievementsView } from '../achievements/AchievementsView'; import { AvatarEditorView } from '../avatar-editor/AvatarEditorView'; import { CatalogView } from '../catalog/CatalogView'; import { FriendListView } from '../friend-list/FriendListView'; @@ -47,6 +48,7 @@ export const MainView: FC = props => + diff --git a/src/views/toolbar/ToolbarView.tsx b/src/views/toolbar/ToolbarView.tsx index ac6e325d..cb932c9e 100644 --- a/src/views/toolbar/ToolbarView.tsx +++ b/src/views/toolbar/ToolbarView.tsx @@ -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 { FC, useCallback, useState } from 'react'; import { AvatarEditorEvent, CatalogEvent, FriendListEvent, InventoryEvent, NavigatorEvent, RoomWidgetCameraEvent } from '../../events'; +import { AchievementsEvent } from '../../events/achievements'; import { dispatchUiEvent } from '../../hooks/events/ui/ui-event'; import { CreateMessageHook } from '../../hooks/messages/message-event'; import { TransitionAnimation } from '../../layout/transitions/TransitionAnimation'; @@ -51,6 +52,10 @@ export const ToolbarView: FC = props => dispatchUiEvent(new AvatarEditorEvent(AvatarEditorEvent.TOGGLE_EDITOR)); setMeExpanded(false); return; + case ToolbarViewItems.ACHIEVEMENTS_ITEM: + dispatchUiEvent(new AchievementsEvent(AchievementsEvent.TOGGLE_ACHIEVEMENTS)); + setMeExpanded(false); + return; } }, []); diff --git a/src/views/toolbar/ToolbarView.types.ts b/src/views/toolbar/ToolbarView.types.ts index 6c280e6c..c8ec4010 100644 --- a/src/views/toolbar/ToolbarView.types.ts +++ b/src/views/toolbar/ToolbarView.types.ts @@ -11,4 +11,5 @@ export class ToolbarViewItems public static FRIEND_LIST_ITEM: string = 'TVI_FRIEND_LIST_ITEM'; public static CLOTHING_ITEM: string = 'TVI_CLOTHING_ITEM'; public static CAMERA_ITEM: string = 'TVI_CAMERA_ITEM'; + public static ACHIEVEMENTS_ITEM: string = 'TVI_ACHIEVEMENTS_ITEM'; } diff --git a/src/views/toolbar/me/ToolbarMeView.tsx b/src/views/toolbar/me/ToolbarMeView.tsx index 31cc71ab..e77e7489 100644 --- a/src/views/toolbar/me/ToolbarMeView.tsx +++ b/src/views/toolbar/me/ToolbarMeView.tsx @@ -38,7 +38,7 @@ export const ToolbarMeView: FC = props =>
-
+
handleToolbarItemClick(ToolbarViewItems.ACHIEVEMENTS_ITEM) }>