From d12a82acddfb11e78dcfb465446ed6cf69d5e147 Mon Sep 17 00:00:00 2001 From: HF Date: Mon, 26 Sep 2022 13:01:52 +0200 Subject: [PATCH] more stats --- src/components/ChatMessage.jsx | 6 +- src/components/Rankings.jsx | 244 +++++++++------------- src/core/Ranks.js | 21 ++ src/core/chartSettings.js | 364 +++++++++++++++++++++++++++++++++ src/data/redis/ranks.js | 90 +++++++- src/store/actions/fetch.js | 2 +- src/store/actions/index.js | 4 + src/store/reducers/ranks.js | 19 +- src/store/selectors/gui.js | 9 + src/store/selectors/ranks.js | 1 + src/store/sharedReducers.js | 2 +- 11 files changed, 598 insertions(+), 164 deletions(-) create mode 100644 src/core/chartSettings.js create mode 100644 src/store/selectors/gui.js diff --git a/src/components/ChatMessage.jsx b/src/components/ChatMessage.jsx index 31786fd..d842453 100644 --- a/src/components/ChatMessage.jsx +++ b/src/components/ChatMessage.jsx @@ -7,10 +7,10 @@ import { setBrightness, getDateTimeString, } from '../core/utils'; +import { selectIsDarkMode } from '../store/selectors/gui'; import { parseParagraph } from '../core/MarkdownParser'; -const selectStyle = (state) => state.gui.style.indexOf('dark') !== -1; function ChatMessage({ name, @@ -20,9 +20,7 @@ function ChatMessage({ ts, openCm, }) { - const isDarkMode = useSelector( - selectStyle, - ); + const isDarkMode = useSelector(selectIsDarkMode); const refEmbed = useRef(); const isInfo = (name === 'info'); diff --git a/src/components/Rankings.jsx b/src/components/Rankings.jsx index e114985..74cbd5d 100644 --- a/src/components/Rankings.jsx +++ b/src/components/Rankings.jsx @@ -17,11 +17,24 @@ import { Tooltip, Legend, LineController, + ArcElement, } from 'chart.js'; -import { Line } from 'react-chartjs-2'; +import { Line, Pie } from 'react-chartjs-2'; +import { selectIsDarkMode } from '../store/selectors/gui'; import { selectStats } from '../store/selectors/ranks'; -import { colorFromText } from '../core/utils'; +import { + getCHistChartOpts, + getCHistChartData, + getOnlineStatsOpts, + getOnlineStatsData, + getHistChartOpts, + getHistChartData, + getCPieOpts, + getCPieData, + getPDailyStatsOpts, + getPDailyStatsData, +} from '../core/chartSettings'; ChartJS.register( CategoryScale, @@ -32,88 +45,10 @@ ChartJS.register( Tooltip, Legend, LineController, + // for pie chart + ArcElement, ); -const options = { - responsive: true, - aspectRatio: 1.2, - color: '#e6e6e6', - scales: { - x: { - grid: { - drawBorder: false, - color: '#656565', - }, - ticks: { - color: '#e6e6e6', - }, - }, - y: { - grid: { - drawBorder: false, - color: '#656565', - }, - ticks: { - color: '#e6e6e6', - }, - }, - }, - interaction: { - mode: 'index', - intersect: false, - }, - plugins: { - legend: { - position: 'top', - }, - title: { - display: true, - color: '#e6e6e6', - text: 'Top 10 Countries [pxls / day]', - }, - }, -}; - -const onlineStatsOptions = { - responsive: true, - color: '#e6e6e6', - scales: { - x: { - grid: { - drawBorder: false, - color: '#656565', - }, - ticks: { - color: '#e6e6e6', - }, - }, - y: { - grid: { - drawBorder: false, - color: '#656565', - }, - ticks: { - color: '#e6e6e6', - }, - }, - }, - interaction: { - mode: 'index', - intersect: false, - }, - plugins: { - legend: { - display: false, - }, - title: { - display: true, - color: '#e6e6e6', - text: 'Players Online per full hour', - }, - }, -}; - - const Rankings = () => { const [area, setArea] = useState('total'); const [ @@ -124,82 +59,80 @@ const Rankings = () => { onlineStats, cHistStats, histStats, + pDailyStats, ] = useSelector(selectStats, shallowEqual); + const isDarkMode = useSelector(selectIsDarkMode); const cHistData = useMemo(() => { if (area !== 'charts') { return null; } - const dataPerCountry = {}; - const labels = []; - let ts = Date.now(); - let c = cHistStats.length; - while (c) { - const dAmount = cHistStats.length - c; - c -= 1; - // x label - const date = new Date(ts); - labels.unshift(`${date.getUTCMonth() + 1} / ${date.getUTCDate()}`); - ts -= 1000 * 3600 * 24; - // y data per country - const dailyRanks = cHistStats[c]; - for (let i = 0; i < dailyRanks.length; i += 1) { - const { cc, px } = dailyRanks[i]; - if (!dataPerCountry[cc]) { - dataPerCountry[cc] = []; - } - const countryDat = dataPerCountry[cc]; - while (countryDat.length < dAmount) { - countryDat.push(null); - } - countryDat.push(px); - } - } - console.log(dataPerCountry); - const countries = Object.keys(dataPerCountry); - const datasets = countries.map((cc) => { - const color = colorFromText(`${cc}${cc}${cc}${cc}${cc}`); - return { - label: cc, - data: dataPerCountry[cc], - borderColor: color, - backgroundColor: color, - }; - }); - return { - labels, - datasets, - }; + return getCHistChartData(cHistStats); }, [area, cHistStats]); + const cHistOpts = useMemo(() => { + if (area !== 'charts') { + return null; + } + return getCHistChartOpts(isDarkMode); + }, [area, isDarkMode]); + const onlineData = useMemo(() => { if (area !== 'charts') { return null; } - const labels = []; - const data = []; - let ts = Date.now(); - let c = onlineStats.length; - while (c) { - c -= 1; - const date = new Date(ts); - const hours = date.getHours(); - const key = hours || `${date.getMonth() + 1} / ${date.getDate()}`; - labels.unshift(String(key)); - ts -= 1000 * 3600; - data.push(onlineStats[c]); - } - return { - labels, - datasets: [{ - label: 'Players', - data, - borderColor: '#3fadda', - backgroundColor: '#3fadda', - }], - }; + return getOnlineStatsData(onlineStats); }, [area, onlineStats]); + const onlineOpts = useMemo(() => { + if (area !== 'charts') { + return null; + } + return getOnlineStatsOpts(isDarkMode); + }, [area, isDarkMode]); + + const histData = useMemo(() => { + if (area !== 'charts') { + return null; + } + return getHistChartData(histStats); + }, [area, histStats]); + + const histOpts = useMemo(() => { + if (area !== 'charts') { + return null; + } + return getHistChartOpts(isDarkMode); + }, [area, isDarkMode]); + + const pDailyData = useMemo(() => { + if (area !== 'charts') { + return null; + } + return getPDailyStatsData(pDailyStats); + }, [area, pDailyStats]); + + const pDailyOpts = useMemo(() => { + if (area !== 'charts') { + return null; + } + return getPDailyStatsOpts(isDarkMode); + }, [area, isDarkMode]); + + const cPieData = useMemo(() => { + if (area !== 'countries') { + return null; + } + return getCPieData(dailyCRanking); + }, [area, dailyCRanking]); + + const cPieOpts = useMemo(() => { + if (area !== 'countries') { + return null; + } + return getCPieOpts(isDarkMode); + }, [area, isDarkMode]); + return ( <>
@@ -210,7 +143,7 @@ const Rankings = () => { (area === 'total') ? 'modallink selected' : 'modallink' } onClick={() => setArea('total')} - >{t`Total`} + > {t`Total`} { (area === 'today') ? 'modallink selected' : 'modallink' } onClick={() => setArea('today')} - >{t`Today`} + > {t`Today`} { (area === 'yesterday') ? 'modallink selected' : 'modallink' } onClick={() => setArea('yesterday')} - >{t`Yesterday`} + > {t`Yesterday`} { (area === 'countries') ? 'modallink selected' : 'modallink' } onClick={() => setArea('countries')} - >{t`Countries Today`} + > {t`Countries Today`} { (area === 'charts') ? 'modallink selected' : 'modallink' } onClick={() => setArea('charts')} - >{t`Chart`} + > {t`Charts`}
+
+ {(area === 'countries') && ( + <> + +
+ + )} {(['total', 'today', 'yesterday', 'countries'].includes(area)) && ( { )} {(area === 'charts') && ( <> - - + + + + )}

diff --git a/src/core/Ranks.js b/src/core/Ranks.js index 7f78d5a..d7033f6 100644 --- a/src/core/Ranks.js +++ b/src/core/Ranks.js @@ -12,6 +12,10 @@ import { getCountryDailyHistory, getCountryRanks, getTopDailyHistory, + storeHourlyPixelsPlaced, + getHourlyPixelStats, + getDailyPixelStats, + populateDailyTotal, } from '../data/redis/ranks'; import socketEvents from '../socket/socketEvents'; import logger from './logger'; @@ -22,13 +26,24 @@ import { DailyCron, HourlyCron } from '../utils/cron'; class Ranks { constructor() { this.ranks = { + // ranking today of users by pixels dailyRanking: [], + // ranking of users by pixels ranking: [], + // ranking today of countries by pixels dailyCRanking: [], + // yesterdays ranking of users by pixels prevTop: [], + // online user amount by hour onlineStats: [], + // ranking of countries by day cHistStats: [], + // ranking of users by day histStats: [], + // pixels placed by hour + pHourlyStats: [], + // pixels placed by day + pDailyStats: [], }; /* * we go through socketEvents for sharding @@ -42,6 +57,7 @@ class Ranks { } async initialize() { + await populateDailyTotal(); try { let someRanks = await Ranks.dailyUpdateRanking(); this.ranks = { @@ -95,10 +111,12 @@ class Ranks { const cHistStats = await getCountryDailyHistory(); const histStats = await getTopDailyHistory(); histStats.users = await populateRanking(histStats.users); + const pHourlyStats = await getHourlyPixelStats(); const ret = { onlineStats, cHistStats, histStats, + pHourlyStats, }; if (socketEvents.amIImportant()) { // only main shard sends to others @@ -111,8 +129,10 @@ class Ranks { const prevTop = await populateRanking( await getPrevTop(), ); + const pDailyStats = await getDailyPixelStats(); const ret = { prevTop, + pDailyStats, }; if (socketEvents.amIImportant()) { // only main shard sends to others @@ -127,6 +147,7 @@ class Ranks { } const amount = socketEvents.onlineCounter.total; await storeOnlinUserAmount(amount); + await storeHourlyPixelsPlaced(); await Ranks.hourlyUpdateRanking(); } diff --git a/src/core/chartSettings.js b/src/core/chartSettings.js new file mode 100644 index 0000000..7eb2c4b --- /dev/null +++ b/src/core/chartSettings.js @@ -0,0 +1,364 @@ +import { t } from 'ttag'; +import { colorFromText } from './utils'; + +export function getCHistChartOpts(isDarkMode) { + const options = { + responsive: true, + aspectRatio: 1.2, + scales: { + x: { + grid: { + drawBorder: false, + }, + }, + y: { + grid: { + drawBorder: false, + }, + }, + }, + interaction: { + mode: 'index', + intersect: false, + }, + plugins: { + legend: { + position: 'top', + }, + title: { + display: true, + text: t`Top 10 Countries [pxls / day]`, + }, + }, + }; + if (isDarkMode) { + const sColor = '#e6e6e6'; + const lColor = '#656565'; + options.color = sColor; + options.scales.x.ticks = { + color: sColor, + }; + options.scales.x.grid.color = lColor; + options.scales.y.ticks = { + color: sColor, + }; + options.scales.y.grid.color = lColor; + options.plugins.title.color = sColor; + } + return options; +} + +export function getCHistChartData(cHistStats) { + const dataPerCountry = {}; + const labels = []; + let ts = Date.now(); + let c = cHistStats.length; + while (c) { + const dAmount = cHistStats.length - c; + c -= 1; + // x label + const date = new Date(ts); + labels.unshift(`${date.getUTCMonth() + 1} / ${date.getUTCDate()}`); + ts -= 1000 * 3600 * 24; + // y data per country + const dailyRanks = cHistStats[c]; + for (let i = 0; i < dailyRanks.length; i += 1) { + const { cc, px } = dailyRanks[i]; + if (!dataPerCountry[cc]) { + dataPerCountry[cc] = []; + } + const countryDat = dataPerCountry[cc]; + while (countryDat.length < dAmount) { + countryDat.push(null); + } + countryDat.push(px); + } + } + const countries = Object.keys(dataPerCountry); + const datasets = countries.map((cc) => { + const color = colorFromText(`${cc}${cc}${cc}${cc}${cc}`); + return { + label: cc, + data: dataPerCountry[cc], + borderColor: color, + backgroundColor: color, + }; + }); + return { + labels, + datasets, + }; +} + +export function getOnlineStatsOpts(isDarkMode) { + const options = { + responsive: true, + scales: { + x: { + grid: { + drawBorder: false, + }, + }, + y: { + grid: { + drawBorder: false, + }, + }, + }, + interaction: { + mode: 'index', + intersect: false, + }, + plugins: { + legend: { + display: false, + }, + title: { + display: true, + text: t`Players Online per full hour`, + }, + }, + }; + if (isDarkMode) { + const sColor = '#e6e6e6'; + const lColor = '#656565'; + options.color = sColor; + options.scales.x.ticks = { + color: sColor, + }; + options.scales.x.grid.color = lColor; + options.scales.y.ticks = { + color: sColor, + }; + options.scales.y.grid.color = lColor; + options.plugins.title.color = sColor; + } + return options; +} + +export function getOnlineStatsData(onlineStats) { + const labels = []; + const data = []; + let ts = Date.now(); + let c = onlineStats.length; + while (c) { + c -= 1; + const date = new Date(ts); + const hours = date.getHours(); + const key = hours || `${date.getMonth() + 1} / ${date.getDate()}`; + labels.unshift(String(key)); + ts -= 1000 * 3600; + data.push(onlineStats[c]); + } + return { + labels, + datasets: [{ + label: 'Players', + data, + borderColor: '#3fadda', + backgroundColor: '#3fadda', + }], + }; +} + +export function getHistChartOpts(isDarkMode) { + const options = { + responsive: true, + aspectRatio: 1.4, + scales: { + x: { + grid: { + drawBorder: false, + }, + }, + y: { + grid: { + drawBorder: false, + }, + }, + }, + interaction: { + mode: 'nearest', + axis: 'xy', + intersect: false, + }, + plugins: { + legend: { + display: false, + }, + title: { + display: true, + text: t`Top 10 Players [pxls / day]`, + }, + }, + }; + if (isDarkMode) { + const sColor = '#e6e6e6'; + const lColor = '#656565'; + options.color = sColor; + options.scales.x.ticks = { + color: sColor, + }; + options.scales.x.grid.color = lColor; + options.scales.y.ticks = { + color: sColor, + }; + options.scales.y.grid.color = lColor; + options.plugins.title.color = sColor; + } + return options; +} + +export function getHistChartData(histStats) { + const { users, stats } = histStats; + const dataPerUser = {}; + users.forEach((u) => { dataPerUser[u.id] = { name: u.name, data: [] }; }); + const labels = []; + let ts = Date.now(); + let c = stats.length; + // skipping todays dataset + while (c > 1) { + const dAmount = stats.length - c; + c -= 1; + // x label + ts -= 1000 * 3600 * 24; + const date = new Date(ts); + labels.unshift(`${date.getUTCMonth() + 1} / ${date.getUTCDate()}`); + // y data per user + const dailyRanks = stats[c]; + for (let i = 0; i < dailyRanks.length; i += 1) { + const { id, px } = dailyRanks[i]; + const userDat = dataPerUser[id].data; + while (userDat.length < dAmount) { + userDat.push(null); + } + userDat.push(px); + } + } + const userIds = Object.keys(dataPerUser); + const datasets = userIds.map((id) => { + const { name, data } = dataPerUser[id]; + const color = colorFromText(name); + return { + label: name, + data, + borderColor: color, + backgroundColor: color, + }; + }); + return { + labels, + datasets, + }; +} + +export function getCPieOpts(isDarkMode) { + const options = { + responsive: true, + plugins: { + legend: { + display: false, + }, + title: { + display: true, + text: t`Countries by Pixels Today`, + }, + }, + }; + if (isDarkMode) { + const sColor = '#e6e6e6'; + options.plugins.title.color = sColor; + } + return options; +} + +export function getCPieData(dailyCRanking) { + const labels = []; + const data = []; + const backgroundColor = []; + dailyCRanking.forEach((r) => { + const { cc, px } = r; + labels.push(cc); + data.push(px); + const color = colorFromText(`${cc}${cc}${cc}${cc}${cc}`); + backgroundColor.push(color); + }); + return { + labels, + datasets: [{ + label: '# of Pixels', + data, + backgroundColor, + borderWidth: 1, + }], + }; +} + +export function getPDailyStatsOpts(isDarkMode) { + const options = { + responsive: true, + scales: { + x: { + grid: { + drawBorder: false, + }, + }, + y: { + grid: { + drawBorder: false, + }, + }, + }, + interaction: { + mode: 'index', + intersect: false, + }, + plugins: { + legend: { + display: false, + }, + title: { + display: true, + text: t`Total Pixels placed per day`, + }, + }, + }; + if (isDarkMode) { + const sColor = '#e6e6e6'; + const lColor = '#656565'; + options.color = sColor; + options.scales.x.ticks = { + color: sColor, + }; + options.scales.x.grid.color = lColor; + options.scales.y.ticks = { + color: sColor, + }; + options.scales.y.grid.color = lColor; + options.plugins.title.color = sColor; + } + return options; +} + +export function getPDailyStatsData(pDailyStats) { + const labels = []; + const data = []; + let ts = Date.now(); + let c = pDailyStats.length; + while (c) { + c -= 1; + ts -= 1000 * 3600 * 24; + const date = new Date(ts); + labels.unshift(`${date.getUTCMonth() + 1} / ${date.getUTCDate()}`); + data.push(pDailyStats[c]); + } + return { + labels, + datasets: [{ + label: 'Pixels', + data, + borderColor: '#3fadda', + backgroundColor: '#3fadda', + }], + }; +} diff --git a/src/data/redis/ranks.js b/src/data/redis/ranks.js index 304462c..1ddc1c8 100644 --- a/src/data/redis/ranks.js +++ b/src/data/redis/ranks.js @@ -11,6 +11,9 @@ const PREV_DAY_TOP_KEY = 'prankd'; const DAY_STATS_RANKS_KEY = 'ds'; const CDAY_STATS_RANKS_KEY = 'cds'; const ONLINE_CNTR_KEY = 'tonl'; +const PREV_HOURLY_PLACED_KEY = 'tmph'; +const HOURLY_PXL_CNTR_KEY = 'thpx'; +const DAILY_PXL_CNTR_KEY = 'tdpx'; /* * get pixelcount and ranking @@ -116,11 +119,11 @@ export async function getPrevTop() { */ export async function storeOnlinUserAmount(amount) { await client.lPush(ONLINE_CNTR_KEY, String(amount)); - await client.lTrim(ONLINE_CNTR_KEY, 0, 14 * 24); + await client.lTrim(ONLINE_CNTR_KEY, 0, 7 * 24); } /* - * get list of online counters + * get list of online counters per hour */ export async function getOnlineUserStats() { let onlineStats = await client.lRange(ONLINE_CNTR_KEY, 0, -1); @@ -128,6 +131,63 @@ export async function getOnlineUserStats() { return onlineStats; } +/* + * calculate sum of scores of zset + * do NOT use it for large seets + */ +async function sumZSet(key) { + const ranks = await client.zRangeWithScores(key, 0, -1); + let total = 0; + ranks.forEach((r) => { total += Number(r.score); }); + return total; +} + +/* + * save hourly pixels placed by substracting + * the current daily total pixels set with the ones of an hour ago + */ +export async function storeHourlyPixelsPlaced() { + const tsNow = Date.now(); + const prevData = await client.get(PREV_HOURLY_PLACED_KEY); + let prevTs; + let prevSum; + if (prevData) { + [prevTs, prevSum] = prevData.split(',').map((z) => Number(z)); + } + + let curSum = await sumZSet(DAILY_CRANKED_KEY); + await client.set(PREV_HOURLY_PLACED_KEY, `${tsNow},${curSum}`); + + if (prevTs && prevTs > tsNow - 1000 * 3600 * 1.5) { + if (prevSum > curSum) { + // assume day change, add amount of yesterday + const dateKey = getDateKeyOfTs(tsNow - 1000 * 3600 * 24); + curSum += await sumZSet(`${CDAY_STATS_RANKS_KEY}:${dateKey}`); + } + const hourlyPixels = curSum - prevSum; + await client.lPush(HOURLY_PXL_CNTR_KEY, String(hourlyPixels)); + await client.lTrim(HOURLY_PXL_CNTR_KEY, 0, 7 * 24); + } +} + +/* + * get list of pixels placed per hour + */ +export async function getHourlyPixelStats() { + let pxlStats = await client.lRange(HOURLY_PXL_CNTR_KEY, 0, -1); + pxlStats = pxlStats.map((s) => Number(s)); + return pxlStats; +} + +/* + * get list of pixels placed per day + */ +export async function getDailyPixelStats() { + let pxlStats = await client.lRange(DAILY_PXL_CNTR_KEY, 0, -1); + pxlStats = pxlStats.map((s) => Number(s)); + return pxlStats; +} + /* * get top 10 of daily pixels over the past days */ @@ -167,6 +227,20 @@ export async function getTopDailyHistory() { }; } +/* + * for populating past daily totals + */ +export async function populateDailyTotal() { + await client.del(DAILY_PXL_CNTR_KEY); + for (let i = 14; i > 0; i -= 1) { + const ts = Date.now() - 1000 * 3600 * 24 * i; + const key = `${CDAY_STATS_RANKS_KEY}:${getDateKeyOfTs(ts)}`; + // eslint-disable-next-line no-await-in-loop + const sum = await sumZSet(key); + client.lPush(DAILY_PXL_CNTR_KEY, String(sum)); + } +} + /* * get top 10 countries over the past days */ @@ -209,12 +283,24 @@ export async function resetDailyRanks() { const dateKey = getDateKeyOfTs( Date.now() - 1000 * 3600 * 24, ); + // daily user rank await client.rename( DAILY_RANKED_KEY, `${DAY_STATS_RANKS_KEY}:${dateKey}`, ); + // daily country rank await client.rename( DAILY_CRANKED_KEY, `${CDAY_STATS_RANKS_KEY}:${dateKey}`, ); + // daily pixel counter + const sum = await sumZSet(`${CDAY_STATS_RANKS_KEY}:${dateKey}`); + await client.lPush(DAILY_PXL_CNTR_KEY, String(sum)); + await client.lTrim(DAILY_PXL_CNTR_KEY, 0, 28); + // purge old data + const purgeDateKey = getDateKeyOfTs( + Date.now() - 1000 * 3600 * 24 * 21, + ); + await client.del(`${DAY_STATS_RANKS_KEY}:${purgeDateKey}`); + await client.del(`${CDAY_STATS_RANKS_KEY}:${purgeDateKey}`); } diff --git a/src/store/actions/fetch.js b/src/store/actions/fetch.js index 1d5a219..ac1166f 100644 --- a/src/store/actions/fetch.js +++ b/src/store/actions/fetch.js @@ -324,7 +324,7 @@ export function requestDeleteAccount(password) { export function requestRankings() { return makeAPIGETRequest( - 'https://pixelplanet.fun/ranking', + '/ranking', false, ); } diff --git a/src/store/actions/index.js b/src/store/actions/index.js index 1b87f58..9339fba 100644 --- a/src/store/actions/index.js +++ b/src/store/actions/index.js @@ -279,6 +279,8 @@ export function receiveStats( onlineStats, cHistStats, histStats, + pDailyStats, + pHourlyStats, } = rankings; return { type: 'REC_STATS', @@ -289,6 +291,8 @@ export function receiveStats( onlineStats, cHistStats, histStats, + pDailyStats, + pHourlyStats, }; } diff --git a/src/store/reducers/ranks.js b/src/store/reducers/ranks.js index c09d6a4..db45923 100644 --- a/src/store/reducers/ranks.js +++ b/src/store/reducers/ranks.js @@ -21,7 +21,9 @@ const initialState = { prevTop: [], onlineStats: [], cHistStats: [], - histStats: [], + histStats: { users: [], stats: [] }, + pDailyStats: [], + pHourlyStats: [], }; export default function ranks( @@ -83,11 +85,10 @@ export default function ranks( onlineStats, cHistStats, histStats, + pDailyStats, + pHourlyStats, } = action; - const lastFetch = Date.now(); - return { - ...state, - lastFetch, + const newStats = { totalRanking, totalDailyRanking, dailyCRanking, @@ -95,6 +96,14 @@ export default function ranks( onlineStats, cHistStats, histStats, + pDailyStats, + pHourlyStats, + }; + const lastFetch = Date.now(); + return { + ...state, + lastFetch, + ...newStats, }; } diff --git a/src/store/selectors/gui.js b/src/store/selectors/gui.js new file mode 100644 index 0000000..b812aa0 --- /dev/null +++ b/src/store/selectors/gui.js @@ -0,0 +1,9 @@ +/* + * selectors related to gui + */ + +/* eslint-disable import/prefer-default-export */ + +export const selectIsDarkMode = (state) => ( + state.gui.style.indexOf('dark') !== -1 +); diff --git a/src/store/selectors/ranks.js b/src/store/selectors/ranks.js index ad09e9d..24b0004 100644 --- a/src/store/selectors/ranks.js +++ b/src/store/selectors/ranks.js @@ -12,4 +12,5 @@ export const selectStats = (state) => [ state.ranks.onlineStats, state.ranks.cHistStats, state.ranks.histStats, + state.ranks.pDailyStats, ]; diff --git a/src/store/sharedReducers.js b/src/store/sharedReducers.js index 0027cc9..dfd5057 100644 --- a/src/store/sharedReducers.js +++ b/src/store/sharedReducers.js @@ -15,7 +15,7 @@ import canvas from './reducers/canvas'; import chat from './reducers/chat'; import fetching from './reducers/fetching'; -export const CURRENT_VERSION = 12; +export const CURRENT_VERSION = 14; export const migrate = (state, version) => { // eslint-disable-next-line no-underscore-dangle