diff --git a/backend/src/api/controllers/result.ts b/backend/src/api/controllers/result.ts index 2403dd158..f50c5545a 100644 --- a/backend/src/api/controllers/result.ts +++ b/backend/src/api/controllers/result.ts @@ -388,7 +388,10 @@ export async function addResult( const { funbox, bailedOut } = result; const validResultCriteria = - (funbox === "none" || funbox === "plus_one" || funbox === "plus_two") && + (funbox === "none" || + funbox === "plus_one" || + funbox === "plus_two" || + funbox === "plus_three") && !bailedOut && user.banned !== true && user.lbOptOut !== true && diff --git a/backend/src/api/schemas/config-schema.ts b/backend/src/api/schemas/config-schema.ts index 1ef254b94..5c3009630 100644 --- a/backend/src/api/schemas/config-schema.ts +++ b/backend/src/api/schemas/config-schema.ts @@ -87,8 +87,9 @@ const CONFIG_SCHEMA = joi.object({ paceCaretCustomSpeed: joi.number().min(0), repeatedPace: joi.boolean(), pageWidth: joi.string().valid("100", "125", "150", "200", "max"), - chartAccuracy: joi.boolean(), - chartStyle: joi.string().valid("line", "scatter"), + accountChart: joi.array().items(joi.string()), + chartAccuracy: joi.boolean().optional(), //remove after a bit + chartStyle: joi.string().valid("line", "scatter").optional(), //remove after a bit minWpm: joi.string().valid("off", "custom"), minWpmCustomSpeed: joi.number().min(0), highlightMode: joi.string().valid("off", "letter", "word"), diff --git a/backend/src/constants/funbox.ts b/backend/src/constants/funbox.ts index 376f7e068..88b1e9948 100644 --- a/backend/src/constants/funbox.ts +++ b/backend/src/constants/funbox.ts @@ -75,6 +75,10 @@ const Funboxes: Record = { canGetPb: true, difficultyLevel: 0, }, + plus_three: { + canGetPb: true, + difficultyLevel: 0, + }, read_ahead_easy: { canGetPb: true, difficultyLevel: 1, diff --git a/frontend/src/styles/account.scss b/frontend/src/styles/account.scss index c67b1a787..9fddefbf4 100644 --- a/frontend/src/styles/account.scss +++ b/frontend/src/styles/account.scss @@ -231,28 +231,16 @@ color: var(--sub-color); margin-top: 1rem; display: grid; - grid-template-columns: auto 300px; + grid-template-columns: auto 400px; align-items: center; .text { height: min-content; } .buttons { + font-size: 0.75rem; display: grid; gap: 0.5rem; - .smoothing { - display: grid; - gap: 0.25rem; - grid-template-columns: 1fr auto; - grid-template-rows: auto auto; - justify-items: start; - color: var(--text-color); - .title { - color: var(--text-color); - } - input[type="range"] { - grid-column: 1/3; - } - } + grid-template-columns: 1fr 1fr 1fr; } } .chart { diff --git a/frontend/src/styles/z_media-queries.scss b/frontend/src/styles/z_media-queries.scss index d50f923ca..e5767306f 100644 --- a/frontend/src/styles/z_media-queries.scss +++ b/frontend/src/styles/z_media-queries.scss @@ -448,6 +448,13 @@ } } + .pageAccount { + .group.chart .below { + grid-template-columns: 1fr; + gap: 0.5rem; + } + } + .pageSettings { .section.themes .tabContent.customTheme { } @@ -496,10 +503,6 @@ display: none; } } - .group.chart .below { - grid-template-columns: 1fr; - gap: 0.5rem; - } .group.topFilters .buttonsAndTitle .buttons { display: grid; justify-content: unset; diff --git a/frontend/src/ts/config.ts b/frontend/src/ts/config.ts index 2088730b5..daf6bfc2b 100644 --- a/frontend/src/ts/config.ts +++ b/frontend/src/ts/config.ts @@ -299,32 +299,64 @@ export function setBlindMode(blind: boolean, nosave?: boolean): boolean { return true; } -export function setChartAccuracy( - chartAccuracy: boolean, +export function setAccountChart( + array: MonkeyTypes.AccountChart, nosave?: boolean ): boolean { - if (!isConfigValueValid("chart accuracy", chartAccuracy, ["boolean"])) { + if ( + !isConfigValueValid("account chart", array, [["on", "off"], "stringArray"]) + ) { return false; } - config.chartAccuracy = chartAccuracy; - saveToLocalStorage("chartAccuracy", nosave); - ConfigEvent.dispatch("chartAccuracy", config.chartAccuracy); + config.accountChart = array; + saveToLocalStorage("accountChart", nosave); + ConfigEvent.dispatch("accountChart", config.accountChart); return true; } -export function setChartStyle( - chartStyle: MonkeyTypes.ChartStyle, +export function setAccountChartAccuracy( + value: boolean, nosave?: boolean ): boolean { - if (!isConfigValueValid("chart style", chartStyle, [["line", "scatter"]])) { + if (!isConfigValueValid("account chart accuracy", value, ["boolean"])) { return false; } - config.chartStyle = chartStyle; - saveToLocalStorage("chartStyle", nosave); - ConfigEvent.dispatch("chartStyle", config.chartStyle); + config.accountChart[0] = value ? "on" : "off"; + saveToLocalStorage("accountChart", nosave); + ConfigEvent.dispatch("accountChart", config.accountChart); + + return true; +} + +export function setAccountChartAvg10( + value: boolean, + nosave?: boolean +): boolean { + if (!isConfigValueValid("account chart avg 10", value, ["boolean"])) { + return false; + } + + config.accountChart[1] = value ? "on" : "off"; + saveToLocalStorage("accountChart", nosave); + ConfigEvent.dispatch("accountChart", config.accountChart); + + return true; +} + +export function setAccountChartAvg100( + value: boolean, + nosave?: boolean +): boolean { + if (!isConfigValueValid("account chart avg 100", value, ["boolean"])) { + return false; + } + + config.accountChart[2] = value ? "on" : "off"; + saveToLocalStorage("accountChart", nosave); + ConfigEvent.dispatch("accountChart", config.accountChart); return true; } @@ -1824,8 +1856,7 @@ export function apply( setPaceCaretCustomSpeed(configObj.paceCaretCustomSpeed, true); setRepeatedPace(configObj.repeatedPace, true); setPageWidth(configObj.pageWidth, true); - setChartAccuracy(configObj.chartAccuracy, true); - setChartStyle(configObj.chartStyle, true); + setAccountChart(configObj.accountChart, true); setMinBurst(configObj.minBurst, true); setMinBurstCustomSpeed(configObj.minBurstCustomSpeed, true); setMinWpm(configObj.minWpm, true); diff --git a/frontend/src/ts/constants/default-config.ts b/frontend/src/ts/constants/default-config.ts index e27ecb0bc..318da4c72 100644 --- a/frontend/src/ts/constants/default-config.ts +++ b/frontend/src/ts/constants/default-config.ts @@ -69,8 +69,7 @@ export default { paceCaretCustomSpeed: 100, repeatedPace: true, pageWidth: "125", - chartAccuracy: true, - chartStyle: "line", + accountChart: ["on", "on", "on"], minWpm: "off", minWpmCustomSpeed: 100, highlightMode: "letter", diff --git a/frontend/src/ts/controllers/chart-controller.ts b/frontend/src/ts/controllers/chart-controller.ts index 019739f35..ce9d2e6d3 100644 --- a/frontend/src/ts/controllers/chart-controller.ts +++ b/frontend/src/ts/controllers/chart-controller.ts @@ -243,38 +243,76 @@ export let accountHistoryActiveIndex: number; export const accountHistory: ChartWithUpdateColors< "line", - MonkeyTypes.HistoryChartData[] | MonkeyTypes.AccChartData[], + | MonkeyTypes.HistoryChartData[] + | MonkeyTypes.AccChartData[] + | MonkeyTypes.OtherChartData[], string > = new ChartWithUpdateColors($(".pageAccount #accountHistoryChart"), { type: "line", data: { - labels: [], datasets: [ { yAxisID: "wpm", - label: "wpm", - fill: false, data: [], - borderColor: "#f44336", - borderWidth: 2, - trendlineLinear: { - style: "rgba(255,105,180, .8)", - lineStyle: "dotted", - width: 4, - }, + fill: false, + borderWidth: 0, + order: 3, + }, + { + yAxisID: "pb", + data: [], + fill: false, + stepped: true, + pointRadius: 0, + pointHoverRadius: 0, + order: 4, }, { yAxisID: "acc", - label: "acc", fill: false, data: [], - borderColor: "#cccccc", - borderWidth: 2, + pointStyle: "triangle", + borderWidth: 0, + pointRadius: 3.5, + order: 3, + }, + { + yAxisID: "wpmAvgTen", + data: [], + fill: false, + pointRadius: 0, + pointHoverRadius: 0, + order: 2, + }, + { + yAxisID: "accAvgTen", + data: [], + fill: false, + pointRadius: 0, + pointHoverRadius: 0, + order: 2, + }, + { + yAxisID: "wpmAvgHundred", + data: [], + fill: false, + pointRadius: 0, + pointHoverRadius: 0, + order: 1, + }, + { + yAxisID: "accAvgHundred", + label: "accAvgHundred", + data: [], + fill: false, + pointRadius: 0, + pointHoverRadius: 0, + order: 1, }, ], }, options: { - responsive: true, + // responsive: true, maintainAspectRatio: false, hover: { mode: "nearest", @@ -283,13 +321,18 @@ export const accountHistory: ChartWithUpdateColors< scales: { x: { axis: "x", - type: "timeseries", - bounds: "ticks", + type: "linear", + min: 0, + ticks: { + stepSize: 10, + }, display: false, - offset: true, title: { + display: true, + text: "Test Number", + }, + grid: { display: false, - text: "Date", }, }, wpm: { @@ -306,21 +349,86 @@ export const accountHistory: ChartWithUpdateColors< }, position: "right", }, + pb: { + axis: "y", + beginAtZero: true, + min: 0, + ticks: { + stepSize: 10, + }, + display: false, + }, acc: { axis: "y", beginAtZero: true, + min: 0, max: 100, + reverse: true, + ticks: { + stepSize: 10, + }, display: true, - position: "left", title: { display: true, - text: "Error rate (100 - accuracy)", + text: "Accuracy", }, grid: { display: false, }, + position: "left", + }, + wpmAvgTen: { + axis: "y", + beginAtZero: true, + min: 0, + ticks: { + stepSize: 10, + }, + display: false, + grid: { + display: false, + }, + }, + accAvgTen: { + axis: "y", + beginAtZero: true, + min: 0, + reverse: true, + ticks: { + stepSize: 10, + }, + display: false, + grid: { + display: false, + }, + }, + wpmAvgHundred: { + axis: "y", + beginAtZero: true, + min: 0, + ticks: { + stepSize: 10, + }, + display: false, + grid: { + display: false, + }, + }, + accAvgHundred: { + axis: "y", + beginAtZero: true, + min: 0, + reverse: true, + ticks: { + stepSize: 10, + }, + display: false, + grid: { + display: false, + }, }, }, + plugins: { annotation: { annotations: [], @@ -329,15 +437,26 @@ export const accountHistory: ChartWithUpdateColors< animation: { duration: 250 }, // Disable the on-canvas tooltip enabled: true, + intersect: false, external: function (ctx): void { if (!ctx) return; ctx.tooltip.options.displayColors = false; }, + filter: function (tooltipItem): boolean { + return ( + tooltipItem.datasetIndex !== 1 && + tooltipItem.datasetIndex !== 3 && + tooltipItem.datasetIndex !== 4 && + tooltipItem.datasetIndex !== 5 && + tooltipItem.datasetIndex !== 6 + ); + }, callbacks: { title: function (): string { return ""; }, + beforeLabel: function (tooltipItem): string { if (tooltipItem.datasetIndex !== 0) { const resultData = tooltipItem.dataset.data[ @@ -828,21 +947,55 @@ export const miniResult: ChartWithUpdateColors< }); function updateAccuracy(): void { - accountHistory.data.datasets[1].hidden = !Config.chartAccuracy; + const accOn = Config.accountChart[0] === "on"; + const avg10On = Config.accountChart[1] === "on"; + const avg100On = Config.accountChart[2] === "on"; + + if (avg10On && avg100On) { + accountHistory.data.datasets[2].hidden = !accOn; + accountHistory.data.datasets[4].hidden = !accOn; + accountHistory.data.datasets[6].hidden = !accOn; + } else if (avg10On) { + accountHistory.data.datasets[2].hidden = !accOn; + accountHistory.data.datasets[4].hidden = !accOn; + } else if (Config.accountChart[2] === "on") { + accountHistory.data.datasets[2].hidden = !accOn; + accountHistory.data.datasets[6].hidden = !accOn; + } else { + accountHistory.data.datasets[2].hidden = !accOn; + } + (accountHistory.options as ScaleChartOptions<"line">).scales["acc"].display = - Config.chartAccuracy; + accOn; accountHistory.update(); } -function updateStyle(): void { - if (Config.chartStyle == "scatter") { - accountHistory.data.datasets[0].showLine = false; - accountHistory.data.datasets[1].showLine = false; +function updateAverage10(): void { + const accOn = Config.accountChart[0] === "on"; + const avg10On = Config.accountChart[1] === "on"; + + if (accOn) { + accountHistory.data.datasets[3].hidden = !avg10On; + accountHistory.data.datasets[4].hidden = !avg10On; } else { - accountHistory.data.datasets[0].showLine = true; - accountHistory.data.datasets[1].showLine = true; + accountHistory.data.datasets[3].hidden = !avg10On; } accountHistory.updateColors(); + accountHistory.update(); +} + +function updateAverage100(): void { + const accOn = Config.accountChart[0] === "on"; + const avg100On = Config.accountChart[2] === "on"; + + if (accOn) { + accountHistory.data.datasets[5].hidden = !avg100On; + accountHistory.data.datasets[6].hidden = !avg100On; + } else { + accountHistory.data.datasets[5].hidden = !avg100On; + } + accountHistory.updateColors(); + accountHistory.update(); } export async function updateColors< @@ -866,6 +1019,7 @@ export async function updateColors< const color = isPb ? textcolor : maincolor; return color; }; + if (chart.data.datasets[1]) { chart.data.datasets[1].borderColor = subcolor; } @@ -910,6 +1064,60 @@ export async function updateColors< )[1].pointBackgroundColor = subcolor; } } + if (chart.data.datasets.length === 2) { + chart.data.datasets[1].borderColor = (): string => { + const color = subcolor; + return color; + }; + } + + if (chart.data.datasets.length === 7) { + chart.data.datasets[2].borderColor = (): string => { + const color = subcolor; + return color; + }; + const avg10On = Config.accountChart[1] === "on"; + const avg100On = Config.accountChart[2] === "on"; + + const text02 = Misc.blendTwoHexColors(bgcolor, textcolor, 0.2); + const main02 = Misc.blendTwoHexColors(bgcolor, maincolor, 0.2); + const main04 = Misc.blendTwoHexColors(bgcolor, maincolor, 0.4); + + const sub02 = Misc.blendTwoHexColors(bgcolor, subcolor, 0.2); + const sub04 = Misc.blendTwoHexColors(bgcolor, subcolor, 0.4); + + const [ + wpmDataset, + pbDataset, + accDataset, + ao10wpmDataset, + ao10accDataset, + ao100wpmDataset, + ao100accDataset, + ] = chart.data.datasets as ChartDataset<"line", TData>[]; + + if (avg10On && avg100On) { + wpmDataset.pointBackgroundColor = main02; + pbDataset.borderColor = text02; + accDataset.pointBackgroundColor = sub02; + ao10wpmDataset.borderColor = main04; + ao10accDataset.borderColor = sub04; + ao100wpmDataset.borderColor = maincolor; + ao100accDataset.borderColor = subcolor; + } else if ((avg10On && !avg100On) || (!avg10On && avg100On)) { + pbDataset.borderColor = text02; + wpmDataset.pointBackgroundColor = main04; + accDataset.pointBackgroundColor = sub04; + ao10wpmDataset.borderColor = maincolor; + ao100wpmDataset.borderColor = maincolor; + ao10accDataset.borderColor = subcolor; + ao100accDataset.borderColor = subcolor; + } else { + pbDataset.borderColor = text02; + wpmDataset.pointBackgroundColor = maincolor; + accDataset.pointBackgroundColor = subcolor; + } + } const chartScaleOptions = chart.options as ScaleChartOptions; Object.keys(chartScaleOptions.scales).forEach((scaleID) => { @@ -923,10 +1131,6 @@ export async function updateColors< chart.data.datasets[0] .trendlineLinear as TrendlineLinearPlugin.TrendlineLinearOptions ).style = subcolor; - ( - chart.data.datasets[1] - .trendlineLinear as TrendlineLinearPlugin.TrendlineLinearOptions - ).style = subcolor; } catch {} ( @@ -958,7 +1162,10 @@ export function updateAllChartColors(): void { } ConfigEvent.subscribe((eventKey, eventValue) => { - if (eventKey === "chartAccuracy") updateAccuracy(); - if (eventKey === "chartStyle") updateStyle(); + if (eventKey === "accountChart") { + updateAccuracy(); + updateAverage10(); + updateAverage100(); + } if (eventKey === "fontFamily") setDefaultFontFamily(eventValue as string); }); diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index e12b61e3a..d40b2aa0e 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -196,53 +196,17 @@ export function reset(): void { ChartController.accountActivity.data.datasets[1].data = []; ChartController.accountHistory.data.datasets[0].data = []; ChartController.accountHistory.data.datasets[1].data = []; + ChartController.accountHistory.data.datasets[2].data = []; + ChartController.accountHistory.data.datasets[3].data = []; + ChartController.accountHistory.data.datasets[4].data = []; + ChartController.accountHistory.data.datasets[5].data = []; + ChartController.accountHistory.data.datasets[6].data = []; } let totalSecondsFiltered = 0; let chartData: MonkeyTypes.HistoryChartData[] = []; let accChartData: MonkeyTypes.AccChartData[] = []; -export function smoothHistory(factor: number): void { - const smoothedWpmData = Misc.smooth( - chartData.map((a) => a.y), - factor - ); - const smoothedAccData = Misc.smooth( - accChartData.map((a) => a.y), - factor - ); - - const chartData2 = chartData.map((a, i) => { - const ret = Object.assign({}, a); - ret.y = smoothedWpmData[i]; - return ret; - }); - - const accChartData2 = accChartData.map((a, i) => { - const ret = Object.assign({}, a); - ret.y = smoothedAccData[i]; - return ret; - }); - - ChartController.accountHistory.data.datasets[0].data = chartData2; - ChartController.accountHistory.data.datasets[1].data = accChartData2; - - if (chartData2.length || accChartData2.length) { - ChartController.accountHistory.options.animation = false; - ChartController.accountHistory.update(); - delete ChartController.accountHistory.options.animation; - } -} - -async function applyHistorySmoothing(): Promise { - const smoothing = $( - ".pageAccount .content .below .smoothing input" - ).val() as string; - $(".pageAccount .content .below .smoothing .value").text(smoothing); - smoothHistory(parseInt(smoothing)); - await Misc.sleep(0); -} - function fillContent(): void { LoadingPage.updateText("Displaying stats..."); LoadingPage.updateBar(100); @@ -310,6 +274,9 @@ function fillContent(): void { filteredResults = []; $(".pageAccount .history table tbody").empty(); + + let testNum = DB.getSnapshot()?.results?.length || 0; + DB.getSnapshot()?.results?.forEach( (result: MonkeyTypes.Result) => { // totalSeconds += tt; @@ -634,7 +601,7 @@ function fillContent(): void { } chartData.push({ - x: result.timestamp, + x: testNum, y: Config.alwaysShowCPM ? Misc.roundTo2(result.wpm * 5) : result.wpm, wpm: Config.alwaysShowCPM ? Misc.roundTo2(result.wpm * 5) : result.wpm, acc: result.acc, @@ -653,11 +620,13 @@ function fillContent(): void { wpmChartData.push(result.wpm); accChartData.push({ - x: result.timestamp, - y: 100 - result.acc, + x: testNum, + y: result.acc, errorRate: 100 - result.acc, }); + testNum--; + if (result.wpm > topWpm) { const puncsctring = result.punctuation ? ",
with punctuation" : ""; const numbsctring = result.numbers @@ -759,8 +728,76 @@ function fillContent(): void { accountHistoryScaleOptions["wpm"].title.text = "Words per Minute"; } - ChartController.accountHistory.data.datasets[0].data = chartData; - ChartController.accountHistory.data.datasets[1].data = accChartData; + if (chartData.length > 0) { + chartData.reverse(); + accChartData.reverse(); + + // get pb points + let currentPb = 0; + const pb: { x: number; y: number }[] = []; + + chartData.forEach((a) => { + if (a.y > currentPb) { + currentPb = a.y; + pb.push(a); + } + }); + + // add last point to pb + const xMax = chartData.length; + + pb.push({ + x: xMax, + y: pb[pb.length - 1].y, + }); + + const avgTen = []; + const avgTenAcc = []; + const avgHundred = []; + const avgHundredAcc = []; + + for (let i = 0; i < chartData.length; i++) { + // calculate 10 subset averages + const startIdxTen = i < 10 ? 0 : i - 9; + const subsetTen = chartData.slice(startIdxTen, i + 1); + const accSubsetTen = accChartData.slice(startIdxTen, i + 1); + const avgTenValue = + subsetTen.reduce((acc, { y }) => acc + y, 0) / subsetTen.length; + const accAvgTenValue = + accSubsetTen.reduce((acc, { y }) => acc + y, 0) / subsetTen.length; + + // add values to arrays + avgTen.push({ x: i + 1, y: avgTenValue }); + avgTenAcc.push({ x: i + 1, y: accAvgTenValue }); + + // calculate 100 subset averages + const startIdxHundred = i < 100 ? 0 : i - 99; + const subsetHundred = chartData.slice(startIdxHundred, i + 1); + const accSubsetHundred = accChartData.slice(startIdxHundred, i + 1); + const avgHundredValue = + subsetHundred.reduce((acc, { y }) => acc + y, 0) / subsetHundred.length; + const accAvgHundredValue = + accSubsetHundred.reduce((acc, { y }) => acc + y, 0) / + subsetHundred.length; + + // add values to arrays + avgHundred.push({ x: i + 1, y: avgHundredValue }); + avgHundredAcc.push({ x: i + 1, y: accAvgHundredValue }); + } + + ChartController.accountHistory.data.datasets[0].data = chartData; + ChartController.accountHistory.data.datasets[1].data = pb; + ChartController.accountHistory.data.datasets[2].data = accChartData; + ChartController.accountHistory.data.datasets[3].data = avgTen; + ChartController.accountHistory.data.datasets[4].data = avgTenAcc; + ChartController.accountHistory.data.datasets[5].data = avgHundred; + ChartController.accountHistory.data.datasets[6].data = avgHundredAcc; + + accountHistoryScaleOptions["x"].max = xMax + 1; + + chartData.reverse(); + accChartData.reverse(); + } const wpms = chartData.map((r) => r.y); const minWpmChartVal = Math.min(...wpms); @@ -769,6 +806,12 @@ function fillContent(): void { // let accuracies = accChartData.map((r) => r.y); accountHistoryScaleOptions["wpm"].max = Math.floor(maxWpmChartVal) + (10 - (Math.floor(maxWpmChartVal) % 10)); + accountHistoryScaleOptions["pb"].max = + Math.floor(maxWpmChartVal) + (10 - (Math.floor(maxWpmChartVal) % 10)); + accountHistoryScaleOptions["wpmAvgTen"].max = + Math.floor(maxWpmChartVal) + (10 - (Math.floor(maxWpmChartVal) % 10)); + accountHistoryScaleOptions["wpmAvgHundred"].max = + Math.floor(maxWpmChartVal) + (10 - (Math.floor(maxWpmChartVal) % 10)); if (!Config.startGraphsAtZero) { accountHistoryScaleOptions["wpm"].min = Math.floor(minWpmChartVal); @@ -800,8 +843,6 @@ function fillContent(): void { Misc.secondsToString(Math.round(totalSecondsFiltered), true, true) ); - const wpmCpm = Config.alwaysShowCPM ? "cpm" : "wpm"; - let highestSpeed: number | string = topWpm; if (Config.alwaysShowCPM) { highestSpeed = topWpm * 5; @@ -812,6 +853,8 @@ function fillContent(): void { highestSpeed = Math.round(highestSpeed); } + const wpmCpm = Config.alwaysShowCPM ? "cpm" : "wpm"; + $(".pageAccount .highestWpm .title").text(`highest ${wpmCpm}`); $(".pageAccount .highestWpm .val").text(highestSpeed); @@ -981,12 +1024,17 @@ function fillContent(): void { Misc.roundTo2( Config.alwaysShowCPM ? wpmChangePerHour * 5 : wpmChangePerHour ) - } ${Config.alwaysShowCPM ? "cpm" : "wpm"}.` + } ${Config.alwaysShowCPM ? "cpm" : "wpm"}` ); $(".pageAccount .estimatedWordsTyped .val").text(totalEstimatedWords); - applyHistorySmoothing(); + if (chartData.length || accChartData.length) { + ChartController.accountHistory.options.animation = false; + ChartController.accountHistory.update(); + delete ChartController.accountHistory.options.animation; + } + ChartController.accountActivity.update(); ChartController.accountHistogram.update(); LoadingPage.updateBar(100, true); @@ -1117,15 +1165,15 @@ function sortAndRefreshHistory( } $(".pageAccount .toggleAccuracyOnChart").on("click", () => { - UpdateConfig.setChartAccuracy(!Config.chartAccuracy); + UpdateConfig.setAccountChartAccuracy(!(Config.accountChart[0] == "on")); }); -$(".pageAccount .toggleChartStyle").on("click", () => { - if (Config.chartStyle == "line") { - UpdateConfig.setChartStyle("scatter"); - } else { - UpdateConfig.setChartStyle("line"); - } +$(".pageAccount .toggleAverage10OnChart").on("click", () => { + UpdateConfig.setAccountChartAvg10(!(Config.accountChart[1] == "on")); +}); + +$(".pageAccount .toggleAverage100OnChart").on("click", () => { + UpdateConfig.setAccountChartAvg100(!(Config.accountChart[2] == "on")); }); $(".pageAccount .loadMoreButton").on("click", () => { @@ -1232,10 +1280,6 @@ $(".pageAccount .group.presetFilterButtons").on( } ); -$(".pageAccount .content .below .smoothing input").on("input", () => { - applyHistorySmoothing(); -}); - $(".pageAccount .content .group.aboveHistory .exportCSV").on("click", () => { Misc.downloadResultsCSV(filteredResults); }); diff --git a/frontend/src/ts/test/funbox/funbox-list.ts b/frontend/src/ts/test/funbox/funbox-list.ts index 5356fe308..c22536ac9 100644 --- a/frontend/src/ts/test/funbox/funbox-list.ts +++ b/frontend/src/ts/test/funbox/funbox-list.ts @@ -118,6 +118,11 @@ const list: MonkeyTypes.FunboxMetadata[] = [ info: "Only two future words are visible.", properties: ["changesWordsVisibility", "toPush:3", "noInfiniteDuration"], }, + { + name: "plus_three", + info: "Only three future words are visible.", + properties: ["changesWordsVisibility", "toPush:4", "noInfiniteDuration"], + }, { name: "read_ahead_easy", info: "Only the current word is invisible.", diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 022fe32bf..3a8f09c39 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -901,14 +901,25 @@ export async function init(): Promise { ?.properties?.find((fp) => fp.startsWith("toPush:")); if (funboxToPush) { wordsBound = +funboxToPush.split(":")[1]; - if ( - (Config.mode === "words" && Config.words < wordsBound) || - (Config.mode === "custom" && - !CustomText.isTimeRandom && - CustomText.word < wordsBound) - ) { + if (Config.mode === "words" && Config.words < wordsBound) { wordsBound = Config.words; } + if ( + Config.mode === "custom" && + !CustomText.isTimeRandom && + CustomText.isWordRandom && + CustomText.word < wordsBound + ) { + wordsBound = CustomText.word; + } + if ( + Config.mode === "custom" && + !CustomText.isTimeRandom && + !CustomText.isWordRandom && + CustomText.text.length < wordsBound + ) { + wordsBound = CustomText.text.length; + } } else if (Config.showAllLines) { if (Config.mode === "quote") { wordsBound = 100; @@ -1798,7 +1809,7 @@ async function saveResult( } DB.saveLocalResult(completedEvent); DB.updateLocalStats( - TestStats.restartCount + 1, + completedEvent.incompleteTests.length + 1, completedEvent.testDuration + completedEvent.incompleteTestSeconds - completedEvent.afkDuration diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 5128736cc..6d38f058c 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -54,6 +54,8 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => { updateWordsInputPosition(true); } + if (eventKey === "theme") applyBurstHeatmap(); + if (eventValue === undefined || typeof eventValue !== "boolean") return; if (eventKey === "flipTestColors") flipColors(eventValue); if (eventKey === "colorfulMode") colorful(eventValue); diff --git a/frontend/src/ts/types/types.d.ts b/frontend/src/ts/types/types.d.ts index 8e5c6fede..e911620db 100644 --- a/frontend/src/ts/types/types.d.ts +++ b/frontend/src/ts/types/types.d.ts @@ -119,7 +119,7 @@ declare namespace MonkeyTypes { type PageWidth = "100" | "125" | "150" | "200" | "max"; - type ChartStyle = "line" | "scatter"; + type AccountChart = ("off" | "on")[]; type MinimumWordsPerMinute = "off" | "custom"; @@ -177,6 +177,11 @@ declare namespace MonkeyTypes { errorRate: number; } + interface OtherChartData { + x: number; + y: number; + } + interface ActivityChartDataPoint { x: number; y: number; @@ -448,8 +453,7 @@ declare namespace MonkeyTypes { paceCaretCustomSpeed: number; repeatedPace: boolean; pageWidth: PageWidth; - chartAccuracy: boolean; - chartStyle: ChartStyle; + accountChart: AccountChart; minWpm: MinimumWordsPerMinute; minWpmCustomSpeed: number; highlightMode: HighlightMode; diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index 883216093..2cbf7f497 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -288,6 +288,7 @@ function hexToRgb(hex: string): } else { return undefined; } + return { r, g, b }; } diff --git a/frontend/static/funbox/_list.json b/frontend/static/funbox/_list.json index 6b5e01c4f..ce03cacdf 100644 --- a/frontend/static/funbox/_list.json +++ b/frontend/static/funbox/_list.json @@ -94,6 +94,11 @@ "info": "Only two future words are visible.", "canGetPb": true }, + { + "name": "plus_three", + "info": "Only three future words are visible.", + "canGetPb": true + }, { "name": "read_ahead_easy", "info": "Only the current word is invisible.", diff --git a/frontend/static/html/pages/account.html b/frontend/static/html/pages/account.html index a9547ca62..b11809418 100644 --- a/frontend/static/html/pages/account.html +++ b/frontend/static/html/pages/account.html @@ -430,21 +430,21 @@
+
-
-
smoothing
-
0
- -
- Toggle Accuracy + Accuracy
-
+
- Toggle Chart Style + Avg of 10 +
+
+ + Avg of 100
diff --git a/frontend/static/languages/_groups.json b/frontend/static/languages/_groups.json index a3a889768..e334e2217 100644 --- a/frontend/static/languages/_groups.json +++ b/frontend/static/languages/_groups.json @@ -272,7 +272,7 @@ }, { "name": "swedish", - "languages": ["swedish", "swedish_1k"] + "languages": ["swedish", "swedish_1k", "swedish_diacritics"] }, { "name": "serbian", diff --git a/frontend/static/languages/_list.json b/frontend/static/languages/_list.json index b11ef6d46..d2cca22d4 100644 --- a/frontend/static/languages/_list.json +++ b/frontend/static/languages/_list.json @@ -151,6 +151,7 @@ ,"hausa_1k" ,"swedish" ,"swedish_1k" + ,"swedish_diacritics" ,"serbian" ,"yoruba_1k" ,"swahili_1k" diff --git a/frontend/static/languages/britishenglish.json b/frontend/static/languages/britishenglish.json index 56125ab0d..2f820143c 100644 --- a/frontend/static/languages/britishenglish.json +++ b/frontend/static/languages/britishenglish.json @@ -185,6 +185,7 @@ ["neighbored", "neighboured"], ["neighbor", "neighbour"], ["neighbors", "neighbours"], + ["neighboring", "neighbouring"], ["magnetizer", "magnetiser"], ["appetizer", "appetiser"], ["hebraizing", "hebraising"], diff --git a/frontend/static/languages/english_5k.json b/frontend/static/languages/english_5k.json index 262e7ac6f..8ee09a766 100644 --- a/frontend/static/languages/english_5k.json +++ b/frontend/static/languages/english_5k.json @@ -2924,7 +2924,7 @@ "negotiation", "neighbor", "neighborhood", - "neighbouring", + "neighboring", "neither", "nerve", "nervous", diff --git a/frontend/static/languages/swedish_diacritics.json b/frontend/static/languages/swedish_diacritics.json new file mode 100644 index 000000000..c8f8527e0 --- /dev/null +++ b/frontend/static/languages/swedish_diacritics.json @@ -0,0 +1,268 @@ +{ + "name": "swedish_diacritics", + "leftToRight": true, + "bcp47": "sv-SE", + "words": [ + "blåbär", + "blågrå", + "blåräv", + "blåröd", + "blåträ", + "blåögd", + "bågsåg", + "bärvåg", + "bärår", + "böjträ", + "börsår", + "dådlös", + "därför", + "därhän", + "därpå", + "däråt", + "dödsjö", + "dödsår", + "frångå", + "fåfäng", + "fåmäld", + "fårlår", + "fåtölj", + "fänkål", + "fästmö", + "fäsör", + "förbön", + "föregå", + "förgå", + "förgås", + "förhör", + "förklä", + "förköp", + "förlåt", + "förmå", + "förmån", + "förnäm", + "förnär", + "förråa", + "förråd", + "förrän", + "förslå", + "försmå", + "förstå", + "försåt", + "försök", + "förvår", + "förväg", + "förår", + "förära", + "föräta", + "föröda", + "föröka", + "föröva", + "gråblå", + "grågås", + "gråsäl", + "gråögd", + "gåpåig", + "gåsört", + "gåtåg", + "gökärt", + "håglös", + "hålkäl", + "hålslå", + "hålsöm", + "hålväg", + "hålögd", + "hårfön", + "hårlös", + "hårnål", + "hårnät", + "håröm", + "härför", + "härpå", + "härtåg", + "härväg", + "häråt", + "häxbål", + "häxört", + "höfång", + "högblå", + "högröd", + "höhäck", + "höstså", + "hösäck", + "hövålm", + "jämväl", + "järnår", + "knähög", + "knärör", + "kåsör", + "kåsös", + "köksö", + "kölbåt", + "könlös", + "köpråd", + "köpslå", + "körspö", + "körväg", + "låglön", + "läläge", + "läroår", + "läsår", + "lättöl", + "löksås", + "lönlös", + "löshår", + "lössnö", + "lösöre", + "lövlös", + "lövsåg", + "löväng", + "måbär", + "måfå", + "mållös", + "månår", + "märsrå", + "nådeår", + "nåväl", + "närapå", + "närköp", + "nödlån", + "nödår", + "nöthår", + "påbrå", + "påbröd", + "påföra", + "pågå", + "påhäng", + "påkörd", + "pålägg", + "påläst", + "påsköl", + "påstå", + "påstöt", + "påtår", + "påtänd", + "påväxt", + "påökt", + "rådlös", + "rådslå", + "rågång", + "rårörd", + "råstål", + "råämne", + "rättså", + "rävröd", + "rödblå", + "rödkål", + "rödlök", + "rödräv", + "rödögd", + "röjsåg", + "rötägg", + "rövhål", + "sjöblå", + "sjönöd", + "sjönöt", + "sjörök", + "sjöväg", + "slösäd", + "småbåt", + "småkär", + "smålån", + "småsjö", + "småsår", + "småväg", + "småäta", + "snöbär", + "snöhög", + "snölös", + "snörök", + "sådär", + "såhär", + "sångmö", + "sånär", + "såpört", + "såväl", + "sädgås", + "säldöd", + "sälhål", + "sökväg", + "sömhål", + "sömlös", + "träbåt", + "träkåk", + "trälår", + "träpåk", + "tvåårs", + "tvärdö", + "tågrån", + "tåjärn", + "tålös", + "tårlös", + "tårögd", + "tätört", + "töföre", + "töjmån", + "tösnö", + "vrålåk", + "vågrät", + "vårdkö", + "vårlök", + "vårså", + "vårsäd", + "våtår", + "väglös", + "vägnät", + "vägrån", + "västpå", + "åbrädd", + "åbäka", + "åbäke", + "åbäkig", + "ådöma", + "åfåra", + "åhåga", + "åhöra", + "åkhöjd", + "åkpåse", + "ålgräs", + "ålägga", + "åmålit", + "ångbåt", + "ångrör", + "ångtåg", + "årgång", + "årslön", + "åskblå", + "åskrön", + "åskåda", + "åsätta", + "åtbörd", + "återfå", + "återgå", + "åtgå", + "åtgång", + "åtgärd", + "åtlöje", + "åtnöja", + "åtrå", + "äggört", + "älgört", + "ändlös", + "ändträ", + "ändå", + "ängshö", + "ärekär", + "ärelös", + "ärmhål", + "ärmlös", + "ävenså", + "öbåge", + "ödesår", + "ökänd", + "öltält", + "örtsås", + "östpå", + "övergå", + "övärld" + ] +} \ No newline at end of file diff --git a/frontend/static/layouts/_list.json b/frontend/static/layouts/_list.json index 72554b9ad..f23a7a64a 100644 --- a/frontend/static/layouts/_list.json +++ b/frontend/static/layouts/_list.json @@ -494,6 +494,17 @@ "row5": [" "] } }, + "mongolian": { + "keymapShowTopRow": true, + "type": "ansi", + "keys": { + "row1": ["=+", "№1", "-2", "\"3", "₮4", ":5", ".6", "_7", ",8", "%9", "?0", "еЕ", "щЩ"], + "row2": ["фФ", "цЦ", "уУ", "жЖ", "эЭ", "нН", "гГ", "шШ", "үҮ", "зЗ", "кК", "ъЪ", "\\|"], + "row3": ["йЙ", "ыЫ", "бБ", "өӨ", "аА", "хХ", "рР", "оО", "лЛ", "дД", "пП"], + "row4": ["яЯ", "чЧ", "ёЁ", "сС", "мМ", "иИ", "тТ", "ьЬ", "вВ", "юЮ"], + "row5": [" "] + } + }, "JCUKEN": { "keymapShowTopRow": true, "type": "ansi", @@ -740,10 +751,10 @@ "keymapShowTopRow": true, "type": "ansi", "keys": { - "row1": ["-%", "ๅ+", "/๑", "_๒", "ภ๓", "ถ๔", "ุู", "ึ฿", "ค๕", "ต๖", "จ๗", "ข๘", "ช๙"], + "row1": ["_%", "ๅ+", "/๑", "-๒", "ภ๓", "ถ๔", "ุู", "ึ฿", "ค๕", "ต๖", "จ๗", "ข๘", "ช๙"], "row2": ["ๆ๐", "ไ\"", "ำฎ", "พฑ", "ะธ", "ัํ", "ี๊", "รณ", "นฯ", "ยญ", "บฐ", "ล,", "ฃฅ"], "row3": ["ฟฤ", "หฆ", "กฏ", "ดโ", "เฌ", "้็", "่๋", "าษ", "สศ", "วซ", "ง."], - "row4": ["ฃฅ", "ผ(", "ป)", "แฉ", "อฮ", "ิฺ", "ื์", "ท?", "มฒ", "ใฬ"], + "row4": ["ผ(", "ป)", "แฉ", "อฮ", "ิฺ", "ื์", "ท?", "มฒ", "ใฬ", "ฝฦ"], "row5": [" "] } }, diff --git a/frontend/static/quotes/english.json b/frontend/static/quotes/english.json index 4577bdc09..a15e2a1b3 100644 --- a/frontend/static/quotes/english.json +++ b/frontend/static/quotes/english.json @@ -4783,12 +4783,6 @@ "id": 814, "length": 355 }, - { - "text": "There's no more harm in a kiss than shaving a monkey and pretending it's a woman.", - "source": "QI", - "id": 815, - "length": 81 - }, { "text": "The most precious treasures we have in life are the images we store in the memory banks of our brains. The sum of these stored experiences is responsible for our sense of personal identity and our sense of connectedness to those around us.", "source": "Change Your Brain, Change Your Life", @@ -6745,12 +6739,6 @@ "id": 1148, "length": 211 }, - { - "text": "They say that right after God created man, he took a rib from him and made a chick. That's actually a bit of a creation myth - the truth is, after God saw that man was good, he created another man and saw that it was all good.", - "source": "Brocabulary: The New Man-i-festo of Dude Talk", - "id": 1149, - "length": 226 - }, { "text": "When the snow is on our mind, rows and rows and lines and lines. In the haze of your fixation, we all crave for you, we all crave. And you can kill the wildest thing, but when you murder it's a sin. In the midst of your conviction, we all crave for you, we all crave.", "source": "Crave", @@ -11577,7 +11565,7 @@ }, { "text": "The world ain't all sunshine and rainbows. It's a very mean and nasty place and I don't care how tough you are, it will beat you to your knees and keep you there permanently if you let it. You, me, or nobody is gonna hit as hard as life. But it ain't about how hard you hit. It's about how hard you can get hit and keep moving forward. That's how winning is done! Now if you know what you're worth then go out and get what you're worth. But you've gotta be willing to take the hits, and not pointing fingers saying you ain't where you wanna be because of him, or her, or anybody! Cowards do that and that ain't you! You're better than that!", - "source": "Rocky IV", + "source": "Rocky Balboa (2006)", "id": 1984, "length": 640 }, @@ -15991,12 +15979,6 @@ "id": 2753, "length": 174 }, - { - "text": "I know you're coming in the night like a thief, but I've had some time alone to hone my lying technique. I know you think that I'm someone you can trust, but I'm scared I'll get scared and I swear I'll try to nail you back up.", - "source": "Jesus Christ", - "id": 2754, - "length": 226 - }, { "text": "Keep drinking coffee, stare me down across the table while I look outside. So many things I'd say if only I were able, but I just keep quiet and count the cars that pass by.", "source": "King of Anything", @@ -28039,12 +28021,6 @@ "id": 4844, "length": 281 }, - { - "text": "The oracles of God foretold the rising of an Antichrist in the Christian Church: and in the Pope of Rome, all the characteristics of that Antichrist are so marvelously answered that if any who read the Scriptures do not see it, there is a marvelous blindness upon them.", - "source": "Prophetic Faith of Our Fathers Vol 3", - "id": 4845, - "length": 269 - }, { "text": "We didn't start the fire. It was always burning since the world's been turning. We didn't start the fire. No, we didn't light it, but we tried to fight it.", "source": "We Didn't Start the Fire", diff --git a/frontend/static/quotes/spanish.json b/frontend/static/quotes/spanish.json index 8cb076fa5..11be9c739 100644 --- a/frontend/static/quotes/spanish.json +++ b/frontend/static/quotes/spanish.json @@ -762,6 +762,24 @@ "source": "Marshal D. Teach - One Piece", "length": 150, "id": 126 + }, + { + "text": "Ayer tuve un amor que hoy me abandonó porque no me quería. Fue tanta la ilusión por hacerla feliz pero todo fue en vano.", + "source": "Manuel Jiménez. Máximo Fonseca - Ayer y Hoy", + "length": 120, + "id": 127 + }, + { + "text": "Ódiame por piedad yo te lo pido, ódiame sin medida ni clemencia. Odio quiero más que indiferencia, porque el rencor hiere menos que el olvido.", + "source": "Rafael Otero López - Ódiame", + "length": 142, + "id": 128 + }, + { + "text": "Dicen que con el tiempo los recuerdos se esfuman. Se ahonda en el olvido lo que fue una pasión, Mentira, cuando mueras y bajas a mi tumba verás que aún por ti arde la llama de mi amor.", + "source": "Luis Aguirre Pinto - Reminiscencias", + "length": 184, + "id": 129 } ] }