From 86f64dfc981cece7afc1ff64ce23cd33ce1510a8 Mon Sep 17 00:00:00 2001 From: alexsparkes Date: Sat, 16 Mar 2024 23:28:35 +0000 Subject: [PATCH] feat: achievement improvements - Added achievement notification - Show locked achievements - Fixed quote padding - Fixed date picker layout Co-authored-by: David Ralph Co-authored-by: Isaac --- .../MainModal/scss/settings/_main.scss | 2 + .../scss/settings/modules/tabs/_stats.scss | 6 ++ src/features/misc/sections/Stats.jsx | 67 +++++++------------ src/features/quote/Quote.jsx | 37 +++++----- src/i18n/locales/achievements/en_GB.json | 2 +- src/utils/achievements/condition.js | 50 ++++++++++++++ src/utils/achievements/index.js | 20 ++++-- src/utils/data/achievements.json | 2 +- src/utils/stats.js | 15 +++++ 9 files changed, 137 insertions(+), 64 deletions(-) create mode 100644 src/utils/achievements/condition.js diff --git a/src/components/Elements/MainModal/scss/settings/_main.scss b/src/components/Elements/MainModal/scss/settings/_main.scss index 615ebde4..e56dc9cb 100644 --- a/src/components/Elements/MainModal/scss/settings/_main.scss +++ b/src/components/Elements/MainModal/scss/settings/_main.scss @@ -51,6 +51,8 @@ input { /* date picker */ &[type='date'] { width: 260px; + display: flex; + flex-flow: column; @include themed { background: t($modal-sidebar); diff --git a/src/components/Elements/MainModal/scss/settings/modules/tabs/_stats.scss b/src/components/Elements/MainModal/scss/settings/modules/tabs/_stats.scss index 23826c5b..5890342a 100644 --- a/src/components/Elements/MainModal/scss/settings/modules/tabs/_stats.scss +++ b/src/components/Elements/MainModal/scss/settings/modules/tabs/_stats.scss @@ -33,6 +33,12 @@ } .achievements { + display: flex; + flex-flow: column; + gap: 15px; +} + +.achievementsGrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); grid-gap: 10px; diff --git a/src/features/misc/sections/Stats.jsx b/src/features/misc/sections/Stats.jsx index 18bcf9f7..896bf3df 100644 --- a/src/features/misc/sections/Stats.jsx +++ b/src/features/misc/sections/Stats.jsx @@ -10,7 +10,7 @@ import { Header, CustomActions } from 'components/Layout/Settings'; import { saveFile } from 'utils/saveFile'; -import { translations, achievements } from 'utils/achievements'; +import { getLocalisedAchievementData, achievements, checkAchievements } from 'utils/achievements'; class Stats extends PureComponent { constructor() { @@ -21,29 +21,10 @@ class Stats extends PureComponent { }; } - getAchievements() { - const achievements = this.state.achievements; - achievements.forEach((achievement) => { - switch (achievement.condition.type) { - case 'tabsOpened': - if (this.state.stats['tabs-opened'] >= achievement.condition.amount) { - achievement.achieved = true; - } - break; - case 'addonInstall': - if (this.state.stats.marketplace) { - if (this.state.stats.marketplace['install'] >= achievement.condition.amount) { - achievement.achieved = true; - } - } - break; - default: - break; - } - }); - + updateAchievements() { + const achieved = checkAchievements(this.state.stats); this.setState({ - achievements, + achievements: achieved, }); } @@ -59,11 +40,13 @@ class Stats extends PureComponent { resetStats() { localStorage.setItem('statsData', JSON.stringify({})); + localStorage.setItem('achievements', JSON.stringify(achievements)); this.setState({ stats: {}, + achievements, }); toast(variables.getMessage('toasts.stats_reset')); - this.getAchievements(); + this.updateAchievements(); this.forceUpdate(); } @@ -75,31 +58,21 @@ class Stats extends PureComponent { saveFile(JSON.stringify(this.state.stats, null, 2), filename); } - getLocalisedAchievementData(id) { - const localised = translations[variables.languagecode][id] || - translations.en_GB[id] || { name: id, description: '' }; - - return { - name: localised.name, - description: localised.description, - }; - } - componentDidMount() { - this.getAchievements(); + this.updateAchievements(); this.forceUpdate(); } render() { const achievementElement = (key, id, achieved) => { - const { name, description } = this.getLocalisedAchievementData(id); + const { name, description } = getLocalisedAchievementData(id); return (
{name} - {description} + {achieved ? description : '?????'}
); @@ -213,11 +186,21 @@ class Stats extends PureComponent {
- {this.state.achievements.map((achievement, index) => { - if (achievement.achieved) { - return achievementElement(index, achievement.id, achievement.achieved); - } - })} +
+ {this.state.achievements.map((achievement, index) => { + if (achievement.achieved) { + return achievementElement(index, achievement.id, achievement.achieved); + } + })} +
+ Locked +
+ {this.state.achievements.map((achievement, index) => { + if (!achievement.achieved) { + return achievementElement(index, achievement.id, achievement.achieved); + } + })} +
diff --git a/src/features/quote/Quote.jsx b/src/features/quote/Quote.jsx index 8be09032..b4ada133 100644 --- a/src/features/quote/Quote.jsx +++ b/src/features/quote/Quote.jsx @@ -493,22 +493,27 @@ class Quote extends PureComponent { loading )} -
- {this.state.authorOccupation !== 'Unknown' && this.state.authorlink !== null ? ( - - - - {' '} - - ) : null} - {this.state.copy} {this.state.share} {this.state.favourited} -
+ {(this.state.authorOccupation !== 'Unknown' && this.state.authorlink !== null) || + this.state.copy || + this.state.share || + this.state.favourited ? ( +
+ {this.state.authorOccupation !== 'Unknown' && this.state.authorlink !== null ? ( + + + + {' '} + + ) : null} + {this.state.copy} {this.state.share} {this.state.favourited} +
+ ) : null} )} diff --git a/src/i18n/locales/achievements/en_GB.json b/src/i18n/locales/achievements/en_GB.json index 4d63ede3..e1d94027 100644 --- a/src/i18n/locales/achievements/en_GB.json +++ b/src/i18n/locales/achievements/en_GB.json @@ -47,4 +47,4 @@ "name": "System Overload", "description": "Installed 50 add-ons" } -} \ No newline at end of file +} diff --git a/src/utils/achievements/condition.js b/src/utils/achievements/condition.js new file mode 100644 index 00000000..25ea4504 --- /dev/null +++ b/src/utils/achievements/condition.js @@ -0,0 +1,50 @@ +import { achievements } from './index'; + +export function checkAchievements(stats) { + achievements.forEach((achievement) => { + switch (achievement.condition.type) { + case 'tabsOpened': + if (stats['tabs-opened'] >= achievement.condition.amount) { + achievement.achieved = true; + } + break; + case 'addonInstall': + if (stats.marketplace) { + if (stats.marketplace['install'] >= achievement.condition.amount) { + achievement.achieved = true; + } + } + break; + default: + break; + } + }); + + return achievements; +} + +export function newAchievements(stats) { + // calculate new achievements + const oldAchievements = JSON.parse(localStorage.getItem('achievements')) || []; + const checkedAchievements = checkAchievements(stats); + const newAchievements = []; + + checkedAchievements.forEach((achievement) => { + if (achievement.achieved && !oldAchievements.includes(achievement.id)) { + newAchievements.push(achievement); + } + }); + + // add timestamp to new achievements + newAchievements.forEach((achievement) => { + achievement.timestamp = Date.now(); + }); + + // save new achievements to local storage + localStorage.setItem( + 'achievements', + JSON.stringify([...oldAchievements, ...newAchievements.map((achievement) => achievement.id)]), + ); + + return newAchievements; +} diff --git a/src/utils/achievements/index.js b/src/utils/achievements/index.js index 9e49e634..a43acbcc 100644 --- a/src/utils/achievements/index.js +++ b/src/utils/achievements/index.js @@ -1,3 +1,5 @@ +import variables from 'config/variables'; + import de_DE from 'i18n/locales/achievements/de_DE.json'; import en_GB from 'i18n/locales/achievements/en_GB.json'; import en_US from 'i18n/locales/achievements/en_US.json'; @@ -14,6 +16,8 @@ import pt_BR from 'i18n/locales/achievements/pt_BR.json'; import achievements from 'utils/data/achievements.json'; +import { checkAchievements, newAchievements } from './condition'; + const translations = { de_DE, en_GB, @@ -30,7 +34,15 @@ const translations = { pt_BR, }; -export { - achievements, - translations -}; \ No newline at end of file +// todo: clean this up and condition.js too +function getLocalisedAchievementData(id) { + const localised = translations[variables.languagecode][id] || + translations.en_GB[id] || { name: id, description: '' }; + + return { + name: localised.name, + description: localised.description, + }; +} + +export { achievements, checkAchievements, newAchievements, getLocalisedAchievementData }; diff --git a/src/utils/data/achievements.json b/src/utils/data/achievements.json index 18a1cc75..62caefe3 100644 --- a/src/utils/data/achievements.json +++ b/src/utils/data/achievements.json @@ -42,7 +42,7 @@ } }, { - "id": "808sandtabs", + "id": "808s", "condition": { "type": "tabsOpened", "amount": 808 diff --git a/src/utils/stats.js b/src/utils/stats.js index bbd43c49..12529e77 100644 --- a/src/utils/stats.js +++ b/src/utils/stats.js @@ -1,4 +1,17 @@ +import { newAchievements, getLocalisedAchievementData } from './achievements'; +import { toast } from 'react-toastify'; + export default class Stats { + static async achievementTrigger(stats) { + const newAchievement = newAchievements(stats); + newAchievement.forEach((achievement) => { + if (achievement) { + const { name } = getLocalisedAchievementData(achievement.id); + toast(`Achievement Unlocked: ${name}`); + } + }); + } + /** * It takes two arguments, a type and a name, and then it increments the value of the name in the type * object in localStorage @@ -24,6 +37,7 @@ export default class Stats { data[type][value] = data[type][value] + 1; } localStorage.setItem('statsData', JSON.stringify(data)); + this.achievementTrigger(data); } /** @@ -33,5 +47,6 @@ export default class Stats { const data = JSON.parse(localStorage.getItem('statsData')); data['tabs-opened'] = data['tabs-opened'] + 1 || 1; localStorage.setItem('statsData', JSON.stringify(data)); + this.achievementTrigger(data); } }