mirror of
https://github.com/billsonnn/nitro-react.git
synced 2024-11-23 14:40:50 +01:00
Achievement updates
This commit is contained in:
parent
7959b6772e
commit
0e22b4fe7e
20
src/events/achievements/AchievementsUIUnseenCountEvent.ts
Normal file
20
src/events/achievements/AchievementsUIUnseenCountEvent.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
export * from './AchievementsUIEvent';
|
export * from './AchievementsUIEvent';
|
||||||
|
export * from './AchievementsUIUnseenCountEvent';
|
||||||
|
@ -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<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;
|
|
||||||
};
|
|
@ -1,2 +0,0 @@
|
|||||||
export interface IAchievementsMessageHandlerProps
|
|
||||||
{}
|
|
@ -1,63 +1,28 @@
|
|||||||
.nitro-achievements {
|
.nitro-achievements {
|
||||||
width: $achievement-width;
|
width: $achievement-width;
|
||||||
height: $achievement-height;
|
height: $achievement-height;
|
||||||
|
}
|
||||||
|
|
||||||
.score {
|
.nitro-achievements-category-grid {
|
||||||
border-color: $grid-border-color !important;
|
--nitro-grid-column-min-width: 80px !important;
|
||||||
background-color: $grid-bg-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category {
|
.grid-item {
|
||||||
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 {
|
|
||||||
height: 80px;
|
height: 80px;
|
||||||
width: 80px;
|
max-height: 80px;
|
||||||
|
|
||||||
.badge-image {
|
.achievement-score {
|
||||||
width: 80px;
|
top: 50px;
|
||||||
height: 80px;
|
|
||||||
background-size: contain;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
@ -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 { 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 { useUiEvent } from '../../hooks/events';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardSubHeaderView, NitroCardView, NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../layout';
|
||||||
import { AchievementsMessageHandler } from './AchievementsMessageHandler';
|
import { NitroLayoutBase } from '../../layout/base';
|
||||||
import { AchievementsViewProps } from './AchievementsView.types';
|
import { AchievementsViewProps } from './AchievementsView.types';
|
||||||
import { AchievementsContextProvider } from './context/AchievementsContext';
|
import { AchievementCategory } from './common/AchievementCategory';
|
||||||
import { AchievementsReducer, initialAchievements } from './reducers/AchievementsReducer';
|
import { AchievementUtilities } from './common/AchievementUtilities';
|
||||||
import { AchievementsListView } from './views/category-list/AchievementsListView';
|
import { AchievementsCategoryListView } from './views/category-list/AchievementsCategoryListView';
|
||||||
import { AchievementCategoryView } from './views/category/AchievementCategoryView';
|
import { AchievementCategoryView } from './views/category/AchievementCategoryView';
|
||||||
|
|
||||||
export const AchievementsView: FC<AchievementsViewProps> = props =>
|
export const AchievementsView: FC<AchievementsViewProps> = props =>
|
||||||
{
|
{
|
||||||
const [ isVisible, setIsVisible ] = useState(false);
|
const [ isVisible, setIsVisible ] = useState(false);
|
||||||
const [ achievementsState, dispatchAchievementsState ] = useReducer(AchievementsReducer, initialAchievements);
|
const [ isInitalized, setIsInitalized ] = useState(false);
|
||||||
const { score = null } = achievementsState;
|
const [ achievementCategories, setAchievementCategories ] = useState<AchievementCategory[]>([]);
|
||||||
|
const [ selectedCategoryCode, setSelectedCategoryCode ] = useState<string>(null);
|
||||||
|
const [ achievementScore, setAchievementScore ] = useState(0);
|
||||||
|
|
||||||
const onAchievementsEvent = useCallback((event: AchievementsUIEvent) =>
|
const onAchievementsUIEvent = useCallback((event: AchievementsUIEvent) =>
|
||||||
{
|
{
|
||||||
switch(event.type)
|
switch(event.type)
|
||||||
{
|
{
|
||||||
@ -32,30 +36,203 @@ export const AchievementsView: FC<AchievementsViewProps> = props =>
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useUiEvent(AchievementsUIEvent.SHOW_ACHIEVEMENTS, onAchievementsEvent);
|
useUiEvent(AchievementsUIEvent.SHOW_ACHIEVEMENTS, onAchievementsUIEvent);
|
||||||
useUiEvent(AchievementsUIEvent.HIDE_ACHIEVEMENTS, onAchievementsEvent);
|
useUiEvent(AchievementsUIEvent.HIDE_ACHIEVEMENTS, onAchievementsUIEvent);
|
||||||
useUiEvent(AchievementsUIEvent.TOGGLE_ACHIEVEMENTS, onAchievementsEvent);
|
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 (
|
return (
|
||||||
<AchievementsContextProvider value={ { achievementsState, dispatchAchievementsState } }>
|
|
||||||
<AchievementsMessageHandler />
|
|
||||||
{ isVisible &&
|
|
||||||
<NitroCardView uniqueKey="achievements" className="nitro-achievements" simple={ true }>
|
<NitroCardView uniqueKey="achievements" className="nitro-achievements" simple={ true }>
|
||||||
<NitroCardHeaderView headerText={ LocalizeText('inventory.achievements') } onCloseClick={ event => setIsVisible(false) } />
|
<NitroCardHeaderView headerText={ LocalizeText('inventory.achievements') } onCloseClick={ event => setIsVisible(false) } />
|
||||||
|
{ getSelectedCategory &&
|
||||||
|
<NitroCardSubHeaderView className="justify-content-center align-items-center cursor-pointer" gap={ 3 }>
|
||||||
|
<NitroLayoutBase onClick={ event => setSelectedCategoryCode(null) } className="nitro-achievements-back-arrow" />
|
||||||
|
<NitroLayoutFlexColumn className="flex-grow-1">
|
||||||
|
<NitroLayoutBase className="fs-4 text-black fw-bold">
|
||||||
|
{ LocalizeText(`quests.${ getSelectedCategory.code }.name`) }
|
||||||
|
</NitroLayoutBase>
|
||||||
|
<NitroLayoutBase className="text-black">
|
||||||
|
{ LocalizeText('achievements.details.categoryprogress', [ 'progress', 'limit' ], [ getSelectedCategory.getProgress().toString(), getSelectedCategory.getMaxProgress().toString() ]) }
|
||||||
|
</NitroLayoutBase>
|
||||||
|
</NitroLayoutFlexColumn>
|
||||||
|
</NitroCardSubHeaderView> }
|
||||||
<NitroCardContentView>
|
<NitroCardContentView>
|
||||||
<div className="row">
|
<NitroLayoutGrid>
|
||||||
<div className="col-6">
|
<NitroLayoutGridColumn size={ 12 }>
|
||||||
<AchievementsListView />
|
{ !getSelectedCategory &&
|
||||||
<div className="score border border-2 text-black text-center rounded">
|
<>
|
||||||
{ LocalizeText('achievements.categories.score', ['score'], [score.toString()]) }
|
<AchievementsCategoryListView categories={ achievementCategories } selectedCategoryCode={ selectedCategoryCode } setSelectedCategoryCode={ setSelectedCategoryCode } />
|
||||||
</div>
|
<NitroLayoutFlexColumn className="flex-grow-1 justify-content-end" gap={ 2 }>
|
||||||
</div>
|
<NitroLayoutBase className="bg-muted text-black text-center rounded">
|
||||||
<div className="col-6">
|
{ LocalizeText('achievements.categories.totalprogress', [ 'progress', 'limit' ], [ getProgress.toString(), getMaxProgress.toString() ]) }
|
||||||
<AchievementCategoryView />
|
</NitroLayoutBase>
|
||||||
</div>
|
<NitroLayoutBase className="bg-muted text-black text-center rounded">
|
||||||
</div>
|
{ LocalizeText('achievements.categories.score', [ 'score' ], [ achievementScore.toString() ]) }
|
||||||
|
</NitroLayoutBase>
|
||||||
|
</NitroLayoutFlexColumn>
|
||||||
|
</> }
|
||||||
|
{ getSelectedCategory &&
|
||||||
|
<AchievementCategoryView category={ getSelectedCategory } setAchievementSeen={ setAchievementSeen } /> }
|
||||||
|
</NitroLayoutGridColumn>
|
||||||
|
</NitroLayoutGrid>
|
||||||
</NitroCardContentView>
|
</NitroCardContentView>
|
||||||
</NitroCardView> }
|
</NitroCardView>
|
||||||
</AchievementsContextProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,23 +2,42 @@ import { AchievementData } from '@nitrots/nitro-renderer';
|
|||||||
|
|
||||||
export class AchievementCategory
|
export class AchievementCategory
|
||||||
{
|
{
|
||||||
private _name: string;
|
private _code: string;
|
||||||
private _achievements: AchievementData[];
|
private _achievements: AchievementData[];
|
||||||
|
|
||||||
constructor(name: string)
|
constructor(code: string)
|
||||||
{
|
{
|
||||||
this._name = name;
|
this._code = code;
|
||||||
this._achievements = [];
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
public set name(name: string)
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
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[]
|
public get achievements(): AchievementData[]
|
||||||
|
38
src/views/achievements/common/AchievementUtilities.ts
Normal file
38
src/views/achievements/common/AchievementUtilities.ts
Normal file
@ -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<string[]>('achievements.unseen.ignored');
|
||||||
|
const value = achievement.badgeId.replace(/[0-9]/g, '');
|
||||||
|
const index = ignored.indexOf(value);
|
||||||
|
|
||||||
|
if(index >= 0) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
15
src/views/achievements/common/IsIgnoredAchievement.ts
Normal file
15
src/views/achievements/common/IsIgnoredAchievement.ts
Normal file
@ -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<string[]>('achievements.unseen.ignored');
|
||||||
|
const value = achievement.badgeId.replace(/[0-9]/g, '');
|
||||||
|
const index = ignored.indexOf(value);
|
||||||
|
|
||||||
|
if(index >= 0) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
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);
|
|
@ -1,13 +0,0 @@
|
|||||||
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>
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
@ -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<IAchievementsState, IAchievementsAction> = (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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<AchievementBadgeViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { achievement = null, scale = 1, ...rest } = props;
|
||||||
|
|
||||||
|
if(!achievement) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BadgeImageView badgeCode={ AchievementUtilities.getBadgeCode(achievement) } isGrayscale={ !AchievementUtilities.hasStarted(achievement) } scale={ scale } { ...rest } />
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import { AchievementData } from '@nitrots/nitro-renderer';
|
||||||
|
import { NitroLayoutBaseProps } from '../../../../layout/base';
|
||||||
|
|
||||||
|
export interface AchievementBadgeViewProps extends NitroLayoutBaseProps
|
||||||
|
{
|
||||||
|
achievement: AchievementData;
|
||||||
|
scale?: number;
|
||||||
|
}
|
@ -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<AchievementDetailsViewProps> = 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 (
|
||||||
|
<NitroLayoutFlex className="bg-muted rounded p-2 text-black flex-shrink-0" gap={ 2 } overflow="hidden">
|
||||||
|
<NitroLayoutFlexColumn className="justify-content-center align-items-center">
|
||||||
|
<AchievementBadgeView className="nitro-achievements-badge-image" achievement={ achievement } scale={ 2 } />
|
||||||
|
<NitroLayoutBase className="fw-bold">
|
||||||
|
{ LocalizeText('achievements.details.level', [ 'level', 'limit' ], [ getAchievementLevel.toString(), achievement.levelCount.toString() ]) }
|
||||||
|
</NitroLayoutBase>
|
||||||
|
</NitroLayoutFlexColumn>
|
||||||
|
<NitroLayoutFlexColumn className="justify-content-center w-100" overflow="hidden" gap={ 2 }>
|
||||||
|
<NitroLayoutFlexColumn>
|
||||||
|
<NitroLayoutBase className="fw-bold text-truncate">
|
||||||
|
{ LocalizeBadgeName(AchievementUtilities.getBadgeCode(achievement)) }
|
||||||
|
</NitroLayoutBase>
|
||||||
|
<NitroLayoutBase className="text-truncate">
|
||||||
|
{ LocalizeBadgeDescription(AchievementUtilities.getBadgeCode(achievement)) }
|
||||||
|
</NitroLayoutBase>
|
||||||
|
</NitroLayoutFlexColumn>
|
||||||
|
{ ((achievement.levelRewardPoints > 0) || (maxAmount > 0)) &&
|
||||||
|
<NitroLayoutFlexColumn gap={ 1 }>
|
||||||
|
{ (achievement.levelRewardPoints > 0) &&
|
||||||
|
<NitroLayoutFlex gap={ 1 }>
|
||||||
|
<NitroLayoutBase className="text-truncate">
|
||||||
|
{ LocalizeText('achievements.details.reward') }
|
||||||
|
</NitroLayoutBase>
|
||||||
|
<NitroLayoutFlex className="fw-bold align-items-center justify-content-center" gap={ 1 }>
|
||||||
|
{ achievement.levelRewardPoints }
|
||||||
|
<CurrencyIcon type={ achievement.levelRewardPointType } />
|
||||||
|
</NitroLayoutFlex>
|
||||||
|
</NitroLayoutFlex> }
|
||||||
|
{ (maxAmount > 0) &&
|
||||||
|
LocalizeText('achievements.details.progress', [ 'progress', 'limit' ], [ (currentAmount + scoreAtStartOfLevel).toString(), (maxAmount + scoreAtStartOfLevel).toString() ]) }
|
||||||
|
</NitroLayoutFlexColumn> }
|
||||||
|
</NitroLayoutFlexColumn>
|
||||||
|
</NitroLayoutFlex>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import { AchievementData } from '@nitrots/nitro-renderer';
|
||||||
|
|
||||||
|
export interface AchievementDetailsViewProps
|
||||||
|
{
|
||||||
|
achievement: AchievementData;
|
||||||
|
}
|
@ -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<AchievementListItemViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { achievement = null, children = null, ...rest } = props;
|
||||||
|
|
||||||
|
if(!achievement) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardGridItemView itemCount={ achievement.unseen } itemCountMinimum={ 0 } { ...rest }>
|
||||||
|
<AchievementBadgeView achievement={ achievement } />
|
||||||
|
</NitroCardGridItemView>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import { AchievementData } from '@nitrots/nitro-renderer';
|
||||||
|
import { NitroCardGridItemViewProps } from '../../../../layout';
|
||||||
|
|
||||||
|
export interface AchievementListItemViewProps extends NitroCardGridItemViewProps
|
||||||
|
{
|
||||||
|
achievement: AchievementData;
|
||||||
|
}
|
@ -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<AchievementListViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { achievements = null, selectedAchievementId = 0, setSelectedAchievementId = null, ...rest } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardGridView { ...rest }>
|
||||||
|
{ achievements && (achievements.length > 0) && achievements.map(achievement =>
|
||||||
|
{
|
||||||
|
return <AchievementListItemView key={ achievement.achievementId } achievement={ achievement } itemActive={ (selectedAchievementId === achievement.achievementId) } onClick={ event => setSelectedAchievementId(achievement.achievementId) } />;
|
||||||
|
}) }
|
||||||
|
</NitroCardGridView>
|
||||||
|
);
|
||||||
|
}
|
@ -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<SetStateAction<number>>;
|
||||||
|
}
|
@ -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<AchievementCategoryListItemViewProps> = 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<string>('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 (
|
|
||||||
<div className="col mb-3">
|
|
||||||
<div className={ 'd-flex flex-column justify-content-center align-items-center category border border-2 rounded p-2' + classNames({ ' active': isActive }) } onClick={ () => selectCategory(category.name) }>
|
|
||||||
<img src={ getCategoryImage } alt="" />
|
|
||||||
<div className="position-absolute category-score small">{ getCategoryProgress }</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
|
import { NitroCardGridItemViewProps } from '../../../../layout';
|
||||||
import { AchievementCategory } from '../../common/AchievementCategory';
|
import { AchievementCategory } from '../../common/AchievementCategory';
|
||||||
|
|
||||||
export interface AchievementCategoryListItemViewProps
|
export interface AchievementCategoryListItemViewProps extends NitroCardGridItemViewProps
|
||||||
{
|
{
|
||||||
category: AchievementCategory;
|
category: AchievementCategory;
|
||||||
isActive?: boolean;
|
|
||||||
}
|
}
|
||||||
|
@ -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<AchievementCategoryListItemViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { category = null, ...rest } = props;
|
||||||
|
|
||||||
|
const progress = category.getProgress();
|
||||||
|
const maxProgress = category.getMaxProgress();
|
||||||
|
|
||||||
|
const getCategoryImage = useMemo(() =>
|
||||||
|
{
|
||||||
|
const imageUrl = GetConfiguration<string>('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 (
|
||||||
|
<NitroCardGridItemView itemImage={ getCategoryImage } itemCount={ getTotalUnseen() } itemCountMinimum={ 0 } { ...rest }>
|
||||||
|
<NitroLayoutBase className="text-black small">{ category.code }</NitroLayoutBase>
|
||||||
|
<NitroLayoutBase className="achievement-score small" position="absolute">
|
||||||
|
{ progress } / { maxProgress }
|
||||||
|
</NitroLayoutBase>
|
||||||
|
</NitroCardGridItemView>
|
||||||
|
);
|
||||||
|
}
|
@ -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<AchievementsCategoryListViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { categories = null, selectedCategoryCode = null, setSelectedCategoryCode = null, ...rest } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardGridView className="nitro-achievements-category-grid" { ...rest }>
|
||||||
|
{ categories && (categories.length > 0) && categories.map((category, index) =>
|
||||||
|
{
|
||||||
|
return <AchievementsCategoryListItemView key={ index } category={ category } itemActive={ (selectedCategoryCode === category.code) } onClick={ event => setSelectedCategoryCode(category.code) } />;
|
||||||
|
}) }
|
||||||
|
</NitroCardGridView>
|
||||||
|
);
|
||||||
|
};
|
@ -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<SetStateAction<string>>;
|
||||||
|
}
|
@ -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 (
|
|
||||||
<div className="row row-cols-3">
|
|
||||||
{ categories && categories.map((category, index) =>
|
|
||||||
{
|
|
||||||
return <AchievementCategoryListItemView key={ index } category={ category } isActive={ selectedCategoryName === category.name } />;
|
|
||||||
}) }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,86 +1,51 @@
|
|||||||
import { AchievementData } from '@nitrots/nitro-renderer';
|
import { FC, useEffect, useMemo, useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import { NitroLayoutFlexColumn } from '../../../../layout';
|
||||||
import { FC, useCallback, useMemo } from 'react';
|
import { AchievementDetailsView } from '../achievement-details/AchievementDetailsView';
|
||||||
import { LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../../../api';
|
import { AchievementListView } from '../achievement-list/AchievementListView';
|
||||||
import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView';
|
|
||||||
import { useAchievementsContext } from '../../context/AchievementsContext';
|
|
||||||
import { AchievementsActions } from '../../reducers/AchievementsReducer';
|
|
||||||
import { AchievementCategoryViewProps } from './AchievementCategoryView.types';
|
import { AchievementCategoryViewProps } from './AchievementCategoryView.types';
|
||||||
|
|
||||||
export const AchievementCategoryView: FC<AchievementCategoryViewProps> = props =>
|
export const AchievementCategoryView: FC<AchievementCategoryViewProps> = props =>
|
||||||
{
|
{
|
||||||
const achievementsContext = useAchievementsContext();
|
const { category = null, setAchievementSeen = null } = props;
|
||||||
|
const [ selectedAchievementId, setSelectedAchievementId ] = useState(0);
|
||||||
|
|
||||||
const { achievementsState = null, dispatchAchievementsState = null } = achievementsContext;
|
const getSelectedAchievement = useMemo(() =>
|
||||||
const { categories = null, selectedCategoryName = null, selectedAchievementId = null } = achievementsState;
|
|
||||||
|
|
||||||
const getSelectedCategory = useCallback(() =>
|
|
||||||
{
|
{
|
||||||
return categories.find(category => category.name === selectedCategoryName);
|
if(!category || !category.achievements.length) return null;
|
||||||
}, [ categories, selectedCategoryName ]);
|
|
||||||
|
|
||||||
const getAchievementImage = useCallback((achievement: AchievementData) =>
|
return category.achievements.find(existing => (existing.achievementId === selectedAchievementId));
|
||||||
|
}, [ category, selectedAchievementId ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(!achievement) return null;
|
let achievementId = 0;
|
||||||
|
|
||||||
let badgeId = achievement.badgeId;
|
if(!category || !category.achievements.length)
|
||||||
|
|
||||||
if(achievement.levelCount > 1)
|
|
||||||
{
|
{
|
||||||
badgeId = badgeId.replace(/[0-9]/g, '');
|
achievementId = 0;
|
||||||
badgeId = (badgeId + (((achievement.level - 1) > 0) ? (achievement.level - 1) : achievement.level));
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
achievementId = category.achievements[0].achievementId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return badgeId;
|
setSelectedAchievementId(achievementId);
|
||||||
}, []);
|
}, [ category ]);
|
||||||
|
|
||||||
const selectedAchievement = useMemo(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(!getSelectedCategory()) return null;
|
if(!getSelectedAchievement || !getSelectedAchievement.unseen) return;
|
||||||
|
|
||||||
return getSelectedCategory().achievements.find(achievement => achievement.achievementId === selectedAchievementId);
|
setAchievementSeen(category.code, getSelectedAchievement.achievementId);
|
||||||
}, [ getSelectedCategory, selectedAchievementId ]);
|
}, [ category, getSelectedAchievement, setAchievementSeen ]);
|
||||||
|
|
||||||
const selectAchievement = useCallback((id: number) =>
|
|
||||||
{
|
|
||||||
dispatchAchievementsState({
|
|
||||||
type: AchievementsActions.SELECT_ACHIEVEMENT,
|
|
||||||
payload: {
|
|
||||||
selectedAchievementId: id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [ dispatchAchievementsState ]);
|
|
||||||
|
|
||||||
|
if(!category) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="d-flex flex-column h-100">
|
<NitroLayoutFlexColumn className="justify-content-between h-100" gap={ 2 }>
|
||||||
<div className="bg-primary rounded p-2 d-flex align-items-center mb-2">
|
<AchievementListView achievements={ category.achievements } selectedAchievementId={ selectedAchievementId } setSelectedAchievementId={ setSelectedAchievementId } />
|
||||||
<h5 className="m-0 me-2 w-100">{ LocalizeText('quests.' + selectedCategoryName + '.name') }</h5>
|
{ getSelectedAchievement &&
|
||||||
<div>IMAGE</div>
|
<AchievementDetailsView achievement={ getSelectedAchievement } /> }
|
||||||
</div>
|
</NitroLayoutFlexColumn>
|
||||||
{ selectedAchievement && <div className="bg-secondary rounded p-2 mb-3 d-flex gap-2 align-items-center">
|
|
||||||
<div className="achievement-image">
|
|
||||||
<BadgeImageView badgeCode={ getAchievementImage(selectedAchievement) } />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div>{ LocalizeBadgeName(selectedAchievement.badgeId) }</div>
|
|
||||||
<div>{ LocalizeBadgeDescription(selectedAchievement.badgeId) }</div>
|
|
||||||
</div>
|
|
||||||
</div> }
|
|
||||||
<div className="achievements">
|
|
||||||
<div className="row row-cols-4">
|
|
||||||
{ getSelectedCategory().achievements.map((achievement, index) =>
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
<div key={ index } className="col mb-3">
|
|
||||||
<div className={'achievement border border-2 rounded d-flex flex-column justify-content-center align-items-center p-2' + classNames({ ' active': selectedAchievementId === achievement.achievementId, ' gray': achievement.progress === 0 })} onClick={() => selectAchievement(achievement.achievementId)}>
|
|
||||||
<BadgeImageView badgeCode={ getAchievementImage(achievement) } />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}) }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,7 @@
|
|||||||
|
import { AchievementCategory } from '../../common/AchievementCategory';
|
||||||
|
|
||||||
export class AchievementCategoryViewProps
|
export class AchievementCategoryViewProps
|
||||||
{}
|
{
|
||||||
|
category: AchievementCategory;
|
||||||
|
setAchievementSeen: (code: string, achievementId: number) => void;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user