From 4def9db23f36b476e55a05b7bf7e54a02be433fb Mon Sep 17 00:00:00 2001 From: alexsparkes Date: Wed, 29 May 2024 11:24:32 +0100 Subject: [PATCH] refactor: overview & stats to functional components --- src/features/misc/sections/Overview.jsx | 206 +++++------- src/features/stats/api/stats.js | 3 +- src/features/stats/options/StatsOptions.jsx | 344 +++++++++----------- src/scss/_toast.scss | 4 +- 4 files changed, 256 insertions(+), 301 deletions(-) diff --git a/src/features/misc/sections/Overview.jsx b/src/features/misc/sections/Overview.jsx index 3d421e2a..617bd354 100644 --- a/src/features/misc/sections/Overview.jsx +++ b/src/features/misc/sections/Overview.jsx @@ -1,9 +1,9 @@ -import variables from 'config/variables'; -import { PureComponent } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { MdOutlineDragIndicator } from 'react-icons/md'; import { sortableContainer, sortableElement } from '@muetab/react-sortable-hoc'; import { toast } from 'react-toastify'; +import variables from 'config/variables'; import Greeting from './overview_skeletons/Greeting'; import Clock from './overview_skeletons/Clock'; import Quote from './overview_skeletons/Quote'; @@ -30,53 +30,40 @@ const SortableItem = sortableElement(({ value }) => ( )); const SortableContainer = sortableContainer(({ children }) => ( - + )); -class Overview extends PureComponent { - constructor() { - super(); - this.state = { - items: JSON.parse(localStorage.getItem('order')), - news: { - title: '', - date: '', - description: '', - link: '', - linkText: '', - }, - newsDone: false, - }; - } +const Overview = () => { + const [items, setItems] = useState(() => JSON.parse(localStorage.getItem('order')) || ['greeting', 'time', 'quicklinks', 'quote', 'date', 'message']); + const [news, setNews] = useState({ + title: '', + date: '', + description: '', + link: '', + linkText: '', + }); + const [newsDone, setNewsDone] = useState(false); + const [loading, setLoading] = useState(true); - arrayMove(array, oldIndex, newIndex) { + const arrayMove = (array, oldIndex, newIndex) => { const result = Array.from(array); const [removed] = result.splice(oldIndex, 1); result.splice(newIndex, 0, removed); - return result; - } - - onSortEnd = ({ oldIndex, newIndex }) => { - this.setState({ - items: this.arrayMove(this.state.items, oldIndex, newIndex), - }); }; - reset = () => { - localStorage.setItem( - 'order', - JSON.stringify(['greeting', 'time', 'quicklinks', 'quote', 'date', 'message']), - ); - - this.setState({ - items: JSON.parse(localStorage.getItem('order')), - }); + const onSortEnd = ({ oldIndex, newIndex }) => { + setItems((prevItems) => arrayMove(prevItems, oldIndex, newIndex)); + }; + const reset = () => { + const defaultOrder = ['greeting', 'time', 'quicklinks', 'quote', 'date', 'message']; + localStorage.setItem('order', JSON.stringify(defaultOrder)); + setItems(defaultOrder); toast(variables.getMessage('toasts.reset')); }; - enabled = (setting) => { + const enabled = (setting) => { switch (setting) { case 'quicklinks': return localStorage.getItem('quicklinksenabled') === 'true'; @@ -85,7 +72,7 @@ class Overview extends PureComponent { } }; - getTab = (value) => { + const getTab = (value) => { switch (value) { case 'greeting': return ; @@ -104,94 +91,81 @@ class Overview extends PureComponent { } }; - async getNews() { - const data = await (await fetch(variables.constants.API_URL + '/news')).json(); - data.date = new window.Date(data.date).toLocaleDateString( - variables.languagecode.replace('_', '-'), - { - year: 'numeric', - month: 'long', - day: 'numeric', - }, - ); + const getNews = useCallback(async () => { + try { + const response = await fetch(`${variables.constants.API_URL}/news`); + const data = await response.json(); + data.date = new window.Date(data.date).toLocaleDateString( + variables.languagecode.replace('_', '-'), + { + year: 'numeric', + month: 'long', + day: 'numeric', + } + ); + setNews(data); + setNewsDone(true); + } catch (error) { + console.error('Failed to fetch news:', error); + } finally { + setLoading(false); + } + }, []); - this.setState({ - news: data, - newsDone: true, - }); - } + useEffect(() => { + getNews(); + }, [getNews]); - componentDidUpdate() { - localStorage.setItem('order', JSON.stringify(this.state.items)); + useEffect(() => { + localStorage.setItem('order', JSON.stringify(items)); variables.stats.postEvent('setting', 'Widget order'); EventBus.emit('refresh', 'widgets'); - } + }, [items]); - componentDidMount() { - this.getNews(); - } - - render() { - return ( - <> - - {variables.getMessage('modals.main.marketplace.product.overview')} - - {/*{variables.getMessage('modals.main.marketplace.product.overview')} - - {variables.getMessage('modals.main.settings.buttons.reset')} - */} -
-
- {variables.getMessage('modals.welcome.buttons.preview')} -
-
- {this.state.items.map((value, index) => { - if (!this.enabled(value)) { - return null; - } - - return ( -
- {' '} - {this.getTab(value)} -
- ); - })} -
-
- {/*
- {this.state.news.title} - {this.state.news.date} - {this.state.news.description} - - {this.state.news.linkText} - -
*/} -
-
- - {variables.getMessage('modals.main.settings.sections.order.title')} - - - {this.state.items.map((value, index) => { - if (!this.enabled(value)) { + return ( + <> + + {variables.getMessage('modals.main.marketplace.product.overview')} + +
+
+ {variables.getMessage('modals.welcome.buttons.preview')} +
+
+ {items.map((value, index) => { + if (!enabled(value)) { return null; } - - return ; + return ( +
+ {getTab(value)} +
+ ); })} - +
- - ); - } -} +
+ + {variables.getMessage('modals.main.settings.sections.order.title')} + + + {items.map((value, index) => { + if (!enabled(value)) { + return null; + } + return ; + })} + +
+
+ + ); +}; export { Overview as default, Overview }; diff --git a/src/features/stats/api/stats.js b/src/features/stats/api/stats.js index 1bb351b8..eb373ee6 100644 --- a/src/features/stats/api/stats.js +++ b/src/features/stats/api/stats.js @@ -8,10 +8,11 @@ export default class Stats { newAchievement.forEach((achievement) => { if (achievement) { const { name } = getLocalisedAchievementData(achievement.id); - toast.success( + toast.info( `🏆 ${variables.getMessage('modals.main.settings.sections.stats.achievement_unlocked', { name: name })}`, { icon: false, + closeButton: false }, ); } diff --git a/src/features/stats/options/StatsOptions.jsx b/src/features/stats/options/StatsOptions.jsx index c92889ea..05bf20cb 100644 --- a/src/features/stats/options/StatsOptions.jsx +++ b/src/features/stats/options/StatsOptions.jsx @@ -1,6 +1,5 @@ /* eslint-disable array-callback-return */ -import variables from 'config/variables'; -import { PureComponent } from 'react'; +import { useState, useEffect, useCallback, useMemo } from 'react'; import { MdShowChart, MdRestartAlt, MdDownload, MdAccessTime, MdLock } from 'react-icons/md'; import { FaTrophy } from 'react-icons/fa'; import { toast } from 'react-toastify'; @@ -11,202 +10,183 @@ import { Header, CustomActions } from 'components/Layout/Settings'; import { ClearModal } from './ClearModal'; import { saveFile } from 'utils/saveFile'; +import variables from 'config/variables'; import { getLocalisedAchievementData, - achievements, + achievements as initialAchievements, checkAchievements, } from 'features/stats/api/achievements'; -class Stats extends PureComponent { - constructor() { - super(); - this.state = { - stats: JSON.parse(localStorage.getItem('statsData')) || {}, - achievements, - clearmodal: false, - }; - } +const Stats = () => { + const [stats, setStats] = useState(() => JSON.parse(localStorage.getItem('statsData')) || {}); + const [achievements, setAchievements] = useState(initialAchievements); + const [clearmodal, setClearmodal] = useState(false); - updateAchievements() { - const achieved = checkAchievements(this.state.stats); - this.setState({ - achievements: achieved, - }); - } + const updateAchievements = useCallback(() => { + const achieved = checkAchievements(stats); + setAchievements(achieved); + }, [stats]); - getUnlockedCount() { - let count = 0; - this.state.achievements.forEach((achievement) => { - if (achievement.achieved) { - count++; - } - }); - return count; - } + useEffect(() => { + updateAchievements(); + }, [stats, updateAchievements]); - resetStats() { - localStorage.setItem('statsData', JSON.stringify({})); - localStorage.setItem('achievements', JSON.stringify(achievements)); - this.setState({ - stats: {}, - achievements, - clearmodal: false, - }); + const getUnlockedCount = useMemo(() => { + return achievements.filter(achievement => achievement.achieved).length; + }, [achievements]); + + const resetStats = () => { + const emptyStats = {}; + localStorage.setItem('statsData', JSON.stringify(emptyStats)); + localStorage.setItem('achievements', JSON.stringify(initialAchievements)); + setStats(emptyStats); + setClearmodal(false); toast(variables.getMessage('toasts.stats_reset')); - this.updateAchievements(); - this.forceUpdate(); - } + updateAchievements(); // Call updateAchievements to refresh achievements after reset + }; - downloadStats() { - let date = new Date(); - // Format the date as YYYY-MM-DD_HH-MM-SS - let formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}_${date.getHours().toString().padStart(2, '0')}-${date.getMinutes().toString().padStart(2, '0')}-${date.getSeconds().toString().padStart(2, '0')}`; - let filename = `mue_stats_${formattedDate}.json`; - saveFile(JSON.stringify(this.state.stats, null, 2), filename); - } + const downloadStats = () => { + const date = new Date(); + const formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}_${date.getHours().toString().padStart(2, '0')}-${date.getMinutes().toString().padStart(2, '0')}-${date.getSeconds().toString().padStart(2, '0')}`; + const filename = `mue_stats_${formattedDate}.json`; + saveFile(JSON.stringify(stats, null, 2), filename); + }; - componentDidMount() { - this.updateAchievements(); - this.forceUpdate(); - } - - render() { - const achievementElement = (key, id, achieved, timestamp) => { - const { name, description } = getLocalisedAchievementData(id); - - return ( -
- {achieved ? : } -
- {achieved ? ( - timestamp !== undefined ? ( - - {new Date(timestamp).toLocaleDateString()} - - ) : null - ) : null} - {name} - {achieved ? description : '?????'} -
-
- ); - }; - - const statsElement = (title, value) => { - return ( -
- {title} - {value} -
- ); - }; - - const STATS_SECTION = 'modals.main.settings.sections.stats'; + const AchievementElement = ({ key, id, achieved, timestamp }) => { + const { name, description } = getLocalisedAchievementData(id); return ( - <> -
- -
- this.setState({ clearmodal: false })} - isOpen={this.state.clearmodal} - className="Modal ClearModal mainModal" - overlayClassName="Overlay resetoverlay" - ariaHideApp={false} - > - this.setState({ clearmodal: false })} - resetStats={() => this.resetStats()} - /> - -
-
-
- -
-
- {statsElement( - variables.getMessage(`${STATS_SECTION}.sections.tabs_opened`), - this.state.stats['tabs-opened'] || 0, - )} - {statsElement( - variables.getMessage(`${STATS_SECTION}.sections.backgrounds_favourited`), - this.state.stats['background-favourite'] || 0, - )} - {statsElement( - variables.getMessage(`${STATS_SECTION}.sections.backgrounds_downloaded`), - this.state.stats.feature ? this.state.stats.feature['background-download'] || 0 : 0, - )} - {statsElement( - variables.getMessage(`${STATS_SECTION}.sections.quotes_favourited`), - this.state.stats.feature ? this.state.stats.feature['quoted-favourite'] || 0 : 0, - )} - {statsElement( - variables.getMessage(`${STATS_SECTION}.sections.quicklinks_added`), - this.state.stats.feature ? this.state.stats.feature['quicklink-add'] || 0 : 0, - )} - {statsElement( - variables.getMessage(`${STATS_SECTION}.sections.settings_changed`), - this.state.stats.setting ? Object.keys(this.state.stats.setting).length : 0, - )} - {statsElement( - variables.getMessage(`${STATS_SECTION}.sections.addons_installed`), - this.state.stats.marketplace && this.state.stats.marketplace['install'] - ? this.state.stats.marketplace['install'].length - : 0, - )} -
-
-
- {variables.getMessage(`${STATS_SECTION}.achievements`)} -
- - {variables.getMessage(`${STATS_SECTION}.unlocked`, { - count: this.getUnlockedCount() + '/' + this.state.achievements.length, - })} +
+ {achieved ? : } +
+ {achieved && timestamp && ( + + {new Date(timestamp).toLocaleDateString()} + )} + {name} + {achieved ? description : '?????'} +
+
+ ); + }; + + const StatsElement = ({ title, value }) => ( +
+ {title} + {value} +
+ ); + + const STATS_SECTION = 'modals.main.settings.sections.stats'; + + return ( + <> +
+ +
+ setClearmodal(false)} + isOpen={clearmodal} + className="Modal ClearModal mainModal" + overlayClassName="Overlay resetoverlay" + ariaHideApp={false} + > + setClearmodal(false)} + resetStats={resetStats} + /> + +
+
+
+
-
-
- {this.state.achievements.map((achievement, index) => { - if (achievement.achieved) { - return achievementElement( - index, - achievement.id, - achievement.achieved, - achievement.timestamp, - ); - } - })} -
- {variables.getMessage(`${STATS_SECTION}.locked`)} -
- {this.state.achievements.map((achievement, index) => { - if (!achievement.achieved) { - return achievementElement(index, achievement.id, achievement.achieved); - } - })} -
+
+ + + + + + +
- - ); - } -} +
+ {variables.getMessage(`${STATS_SECTION}.achievements`)} +
+ + {variables.getMessage(`${STATS_SECTION}.unlocked`, { count: `${getUnlockedCount}/${achievements.length}` })} + +
+
+
+ {achievements.map((achievement, index) => { + if (achievement.achieved) { + return ( + + ); + } + })} +
+ {variables.getMessage(`${STATS_SECTION}.locked`)} +
+ {achievements.map((achievement, index) => { + if (!achievement.achieved) { + return ( + + ); + } + })} +
+
+
+ + ); +}; export { Stats as default, Stats }; diff --git a/src/scss/_toast.scss b/src/scss/_toast.scss index 703896a0..0d2bc757 100644 --- a/src/scss/_toast.scss +++ b/src/scss/_toast.scss @@ -32,10 +32,10 @@ @extend %basic; } -.Toastify__toast--success { +.Toastify__toast--info { @extend %basic; } -.Toastify__progress-bar--success { +.Toastify__progress-bar--info { background-color: gold !important; } \ No newline at end of file