diff --git a/src/events/achievements/AchievementsUIUnseenCountEvent.ts b/src/events/achievements/AchievementsUIUnseenCountEvent.ts new file mode 100644 index 00000000..330e991c --- /dev/null +++ b/src/events/achievements/AchievementsUIUnseenCountEvent.ts @@ -0,0 +1,20 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class AchievementsUIUnseenCountEvent extends NitroEvent +{ + public static UNSEEN_COUNT: string = 'AUUCE_UNSEEN_COUNT'; + + private _count: number; + + constructor(count: number) + { + super(AchievementsUIUnseenCountEvent.UNSEEN_COUNT); + + this._count = count; + } + + public get count(): number + { + return this._count; + } +} diff --git a/src/events/achievements/index.ts b/src/events/achievements/index.ts index 264ce6ad..a8c548f4 100644 --- a/src/events/achievements/index.ts +++ b/src/events/achievements/index.ts @@ -1 +1,2 @@ export * from './AchievementsUIEvent'; +export * from './AchievementsUIUnseenCountEvent'; diff --git a/src/views/achievements/AchievementsMessageHandler.tsx b/src/views/achievements/AchievementsMessageHandler.tsx deleted file mode 100644 index d9e941a6..00000000 --- a/src/views/achievements/AchievementsMessageHandler.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { AchievementEvent, AchievementsEvent, AchievementsScoreEvent } from '@nitrots/nitro-renderer'; -import { FC, useCallback } from 'react'; -import { CreateMessageHook } from '../../hooks/messages'; -import { IAchievementsMessageHandlerProps } from './AchievementsMessageHandler.types'; -import { AchievementCategory } from './common/AchievementCategory'; -import { useAchievementsContext } from './context/AchievementsContext'; -import { AchievementsActions } from './reducers/AchievementsReducer'; - -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 deleted file mode 100644 index 3de1326b..00000000 --- a/src/views/achievements/AchievementsMessageHandler.types.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface IAchievementsMessageHandlerProps -{} diff --git a/src/views/achievements/AchievementsView.scss b/src/views/achievements/AchievementsView.scss index f9f69f5d..025a30c8 100644 --- a/src/views/achievements/AchievementsView.scss +++ b/src/views/achievements/AchievementsView.scss @@ -1,63 +1,28 @@ .nitro-achievements { width: $achievement-width; height: $achievement-height; +} - .score { - border-color: $grid-border-color !important; - background-color: $grid-bg-color; - } +.nitro-achievements-category-grid { + --nitro-grid-column-min-width: 80px !important; - .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; - } - } - - .achievements { - height: 152px; - overflow-y: auto; - overflow-x: hidden; - - .achievement { - 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; - } - - &.gray { - div { - filter: grayscale(1); - opacity: .5; - } - } - - div { - height: 40px; - } - } - } - - .achievement-image { + .grid-item { height: 80px; - width: 80px; + max-height: 80px; - .badge-image { - width: 80px; - height: 80px; - background-size: contain; + .achievement-score { + top: 50px; } } } + +.nitro-achievements-back-arrow { + background: url('../../assets/images/achievements/back-arrow.png') no-repeat center; + width: 33px; + height: 34px; +} + +.nitro-achievements-badge-image { + width: 80px; + height: 80px; +} diff --git a/src/views/achievements/AchievementsView.tsx b/src/views/achievements/AchievementsView.tsx index 48bb619b..9ef315a6 100644 --- a/src/views/achievements/AchievementsView.tsx +++ b/src/views/achievements/AchievementsView.tsx @@ -1,22 +1,26 @@ -import { FC, useCallback, useReducer, useState } from 'react'; +import { AchievementData, AchievementEvent, AchievementsEvent, AchievementsScoreEvent, RequestAchievementsMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { LocalizeText } from '../../api'; -import { AchievementsUIEvent } from '../../events/achievements'; +import { AchievementsUIEvent, AchievementsUIUnseenCountEvent } from '../../events/achievements'; +import { BatchUpdates, CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../hooks'; import { useUiEvent } from '../../hooks/events'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout'; -import { AchievementsMessageHandler } from './AchievementsMessageHandler'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardSubHeaderView, NitroCardView, NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../layout'; +import { NitroLayoutBase } from '../../layout/base'; import { AchievementsViewProps } from './AchievementsView.types'; -import { AchievementsContextProvider } from './context/AchievementsContext'; -import { AchievementsReducer, initialAchievements } from './reducers/AchievementsReducer'; -import { AchievementsListView } from './views/category-list/AchievementsListView'; +import { AchievementCategory } from './common/AchievementCategory'; +import { AchievementUtilities } from './common/AchievementUtilities'; +import { AchievementsCategoryListView } from './views/category-list/AchievementsCategoryListView'; import { AchievementCategoryView } from './views/category/AchievementCategoryView'; export const AchievementsView: FC = props => { const [ isVisible, setIsVisible ] = useState(false); - const [ achievementsState, dispatchAchievementsState ] = useReducer(AchievementsReducer, initialAchievements); - const { score = null } = achievementsState; + const [ isInitalized, setIsInitalized ] = useState(false); + const [ achievementCategories, setAchievementCategories ] = useState([]); + const [ selectedCategoryCode, setSelectedCategoryCode ] = useState(null); + const [ achievementScore, setAchievementScore ] = useState(0); - const onAchievementsEvent = useCallback((event: AchievementsUIEvent) => + const onAchievementsUIEvent = useCallback((event: AchievementsUIEvent) => { switch(event.type) { @@ -32,30 +36,203 @@ export const AchievementsView: FC = props => } }, []); - useUiEvent(AchievementsUIEvent.SHOW_ACHIEVEMENTS, onAchievementsEvent); - useUiEvent(AchievementsUIEvent.HIDE_ACHIEVEMENTS, onAchievementsEvent); - useUiEvent(AchievementsUIEvent.TOGGLE_ACHIEVEMENTS, onAchievementsEvent); + useUiEvent(AchievementsUIEvent.SHOW_ACHIEVEMENTS, onAchievementsUIEvent); + useUiEvent(AchievementsUIEvent.HIDE_ACHIEVEMENTS, onAchievementsUIEvent); + useUiEvent(AchievementsUIEvent.TOGGLE_ACHIEVEMENTS, onAchievementsUIEvent); + + const onAchievementEvent = useCallback((event: AchievementEvent) => + { + const parser = event.getParser(); + const achievement = parser.achievement; + const newCategories = [ ...achievementCategories ]; + const categoryName = achievement.category; + const categoryIndex = newCategories.findIndex(existing => (existing.code === categoryName)); + + if(categoryIndex === -1) + { + const category = new AchievementCategory(categoryName); + + category.achievements.push(achievement); + + newCategories.push(category); + } + else + { + const category = newCategories[categoryIndex]; + const newAchievements = [ ...category.achievements ]; + const achievementIndex = newAchievements.findIndex(existing => (existing.achievementId === achievement.achievementId)); + let previousAchievement: AchievementData = null; + + if(achievementIndex === -1) + { + newAchievements.push(achievement); + } + else + { + previousAchievement = newAchievements[achievementIndex]; + + newAchievements[achievementIndex] = achievement; + } + + if(!AchievementUtilities.isIgnoredAchievement(achievement)) + { + achievement.unseen++; + + if(previousAchievement) achievement.unseen += previousAchievement.unseen; + } + + category.achievements = newAchievements; + } + + setAchievementCategories(newCategories); + }, [ achievementCategories ]); + + CreateMessageHook(AchievementEvent, onAchievementEvent); + + const onAchievementsEvent = useCallback((event: AchievementsEvent) => + { + const parser = event.getParser(); + + const categories: AchievementCategory[] = []; + + for(const achievement of parser.achievements) + { + const categoryName = achievement.category; + let existing = categories.find(category => (category.code === categoryName)); + + if(!existing) + { + existing = new AchievementCategory(categoryName); + + categories.push(existing); + } + + existing.achievements.push(achievement); + } + + BatchUpdates(() => + { + setAchievementCategories(categories); + setIsInitalized(true); + }); + }, []); + + CreateMessageHook(AchievementsEvent, onAchievementsEvent); + + const onAchievementsScoreEvent = useCallback((event: AchievementsScoreEvent) => + { + const parser = event.getParser(); + + setAchievementScore(parser.score); + }, []); + + CreateMessageHook(AchievementsScoreEvent, onAchievementsScoreEvent); + + const getTotalUnseen = useMemo(() => + { + let unseen = 0; + + for(const category of achievementCategories) + { + for(const achievement of category.achievements) unseen += achievement.unseen; + } + + return unseen; + }, [ achievementCategories ]); + + const getProgress = useMemo(() => + { + let progress = 0; + + for(const category of achievementCategories) progress += category.getProgress(); + + return progress; + }, [ achievementCategories ]); + + const getMaxProgress = useMemo(() => + { + let progress = 0; + + for(const category of achievementCategories) progress += category.getMaxProgress(); + + return progress; + }, [ achievementCategories ]); + + const getSelectedCategory = useMemo(() => + { + if(!achievementCategories || !achievementCategories.length) return null; + + return achievementCategories.find(existing => (existing.code === selectedCategoryCode)); + }, [ achievementCategories, selectedCategoryCode ]); + + const setAchievementSeen = useCallback((code: string, achievementId: number) => + { + const newCategories = [ ...achievementCategories ]; + + for(const category of newCategories) + { + if(category.code !== code) continue; + + for(const achievement of category.achievements) + { + if(achievement.achievementId !== achievementId) continue; + + achievement.unseen = 0; + } + } + + setAchievementCategories(newCategories); + }, [ achievementCategories ]); + + useEffect(() => + { + if(!isVisible || !isInitalized) return; + + SendMessageHook(new RequestAchievementsMessageComposer()); + }, [ isVisible, isInitalized ]); + + useEffect(() => + { + dispatchUiEvent(new AchievementsUIUnseenCountEvent(getTotalUnseen)); + }, [ getTotalUnseen ]); + + if(!isVisible || !isInitalized) return null; return ( - - - { isVisible && - - setIsVisible(false) } /> - -
-
- -
- { LocalizeText('achievements.categories.score', ['score'], [score.toString()]) } -
-
-
- -
-
-
-
} -
+ + setIsVisible(false) } /> + { getSelectedCategory && + + setSelectedCategoryCode(null) } className="nitro-achievements-back-arrow" /> + + + { LocalizeText(`quests.${ getSelectedCategory.code }.name`) } + + + { LocalizeText('achievements.details.categoryprogress', [ 'progress', 'limit' ], [ getSelectedCategory.getProgress().toString(), getSelectedCategory.getMaxProgress().toString() ]) } + + + } + + + + { !getSelectedCategory && + <> + + + + { LocalizeText('achievements.categories.totalprogress', [ 'progress', 'limit' ], [ getProgress.toString(), getMaxProgress.toString() ]) } + + + { LocalizeText('achievements.categories.score', [ 'score' ], [ achievementScore.toString() ]) } + + + } + { getSelectedCategory && + } + + + + ); }; diff --git a/src/views/achievements/common/AchievementCategory.ts b/src/views/achievements/common/AchievementCategory.ts index e40468d1..992c3a57 100644 --- a/src/views/achievements/common/AchievementCategory.ts +++ b/src/views/achievements/common/AchievementCategory.ts @@ -2,23 +2,42 @@ import { AchievementData } from '@nitrots/nitro-renderer'; export class AchievementCategory { - private _name: string; + private _code: string; private _achievements: AchievementData[]; - constructor(name: string) + constructor(code: string) { - this._name = name; - this._achievements = []; + this._code = code; + this._achievements = []; } - public get name(): string + public getProgress(): number { - return this._name; + let progress = 0; + + for(const achievement of this._achievements) + { + progress += (achievement.finalLevel ? achievement.level : (achievement.level - 1)); + } + + return progress; } - public set name(name: string) + public getMaxProgress(): number { - this._name = name; + let progress = 0; + + for(const achievement of this._achievements) + { + progress += achievement.levelCount; + } + + return progress; + } + + public get code(): string + { + return this._code; } public get achievements(): AchievementData[] diff --git a/src/views/achievements/common/AchievementUtilities.ts b/src/views/achievements/common/AchievementUtilities.ts new file mode 100644 index 00000000..f6c18ac3 --- /dev/null +++ b/src/views/achievements/common/AchievementUtilities.ts @@ -0,0 +1,38 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { GetConfiguration, GetLocalization } from '../../../api'; + +export class AchievementUtilities +{ + public static hasStarted(achievement: AchievementData): boolean + { + if(!achievement) return false; + + if(achievement.finalLevel || ((achievement.level - 1) > 0)) return true; + + return false; + } + + public static getBadgeCode(achievement: AchievementData): string + { + if(!achievement) return null; + + let badgeId = achievement.badgeId; + + if(!achievement.finalLevel) badgeId = GetLocalization().getPreviousLevelBadgeId(badgeId); + + return badgeId; + } + + public static isIgnoredAchievement(achievement: AchievementData): boolean + { + if(!achievement) return false; + + const ignored = GetConfiguration('achievements.unseen.ignored'); + const value = achievement.badgeId.replace(/[0-9]/g, ''); + const index = ignored.indexOf(value); + + if(index >= 0) return true; + + return false; + } +} diff --git a/src/views/achievements/common/IsIgnoredAchievement.ts b/src/views/achievements/common/IsIgnoredAchievement.ts new file mode 100644 index 00000000..0cc81fdd --- /dev/null +++ b/src/views/achievements/common/IsIgnoredAchievement.ts @@ -0,0 +1,15 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { GetConfiguration } from '../../../api'; + +export const IsIgnoredAchievement = (achievement: AchievementData) => +{ + if(!achievement) return false; + + const ignored = GetConfiguration('achievements.unseen.ignored'); + const value = achievement.badgeId.replace(/[0-9]/g, ''); + const index = ignored.indexOf(value); + + if(index >= 0) return true; + + return false; +} diff --git a/src/views/achievements/context/AchievementsContext.tsx b/src/views/achievements/context/AchievementsContext.tsx deleted file mode 100644 index c1162f4d..00000000 --- a/src/views/achievements/context/AchievementsContext.tsx +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 320cd787..00000000 --- a/src/views/achievements/context/AchievementsContext.types.ts +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 6f8e2b11..00000000 --- a/src/views/achievements/reducers/AchievementsReducer.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Reducer } from 'react'; -import { AchievementCategory } from '../common/AchievementCategory'; - -export interface IAchievementsState -{ - categories: AchievementCategory[], - score: number, - selectedCategoryName: string, - selectedAchievementId: number -} - -export interface IAchievementsAction -{ - type: string; - payload: { - categories?: AchievementCategory[], - score?: number, - selectedCategoryName?: string, - selectedAchievementId?: number - } -} - -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'; - public static SELECT_ACHIEVEMENT: string = 'AA_SELECT_ACHIEVEMENT'; -} - -export const initialAchievements: IAchievementsState = { - categories: null, - score: null, - selectedCategoryName: null, - selectedAchievementId: 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; - let selectedAchievementId = null; - - if(categories.length > 0) - { - selectedCategoryName = categories[0].name; - - if(categories[0].achievements.length > 0) selectedAchievementId = categories[0].achievements[0].achievementId; - } - - return { ...state, categories, selectedCategoryName, selectedAchievementId }; - } - 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); - - let selectedAchievementId = null; - - if(selectedCategoryName) - { - const category = state.categories.find(category => category.name === selectedCategoryName); - - if(category && category.achievements.length > 0) - { - selectedAchievementId = category.achievements[0].achievementId; - } - } - - return { ...state, selectedCategoryName, selectedAchievementId }; - } - case AchievementsActions.SELECT_ACHIEVEMENT: { - const selectedAchievementId = (action.payload.selectedAchievementId || state.selectedAchievementId || null); - - return { ...state, selectedAchievementId }; - } - default: - return state; - } -} diff --git a/src/views/achievements/views/achievement-badge/AchievementBadgeView.tsx b/src/views/achievements/views/achievement-badge/AchievementBadgeView.tsx new file mode 100644 index 00000000..578f86dc --- /dev/null +++ b/src/views/achievements/views/achievement-badge/AchievementBadgeView.tsx @@ -0,0 +1,15 @@ +import { FC } from 'react'; +import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView'; +import { AchievementUtilities } from '../../common/AchievementUtilities'; +import { AchievementBadgeViewProps } from './AchievementBadgeView.types'; + +export const AchievementBadgeView: FC = props => +{ + const { achievement = null, scale = 1, ...rest } = props; + + if(!achievement) return null; + + return ( + + ); +} diff --git a/src/views/achievements/views/achievement-badge/AchievementBadgeView.types.ts b/src/views/achievements/views/achievement-badge/AchievementBadgeView.types.ts new file mode 100644 index 00000000..c89706ac --- /dev/null +++ b/src/views/achievements/views/achievement-badge/AchievementBadgeView.types.ts @@ -0,0 +1,8 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { NitroLayoutBaseProps } from '../../../../layout/base'; + +export interface AchievementBadgeViewProps extends NitroLayoutBaseProps +{ + achievement: AchievementData; + scale?: number; +} diff --git a/src/views/achievements/views/achievement-details/AchievementDetailsView.tsx b/src/views/achievements/views/achievement-details/AchievementDetailsView.tsx new file mode 100644 index 00000000..be50d707 --- /dev/null +++ b/src/views/achievements/views/achievement-details/AchievementDetailsView.tsx @@ -0,0 +1,62 @@ +import { FC, useMemo } from 'react'; +import { LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../../../api'; +import { NitroLayoutFlex, NitroLayoutFlexColumn } from '../../../../layout'; +import { NitroLayoutBase } from '../../../../layout/base'; +import { CurrencyIcon } from '../../../shared/currency-icon/CurrencyIcon'; +import { AchievementUtilities } from '../../common/AchievementUtilities'; +import { AchievementBadgeView } from '../achievement-badge/AchievementBadgeView'; +import { AchievementDetailsViewProps } from './AchievementDetailsView.types'; + +export const AchievementDetailsView: FC = props => +{ + const { achievement = null } = props; + + const getAchievementLevel = useMemo(() => + { + if(achievement.finalLevel) return achievement.level; + + return (achievement.level - 1); + }, [ achievement ]); + + if(!achievement) return null; + + const currentAmount = achievement.currentPoints; + const maxAmount = achievement.scoreLimit; + const scoreAtStartOfLevel = achievement.scoreAtStartOfLevel; + + return ( + + + + + { LocalizeText('achievements.details.level', [ 'level', 'limit' ], [ getAchievementLevel.toString(), achievement.levelCount.toString() ]) } + + + + + + { LocalizeBadgeName(AchievementUtilities.getBadgeCode(achievement)) } + + + { LocalizeBadgeDescription(AchievementUtilities.getBadgeCode(achievement)) } + + + { ((achievement.levelRewardPoints > 0) || (maxAmount > 0)) && + + { (achievement.levelRewardPoints > 0) && + + + { LocalizeText('achievements.details.reward') } + + + { achievement.levelRewardPoints } + + + } + { (maxAmount > 0) && + LocalizeText('achievements.details.progress', [ 'progress', 'limit' ], [ (currentAmount + scoreAtStartOfLevel).toString(), (maxAmount + scoreAtStartOfLevel).toString() ]) } + } + + + ) +} diff --git a/src/views/achievements/views/achievement-details/AchievementDetailsView.types.ts b/src/views/achievements/views/achievement-details/AchievementDetailsView.types.ts new file mode 100644 index 00000000..098f0867 --- /dev/null +++ b/src/views/achievements/views/achievement-details/AchievementDetailsView.types.ts @@ -0,0 +1,6 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; + +export interface AchievementDetailsViewProps +{ + achievement: AchievementData; +} diff --git a/src/views/achievements/views/achievement-list-item/AchievementListItemView.tsx b/src/views/achievements/views/achievement-list-item/AchievementListItemView.tsx new file mode 100644 index 00000000..806c1ed5 --- /dev/null +++ b/src/views/achievements/views/achievement-list-item/AchievementListItemView.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { NitroCardGridItemView } from '../../../../layout'; +import { AchievementBadgeView } from '../achievement-badge/AchievementBadgeView'; +import { AchievementListItemViewProps } from './AchievementListItemView.types'; + +export const AchievementListItemView: FC = props => +{ + const { achievement = null, children = null, ...rest } = props; + + if(!achievement) return null; + + return ( + + + + ); +} diff --git a/src/views/achievements/views/achievement-list-item/AchievementListItemView.types.ts b/src/views/achievements/views/achievement-list-item/AchievementListItemView.types.ts new file mode 100644 index 00000000..78072e6d --- /dev/null +++ b/src/views/achievements/views/achievement-list-item/AchievementListItemView.types.ts @@ -0,0 +1,7 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { NitroCardGridItemViewProps } from '../../../../layout'; + +export interface AchievementListItemViewProps extends NitroCardGridItemViewProps +{ + achievement: AchievementData; +} diff --git a/src/views/achievements/views/achievement-list/AchievementListView.tsx b/src/views/achievements/views/achievement-list/AchievementListView.tsx new file mode 100644 index 00000000..8cb6d7e1 --- /dev/null +++ b/src/views/achievements/views/achievement-list/AchievementListView.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; +import { NitroCardGridView } from '../../../../layout'; +import { AchievementListItemView } from '../achievement-list-item/AchievementListItemView'; +import { AchievementListViewProps } from './AchievementListView.types'; + +export const AchievementListView: FC = props => +{ + const { achievements = null, selectedAchievementId = 0, setSelectedAchievementId = null, ...rest } = props; + + return ( + + { achievements && (achievements.length > 0) && achievements.map(achievement => + { + return setSelectedAchievementId(achievement.achievementId) } />; + }) } + + ); +} diff --git a/src/views/achievements/views/achievement-list/AchievementListView.types.ts b/src/views/achievements/views/achievement-list/AchievementListView.types.ts new file mode 100644 index 00000000..9a6013f9 --- /dev/null +++ b/src/views/achievements/views/achievement-list/AchievementListView.types.ts @@ -0,0 +1,10 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { Dispatch, SetStateAction } from 'react'; +import { NitroCardGridViewProps } from '../../../../layout'; + +export interface AchievementListViewProps extends NitroCardGridViewProps +{ + achievements: AchievementData[]; + selectedAchievementId: number; + setSelectedAchievementId: Dispatch>; +} diff --git a/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.tsx b/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.tsx deleted file mode 100644 index ddc6ed24..00000000 --- a/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import classNames from 'classnames'; -import { FC, useCallback, useMemo } from 'react'; -import { GetConfiguration } from '../../../../api'; -import { useAchievementsContext } from '../../context/AchievementsContext'; -import { AchievementsActions } from '../../reducers/AchievementsReducer'; -import { AchievementCategoryListItemViewProps } from './AchievementCategoryListItemView.types'; - -export const AchievementCategoryListItemView: FC = props => -{ - const { category = null, isActive = false } = props; - const { dispatchAchievementsState = null } = useAchievementsContext(); - - const categoryLevel = useMemo(() => - { - let level = 0; - - for(const achievement of category.achievements) - { - level = (level + (achievement.finalLevel ? achievement.level : (achievement.level - 1))); - } - - return level; - }, [ category ]); - - const getCategoryImage = useMemo(() => - { - const level = categoryLevel; - const imageUrl = GetConfiguration('achievements.images.url'); - - return imageUrl.replace('%image%', `achcategory_${ category.name }_${ ((level > 0) ? 'active' : 'inactive') }`); - }, [ category, categoryLevel ]); - - const getCategoryProgress = useMemo(() => - { - let completed = 0; - let total = 0; - - for(const achievement of category.achievements) - { - if(!achievement) continue; - - if(achievement.firstLevelAchieved) completed = (completed + ((achievement.finalLevel) ? achievement.level : (achievement.level - 1))); - - total += achievement.scoreLimit; - } - - return (completed + ' / ' + total); - }, [ category ]); - - const selectCategory = useCallback((name: string) => - { - dispatchAchievementsState({ - type: AchievementsActions.SELECT_CATEGORY, - payload: { - selectedCategoryName: name - } - }); - }, [ dispatchAchievementsState ]); - - return ( -
-
selectCategory(category.name) }> - -
{ getCategoryProgress }
-
-
- ); -} diff --git a/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.types.ts b/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.types.ts index 96e6b946..3f493995 100644 --- a/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.types.ts +++ b/src/views/achievements/views/category-list-item/AchievementCategoryListItemView.types.ts @@ -1,7 +1,7 @@ +import { NitroCardGridItemViewProps } from '../../../../layout'; import { AchievementCategory } from '../../common/AchievementCategory'; -export interface AchievementCategoryListItemViewProps +export interface AchievementCategoryListItemViewProps extends NitroCardGridItemViewProps { category: AchievementCategory; - isActive?: boolean; } diff --git a/src/views/achievements/views/category-list-item/AchievementsCategoryListItemView.tsx b/src/views/achievements/views/category-list-item/AchievementsCategoryListItemView.tsx new file mode 100644 index 00000000..30a82dc9 --- /dev/null +++ b/src/views/achievements/views/category-list-item/AchievementsCategoryListItemView.tsx @@ -0,0 +1,38 @@ +import { FC, useCallback, useMemo } from 'react'; +import { GetConfiguration } from '../../../../api'; +import { NitroCardGridItemView } from '../../../../layout'; +import { NitroLayoutBase } from '../../../../layout/base'; +import { AchievementCategoryListItemViewProps } from './AchievementCategoryListItemView.types'; + +export const AchievementsCategoryListItemView: FC = props => +{ + const { category = null, ...rest } = props; + + const progress = category.getProgress(); + const maxProgress = category.getMaxProgress(); + + const getCategoryImage = useMemo(() => + { + const imageUrl = GetConfiguration('achievements.images.url'); + + return imageUrl.replace('%image%', `achcategory_${ category.code }_${ ((progress > 0) ? 'active' : 'inactive') }`); + }, [ category, progress ]); + + const getTotalUnseen = useCallback(() => + { + let unseen = 0; + + for(const achievement of category.achievements) unseen += achievement.unseen; + + return unseen; + }, [ category ]); + + return ( + + { category.code } + + { progress } / { maxProgress } + + + ); +} diff --git a/src/views/achievements/views/category-list/AchievementsCategoryListView.tsx b/src/views/achievements/views/category-list/AchievementsCategoryListView.tsx new file mode 100644 index 00000000..70cbc44d --- /dev/null +++ b/src/views/achievements/views/category-list/AchievementsCategoryListView.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; +import { NitroCardGridView } from '../../../../layout'; +import { AchievementsCategoryListItemView } from '../category-list-item/AchievementsCategoryListItemView'; +import { AchievementsCategoryListViewProps } from './AchievementsCategoryListView.types'; + +export const AchievementsCategoryListView: FC = props => +{ + const { categories = null, selectedCategoryCode = null, setSelectedCategoryCode = null, ...rest } = props; + + return ( + + { categories && (categories.length > 0) && categories.map((category, index) => + { + return setSelectedCategoryCode(category.code) } />; + }) } + + ); +}; diff --git a/src/views/achievements/views/category-list/AchievementsCategoryListView.types.ts b/src/views/achievements/views/category-list/AchievementsCategoryListView.types.ts new file mode 100644 index 00000000..ef15c583 --- /dev/null +++ b/src/views/achievements/views/category-list/AchievementsCategoryListView.types.ts @@ -0,0 +1,10 @@ +import { Dispatch, SetStateAction } from 'react'; +import { NitroCardGridViewProps } from '../../../../layout'; +import { AchievementCategory } from '../../common/AchievementCategory'; + +export interface AchievementsCategoryListViewProps extends NitroCardGridViewProps +{ + categories: AchievementCategory[]; + selectedCategoryCode: string; + setSelectedCategoryCode: Dispatch>; +} diff --git a/src/views/achievements/views/category-list/AchievementsListView.tsx b/src/views/achievements/views/category-list/AchievementsListView.tsx deleted file mode 100644 index 2ca173c7..00000000 --- a/src/views/achievements/views/category-list/AchievementsListView.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FC } from 'react'; -import { useAchievementsContext } from '../../context/AchievementsContext'; -import { AchievementCategoryListItemView } from '../category-list-item/AchievementCategoryListItemView'; - -export const AchievementsListView: FC<{}> = props => -{ - const { achievementsState = null, dispatchAchievementsState = null } = useAchievementsContext();; - const { categories = null, selectedCategoryName = null } = achievementsState; - - return ( -
- { categories && categories.map((category, index) => - { - return ; - }) } -
- ); -}; diff --git a/src/views/achievements/views/category/AchievementCategoryView.tsx b/src/views/achievements/views/category/AchievementCategoryView.tsx index 879c4d84..4041bf21 100644 --- a/src/views/achievements/views/category/AchievementCategoryView.tsx +++ b/src/views/achievements/views/category/AchievementCategoryView.tsx @@ -1,86 +1,51 @@ -import { AchievementData } from '@nitrots/nitro-renderer'; -import classNames from 'classnames'; -import { FC, useCallback, useMemo } from 'react'; -import { LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../../../api'; -import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView'; -import { useAchievementsContext } from '../../context/AchievementsContext'; -import { AchievementsActions } from '../../reducers/AchievementsReducer'; +import { FC, useEffect, useMemo, useState } from 'react'; +import { NitroLayoutFlexColumn } from '../../../../layout'; +import { AchievementDetailsView } from '../achievement-details/AchievementDetailsView'; +import { AchievementListView } from '../achievement-list/AchievementListView'; import { AchievementCategoryViewProps } from './AchievementCategoryView.types'; export const AchievementCategoryView: FC = props => { - const achievementsContext = useAchievementsContext(); - - const { achievementsState = null, dispatchAchievementsState = null } = achievementsContext; - const { categories = null, selectedCategoryName = null, selectedAchievementId = null } = achievementsState; + const { category = null, setAchievementSeen = null } = props; + const [ selectedAchievementId, setSelectedAchievementId ] = useState(0); - const getSelectedCategory = useCallback(() => + const getSelectedAchievement = useMemo(() => { - return categories.find(category => category.name === selectedCategoryName); - }, [ categories, selectedCategoryName ]); + if(!category || !category.achievements.length) return null; - const getAchievementImage = useCallback((achievement: AchievementData) => + return category.achievements.find(existing => (existing.achievementId === selectedAchievementId)); + }, [ category, selectedAchievementId ]); + + useEffect(() => { - if(!achievement) return null; - - let badgeId = achievement.badgeId; + let achievementId = 0; - if(achievement.levelCount > 1) + if(!category || !category.achievements.length) { - badgeId = badgeId.replace(/[0-9]/g, ''); - badgeId = (badgeId + (((achievement.level - 1) > 0) ? (achievement.level - 1) : achievement.level)); + achievementId = 0; + } + else + { + achievementId = category.achievements[0].achievementId; } - return badgeId; - }, []); + setSelectedAchievementId(achievementId); + }, [ category ]); - const selectedAchievement = useMemo(() => + useEffect(() => { - if(!getSelectedCategory()) return null; - - return getSelectedCategory().achievements.find(achievement => achievement.achievementId === selectedAchievementId); - }, [ getSelectedCategory, selectedAchievementId ]); + if(!getSelectedAchievement || !getSelectedAchievement.unseen) return; - const selectAchievement = useCallback((id: number) => - { - dispatchAchievementsState({ - type: AchievementsActions.SELECT_ACHIEVEMENT, - payload: { - selectedAchievementId: id - } - }); - }, [ dispatchAchievementsState ]); + setAchievementSeen(category.code, getSelectedAchievement.achievementId); + }, [ category, getSelectedAchievement, setAchievementSeen ]); + if(!category) return null; return ( -
-
-
{ LocalizeText('quests.' + selectedCategoryName + '.name') }
-
IMAGE
-
- { selectedAchievement &&
-
- -
-
-
{ LocalizeBadgeName(selectedAchievement.badgeId) }
-
{ LocalizeBadgeDescription(selectedAchievement.badgeId) }
-
-
} -
-
- { getSelectedCategory().achievements.map((achievement, index) => - { - return ( -
-
selectAchievement(achievement.achievementId)}> - -
-
- ) - }) } -
-
-
+ + + { getSelectedAchievement && + } + ); } diff --git a/src/views/achievements/views/category/AchievementCategoryView.types.ts b/src/views/achievements/views/category/AchievementCategoryView.types.ts index d6c378d4..0e819a34 100644 --- a/src/views/achievements/views/category/AchievementCategoryView.types.ts +++ b/src/views/achievements/views/category/AchievementCategoryView.types.ts @@ -1,2 +1,7 @@ +import { AchievementCategory } from '../../common/AchievementCategory'; + export class AchievementCategoryViewProps -{} +{ + category: AchievementCategory; + setAchievementSeen: (code: string, achievementId: number) => void; +}