implement color, opacity change config to a single signal

This commit is contained in:
Miodec 2026-01-09 13:05:53 +01:00
parent b3adfcc04f
commit 048c4a18d0
11 changed files with 330 additions and 440 deletions

View file

@ -124,14 +124,8 @@
<div id="memoryTimer">Time left to memorise all words: 0s</div>
<div id="layoutfluidTimer">Time left to memorise all words: 0s</div>
<div id="testModesNotice"></div>
<div id="liveStatsTextTop" class="timerMain">
<div class="wrapper">
<div class="timerProgress hidden">1:00</div>
</div>
</div>
<div id="liveStatsMini" class="full-width timerMain">
<div class="time hidden">1:00</div>
</div>
<div id="liveStatsTextTop"></div>
<div id="liveStatsMini" class="full-width"></div>
<div id="wordsWrapper" class="content-grid full-width" translate="no">
<textarea
id="wordsInput"

View file

@ -124,7 +124,6 @@
display: grid;
font-size: 10rem;
opacity: 0;
width: 0;
height: 0;
justify-self: center;
@ -163,8 +162,8 @@
}
}
#liveStatsTextBottom.timerMain,
#liveStatsTextTop.timerMain {
#liveStatsTextBottom .colorMain,
#liveStatsTextTop .colorMain {
color: var(--main-color);
}
@ -172,8 +171,8 @@
background: var(--main-color);
}
#liveStatsTextBottom.timerSub,
#liveStatsTextTop.timerSub {
#liveStatsTextBottom .colorSub,
#liveStatsTextTop .colorSub {
color: var(--sub-color);
}
@ -181,8 +180,8 @@
background: var(--sub-color);
}
#liveStatsTextBottom.timerText,
#liveStatsTextTop.timerText {
#liveStatsTextBottom .colorText,
#liveStatsTextTop .colorText {
color: var(--text-color);
}
@ -1474,68 +1473,36 @@
}
}
#liveStatsMini {
width: 0;
justify-content: start;
height: 0;
margin-left: 0.25em;
display: flex;
margin-top: -1.25em;
color: black;
.wrapper {
width: 0;
justify-content: start;
height: 0;
display: flex;
margin-top: -1.25em;
color: black;
div {
font-size: 1em;
line-height: 1em;
}
div {
font-size: 1em;
line-height: 1em;
}
.time,
.speed,
.acc {
margin-right: 0.5em;
}
.time,
.speed,
.acc {
margin-right: 0.5em;
}
.time,
.speed,
.acc,
.burst {
//opacity: 0;
}
&.colorMain {
color: var(--main-color);
}
&.timerMain {
color: var(--main-color);
}
&.colorSub {
color: var(--sub-color);
}
&.timerSub {
color: var(--sub-color);
}
&.timerText {
color: var(--text-color);
}
&.size125 {
margin-top: -1.75rem;
font-size: 1.25rem;
line-height: 1.25rem;
}
&.size15 {
margin-top: -2rem;
font-size: 1.5rem;
line-height: 1.5rem;
}
&.size2 {
margin-top: -2.5rem;
font-size: 2rem;
line-height: 2rem;
}
&.size3 {
margin-top: -3.5rem;
font-size: 3rem;
line-height: 3rem;
}
&.size4 {
margin-top: -4.5rem;
font-size: 4rem;
line-height: 4rem;
&.colorText {
color: var(--text-color);
}
}
}
}

View file

@ -1,15 +1,30 @@
import { render } from "solid-js/web";
import { qsr } from "../utils/dom";
import { LiveStats } from "./test/LiveStats";
import { getAcc, getBurst, getWpm } from "../signals/test";
import {
TextLiveStatsBottom,
MiniLiveStats,
TextLiveStatsTop,
} from "./test/LiveStats";
import { getAcc, getBurst, getProgress, getWpm } from "../signals/test";
export function mountComponents(): void {
render(
() => <LiveStats mode="mini" wpm={getWpm} acc={getAcc} burst={getBurst} />,
() => (
<MiniLiveStats
progress={getProgress}
wpm={getWpm}
acc={getAcc}
burst={getBurst}
/>
),
qsr("#liveStatsMini").native,
);
render(
() => <LiveStats mode="text" wpm={getWpm} acc={getAcc} burst={getBurst} />,
() => <TextLiveStatsBottom wpm={getWpm} acc={getAcc} burst={getBurst} />,
qsr("#liveStatsTextBottom").native,
);
render(
() => <TextLiveStatsTop progress={getProgress} />,
qsr("#liveStatsTextTop").native,
);
}

View file

@ -1,28 +1,28 @@
import { Accessor, JSXElement } from "solid-js";
import {
getLiveAccStyle,
getLiveBurstStyle,
getLiveSpeedStyle,
} from "../../signals/config";
import { isFocused } from "../../test/focus";
import {
useVisibilityAnimation,
VisibilityAnimationOptions,
} from "../../hooks/useVisibilityAnimation";
import { useRefWithUtils } from "../../hooks/useRefWithUtils";
import { statsVisible } from "../../signals/test";
import { getTestTime, statsVisible } from "../../signals/test";
import Config from "../../config";
import * as CustomText from "../../test/custom-text";
import { getConfigSignal } from "../../signals/config";
function Stat(props: {
value: Accessor<string>;
visibilityOptions: Accessor<VisibilityAnimationOptions>;
class?: string;
style?: Record<string, string>;
}): JSXElement {
const [ref, element] = useRefWithUtils<HTMLDivElement>();
useVisibilityAnimation(element, props.visibilityOptions);
return (
<div ref={ref} class={props.class}>
<div ref={ref} class={props.class} style={props.style}>
{props.value()}
</div>
);
@ -37,8 +37,49 @@ const getStatsVisible = (
});
};
export function LiveStats(props: {
mode: "mini" | "text";
function getStatsColorClass(): string {
const cfg = getConfigSignal();
const configValue = cfg.timerColor;
if (configValue === "main") {
return "colorMain";
} else if (configValue === "sub") {
return "colorSub";
} else if (configValue === "text") {
return "colorText";
} else if (configValue === "black") {
return "colorBlack";
}
return "";
}
function getFlashTimerOpacity(): string {
let opacity = "1";
const time = getTestTime();
let maxtime = Config.time;
if (Config.mode === "custom" && CustomText.getLimitMode() === "time") {
maxtime = CustomText.getLimitValue();
}
const timedTest =
Config.mode === "time" ||
(Config.mode === "custom" && CustomText.getLimitMode() === "time");
if (
timedTest &&
(getConfigSignal().timerStyle === "flash_mini" ||
getConfigSignal().timerStyle === "flash_text")
) {
if ((maxtime - time) % 15 !== 0) {
opacity = "0";
} else {
opacity = "1";
}
}
return opacity;
}
export function MiniLiveStats(props: {
progress: Accessor<string>;
wpm: Accessor<string>;
acc: Accessor<string>;
burst: Accessor<string>;
@ -50,22 +91,113 @@ export function LiveStats(props: {
};
return (
<>
<div
class="wrapper"
classList={{
[getStatsColorClass()]: true,
}}
style={{
opacity: getConfigSignal().timerOpacity,
}}
>
<Stat
class="speed"
class={"progress time"}
value={props.progress}
style={{ opacity: getFlashTimerOpacity() }}
visibilityOptions={getStatsVisible(
() =>
getConfigSignal().timerStyle === "mini" ||
getConfigSignal().timerStyle === "flash_mini",
)}
/>
<Stat
class={"speed"}
value={props.wpm}
visibilityOptions={isVisible(getLiveSpeedStyle)}
visibilityOptions={getStatsVisible(
() => getConfigSignal().liveSpeedStyle === "mini",
)}
/>
<Stat
class="acc"
class={"acc"}
value={props.acc}
visibilityOptions={isVisible(getLiveAccStyle)}
visibilityOptions={getStatsVisible(
() => getConfigSignal().liveAccStyle === "mini",
)}
/>
<Stat
class="burst"
class={"burst"}
value={props.burst}
visibilityOptions={isVisible(getLiveBurstStyle)}
visibilityOptions={getStatsVisible(
() => getConfigSignal().liveBurstStyle === "mini",
)}
/>
</>
</div>
);
}
export function TextLiveStatsTop(props: {
progress: Accessor<string>;
}): JSXElement {
return (
<div
class="wrapper"
classList={{
[getStatsColorClass()]: true,
}}
style={{
opacity: getConfigSignal().timerOpacity,
}}
>
<Stat
class={"progress time"}
value={props.progress}
style={{ opacity: getFlashTimerOpacity() }}
visibilityOptions={getStatsVisible(
() =>
getConfigSignal().timerStyle === "text" ||
getConfigSignal().timerStyle === "flash_text",
)}
/>
</div>
);
}
export function TextLiveStatsBottom(props: {
wpm: Accessor<string>;
acc: Accessor<string>;
burst: Accessor<string>;
}): JSXElement {
return (
<div
class="wrapper"
classList={{
[getStatsColorClass()]: true,
}}
style={{
opacity: getConfigSignal().timerOpacity,
}}
>
<Stat
class={"liveSpeed"}
value={props.wpm}
visibilityOptions={getStatsVisible(
() => getConfigSignal().liveSpeedStyle === "text",
)}
/>
<Stat
class={"liveAcc"}
value={props.acc}
visibilityOptions={getStatsVisible(
() => getConfigSignal().liveAccStyle === "text",
)}
/>
<Stat
class={"liveBurst"}
value={props.burst}
visibilityOptions={getStatsVisible(
() => getConfigSignal().liveBurstStyle === "text",
)}
/>
</div>
);
}

View file

@ -1,23 +1,12 @@
import { createSignal } from "solid-js";
import * as ConfigEvent from "../observables/config-event";
import config from "../config";
import { Config } from "@monkeytype/schemas/configs";
const [getLiveSpeedStyle, setLiveSpeedStyle] = createSignal(
config.liveSpeedStyle,
);
const [getLiveAccStyle, setLiveAccStyle] = createSignal(config.liveSpeedStyle);
const [getLiveBurstStyle, setLiveBurstStyle] = createSignal(
config.liveSpeedStyle,
);
const [getConfigSignal, setConfigSignal] = createSignal<Config>(config);
export { getLiveSpeedStyle, getLiveAccStyle, getLiveBurstStyle };
export { getConfigSignal };
ConfigEvent.subscribe(({ key, newValue }) => {
if (key === "liveSpeedStyle") {
setLiveSpeedStyle(newValue);
} else if (key === "liveAccStyle") {
setLiveAccStyle(newValue);
} else if (key === "liveBurstStyle") {
setLiveBurstStyle(newValue);
}
ConfigEvent.subscribe(() => {
setConfigSignal(structuredClone(config));
});

View file

@ -1,6 +1,15 @@
import { createSignal } from "solid-js";
import { VisibilityAnimationOptions } from "../hooks/useVisibilityAnimation";
import * as Time from "../states/time";
import Config from "../config";
import * as CustomText from "../test/custom-text";
import * as DateTime from "../utils/date-and-time";
import * as TestWords from "../test/test-words";
import * as TestInput from "../test/test-input";
import * as TestState from "../test/test-state";
export const [getTestTime, setTestTime] = createSignal(0);
export const [getProgress, setLiveProgress] = createSignal("");
export const [getWpm, setLiveStatWpm] = createSignal("0");
export const [getAcc, setLiveStatAcc] = createSignal("100%");
export const [getBurst, setLiveStatBurst] = createSignal("0");
@ -10,3 +19,102 @@ export const [statsVisible, setStatsVisible] =
visible: false,
animate: true,
});
function getCurrentCount(): number {
if (Config.mode === "custom" && CustomText.getLimitMode() === "section") {
return (
(TestWords.words.sectionIndexList[TestState.activeWordIndex] as number) -
1
);
} else {
return TestInput.input.getHistory().length;
}
}
export function updateProgressSignal(): void {
const time = Time.get();
if (
Config.mode === "time" ||
(Config.mode === "custom" && CustomText.getLimitMode() === "time")
) {
let maxtime = Config.time;
if (Config.mode === "custom" && CustomText.getLimitMode() === "time") {
maxtime = CustomText.getLimitValue();
}
if (Config.timerStyle === "text") {
let displayTime = DateTime.secondsToString(maxtime - time);
if (maxtime === 0) {
displayTime = DateTime.secondsToString(time);
}
setLiveProgress(displayTime);
} else if (Config.timerStyle === "flash_mini") {
let displayTime = DateTime.secondsToString(maxtime - time);
if (maxtime === 0) {
displayTime = DateTime.secondsToString(time);
}
setLiveProgress(displayTime);
} else if (Config.timerStyle === "flash_text") {
let displayTime = DateTime.secondsToString(maxtime - time);
if (maxtime === 0) {
displayTime = DateTime.secondsToString(time);
}
setLiveProgress(displayTime);
} else if (Config.timerStyle === "mini") {
let displayTime = DateTime.secondsToString(maxtime - time);
if (maxtime === 0) {
displayTime = DateTime.secondsToString(time);
}
setLiveProgress(displayTime);
}
} else if (
Config.mode === "words" ||
Config.mode === "custom" ||
Config.mode === "quote"
) {
let outof = TestWords.words.length;
if (Config.mode === "words") {
outof = Config.words;
}
if (Config.mode === "custom") {
outof = CustomText.getLimitValue();
}
if (Config.mode === "quote") {
outof = TestWords.currentQuote?.textSplit.length ?? 1;
}
if (Config.timerStyle === "text") {
if (outof === 0) {
setLiveProgress(`${TestInput.input.getHistory().length}`);
} else {
setLiveProgress(`${getCurrentCount()}/${outof}`);
}
} else if (Config.timerStyle === "flash_mini") {
if (outof === 0) {
setLiveProgress(`${TestInput.input.getHistory().length}`);
} else {
setLiveProgress(`${getCurrentCount()}/${outof}`);
}
} else if (Config.timerStyle === "flash_text") {
if (outof === 0) {
setLiveProgress(`${TestInput.input.getHistory().length}`);
} else {
setLiveProgress(`${getCurrentCount()}/${outof}`);
}
} else if (Config.timerStyle === "mini") {
if (outof === 0) {
setLiveProgress(`${TestInput.input.getHistory().length}`);
} else {
setLiveProgress(`${getCurrentCount()}/${outof}`);
}
}
} else if (Config.mode === "zen") {
if (Config.timerStyle === "text") {
setLiveProgress(`${TestInput.input.getHistory().length}`);
} else if (Config.timerStyle === "flash_mini") {
setLiveProgress(`${TestInput.input.getHistory().length}`);
} else if (Config.timerStyle === "flash_text") {
setLiveProgress(`${TestInput.input.getHistory().length}`);
} else {
setLiveProgress(`${TestInput.input.getHistory().length}`);
}
}
}

View file

@ -1,3 +1,5 @@
import { setTestTime } from "../signals/test";
let time = 0;
export function get(): number {
@ -6,8 +8,10 @@ export function get(): number {
export function set(number: number): void {
time = number;
setTestTime(time);
}
export function increment(): void {
time++;
setTestTime(time);
}

View file

@ -1,5 +1,4 @@
import * as Caret from "./caret";
import * as TimerProgress from "./timer-progress";
import * as PageTransition from "../states/page-transition";
import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame";
import { createSignal } from "solid-js";
@ -63,7 +62,6 @@ export function set(value: boolean, withCursor = false): void {
}
Caret.stopAnimation();
TimerProgress.show();
} else if (!value && state()) {
setState(false);
@ -79,7 +77,6 @@ export function set(value: boolean, withCursor = false): void {
}
Caret.startAnimation();
TimerProgress.hide();
}
});
}

View file

@ -3,7 +3,6 @@
import Config, { setConfig } from "../config";
import * as CustomText from "./custom-text";
import * as TimerProgress from "./timer-progress";
import * as TestStats from "./test-stats";
import * as TestInput from "./test-input";
import * as Monkey from "./monkey";
@ -21,7 +20,7 @@ import { clearLowFpsMode, setLowFpsMode } from "../anim";
import { createTimer } from "animejs";
import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame";
import Format from "../utils/format";
import { setLiveStatWpm } from "../signals/test";
import { setLiveStatWpm, updateProgressSignal } from "../signals/test";
let lastLoop = 0;
const newTimer = createTimer({
@ -235,7 +234,7 @@ function timerStep(): void {
});
// already using raf
TimerProgress.update();
updateProgressSignal();
setLiveStatWpm(
Format.typingSpeed(Config.blindMode ? wpmAndRaw.raw : wpmAndRaw.wpm, {
showDecimalPlaces: false,

View file

@ -16,7 +16,6 @@ import * as Hangul from "hangul-js";
import * as ResultWordHighlight from "../elements/result-word-highlight";
import * as ActivePage from "../states/active-page";
import Format from "../utils/format";
import { TimerColor, TimerOpacity } from "@monkeytype/schemas/configs";
import { convertRemToPixels } from "../utils/numbers";
import {
findSingleActiveFunboxWithFunction,
@ -33,7 +32,6 @@ import * as Numbers from "@monkeytype/util/numbers";
import * as TestStats from "./test-stats";
import * as KeymapEvent from "../observables/keymap-event";
import * as Focus from "../test/focus";
import * as TimerProgress from "../test/timer-progress";
import * as Monkey from "./monkey";
import { animate } from "animejs";
import {
@ -56,10 +54,12 @@ import * as Last10Average from "../elements/last-10-average";
import * as MemoryFunboxTimer from "./funbox/memory-funbox-timer";
import { qsr } from "../utils/dom";
import {
setLiveProgress,
setLiveStatAcc,
setLiveStatBurst,
setLiveStatWpm,
setStatsVisible,
updateProgressSignal,
} from "../signals/test";
export const updateHintsPositionDebounced = Misc.debounceUntilResolved(
@ -1586,48 +1586,6 @@ function updateLiveStatsMargin(): void {
}
}
function updateLiveStatsOpacity(value: TimerOpacity): void {
$("#barTimerProgress").css("opacity", parseFloat(value as string));
$("#liveStatsTextTop").css("opacity", parseFloat(value as string));
$("#liveStatsTextBottom").css("opacity", parseFloat(value as string));
$("#liveStatsMini").css("opacity", parseFloat(value as string));
}
function updateLiveStatsColor(value: TimerColor): void {
$("#barTimerProgress").removeClass("timerSub");
$("#barTimerProgress").removeClass("timerText");
$("#barTimerProgress").removeClass("timerMain");
$("#liveStatsTextTop").removeClass("timerSub");
$("#liveStatsTextTop").removeClass("timerText");
$("#liveStatsTextTop").removeClass("timerMain");
$("#liveStatsTextBottom").removeClass("timerSub");
$("#liveStatsTextBottom").removeClass("timerText");
$("#liveStatsTextBottom").removeClass("timerMain");
$("#liveStatsMini").removeClass("timerSub");
$("#liveStatsMini").removeClass("timerText");
$("#liveStatsMini").removeClass("timerMain");
if (value === "main") {
$("#barTimerProgress").addClass("timerMain");
$("#liveStatsTextTop").addClass("timerMain");
$("#liveStatsTextBottom").addClass("timerMain");
$("#liveStatsMini").addClass("timerMain");
} else if (value === "sub") {
$("#barTimerProgress").addClass("timerSub");
$("#liveStatsTextTop").addClass("timerSub");
$("#liveStatsTextBottom").addClass("timerSub");
$("#liveStatsMini").addClass("timerSub");
} else if (value === "text") {
$("#barTimerProgress").addClass("timerText");
$("#liveStatsTextTop").addClass("timerText");
$("#liveStatsTextBottom").addClass("timerText");
$("#liveStatsMini").addClass("timerText");
}
}
function showHideTestRestartButton(showHide: boolean): void {
if (showHide) {
$(".pageTest #restartTestButton").removeClass("hidden");
@ -1688,7 +1646,8 @@ function afterAnyTestInput(
}
if (Config.mode !== "time") {
TimerProgress.update();
// TimerProgress.update();
updateProgressSignal();
}
if (Config.keymapMode === "next") {
@ -1819,8 +1778,7 @@ export async function afterTestWordChange(
export function onTestStart(): void {
Focus.set(true);
Monkey.show();
TimerProgress.show();
TimerProgress.update();
updateProgressSignal();
setStatsVisible({
visible: true,
animate: true,
@ -1837,11 +1795,10 @@ export function onTestRestart(source: "testPage" | "resultPage"): void {
visible: false,
animate: false,
});
setLiveProgress("0");
setLiveStatWpm("0");
setLiveStatBurst("0");
setLiveStatAcc("100%");
TimerProgress.instantHide();
TimerProgress.reset();
Monkey.instantHide();
LayoutfluidFunboxTimer.instantHide();
updatePremid();
@ -1884,12 +1841,15 @@ export function onTestRestart(source: "testPage" | "resultPage"): void {
export function onTestFinish(): void {
Caret.hide();
TimerProgress.hide();
OutOfFocus.hide();
Monkey.hide();
if (Config.playSoundOnClick === "16") {
void SoundController.playFartReverb();
}
setStatsVisible({
visible: false,
animate: true,
});
}
$(".pageTest #copyWordsListButton").on("click", async () => {
@ -2000,12 +1960,6 @@ ConfigEvent.subscribe(({ key, newValue }) => {
if (key === "quickRestart") {
showHideTestRestartButton(newValue === "off");
}
if (key === "timerOpacity") {
updateLiveStatsOpacity(newValue);
}
if (key === "timerColor") {
updateLiveStatsColor(newValue);
}
if (key === "showOutOfFocusWarning" && !newValue) {
OutOfFocus.hide();
}

View file

@ -1,269 +0,0 @@
import Config from "../config";
import * as CustomText from "./custom-text";
import * as DateTime from "../utils/date-and-time";
import * as TestWords from "./test-words";
import * as TestInput from "./test-input";
import * as Time from "../states/time";
import * as TestState from "./test-state";
import * as ConfigEvent from "../observables/config-event";
import { applyReducedMotion } from "../utils/misc";
import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame";
import { animate } from "animejs";
const barEl = document.querySelector("#barTimerProgress .bar") as HTMLElement;
const barOpacityEl = document.querySelector(
"#barTimerProgress .opacityWrapper",
) as HTMLElement;
const textEl = document.querySelector(
"#liveStatsTextTop .timerProgress",
) as HTMLElement;
const miniEl = document.querySelector("#liveStatsMini .time") as HTMLElement;
export function show(): void {
if (!TestState.isActive()) return;
requestDebouncedAnimationFrame("timer-progress.show", () => {
if (Config.mode !== "zen" && Config.timerStyle === "bar") {
animate(barOpacityEl, {
opacity: [0, 1],
duration: applyReducedMotion(125),
onBegin: () => {
barOpacityEl.classList.remove("hidden");
},
});
} else if (Config.timerStyle === "text") {
animate(textEl, {
opacity: [0, 1],
duration: applyReducedMotion(125),
onBegin: () => {
textEl.classList.remove("hidden");
},
});
} else if (Config.timerStyle === "flash_mini") {
animate(miniEl, {
opacity: [0, 1],
duration: applyReducedMotion(125),
onBegin: () => {
miniEl.classList.remove("hidden");
},
});
} else if (Config.timerStyle === "flash_text") {
animate(textEl, {
opacity: [0, 1],
duration: applyReducedMotion(125),
onBegin: () => {
textEl.classList.remove("hidden");
},
});
} else if (Config.mode === "zen" || Config.timerStyle === "mini") {
animate(miniEl, {
opacity: [0, 1],
duration: applyReducedMotion(125),
onBegin: () => {
miniEl.classList.remove("hidden");
},
});
}
});
}
export function reset(): void {
requestDebouncedAnimationFrame("timer-progress.reset", () => {
let width = "0vw";
if (
Config.mode === "time" ||
(Config.mode === "custom" && CustomText.getLimitMode() === "time")
) {
width = "100vw";
}
animate(barEl, {
width,
duration: 0,
});
miniEl.textContent = "0";
textEl.textContent = "0";
});
}
export function hide(): void {
requestDebouncedAnimationFrame("timer-progress.hide", () => {
animate(barOpacityEl, {
opacity: 0,
duration: applyReducedMotion(125),
});
animate(miniEl, {
opacity: 0,
duration: applyReducedMotion(125),
onComplete: () => {
miniEl.classList.add("hidden");
},
});
animate(textEl, {
opacity: 0,
duration: applyReducedMotion(125),
onComplete: () => {
textEl.classList.add("hidden");
},
});
});
}
export function instantHide(): void {
barOpacityEl.style.opacity = "0";
miniEl.classList.add("hidden");
miniEl.style.opacity = "0";
textEl.classList.add("hidden");
textEl.style.opacity = "0";
}
function getCurrentCount(): number {
if (Config.mode === "custom" && CustomText.getLimitMode() === "section") {
return (
(TestWords.words.sectionIndexList[TestState.activeWordIndex] as number) -
1
);
} else {
return TestInput.input.getHistory().length;
}
}
export function update(): void {
requestDebouncedAnimationFrame("timer-progress.update", () => {
const time = Time.get();
if (
Config.mode === "time" ||
(Config.mode === "custom" && CustomText.getLimitMode() === "time")
) {
let maxtime = Config.time;
if (Config.mode === "custom" && CustomText.getLimitMode() === "time") {
maxtime = CustomText.getLimitValue();
}
if (Config.timerStyle === "bar") {
const percent = 100 - ((time + 1) / maxtime) * 100;
animate(barEl, {
width: percent + "vw",
duration: 1000,
ease: "linear",
});
} else if (Config.timerStyle === "text") {
let displayTime = DateTime.secondsToString(maxtime - time);
if (maxtime === 0) {
displayTime = DateTime.secondsToString(time);
}
if (textEl !== null) {
textEl.innerHTML = "<div>" + displayTime + "</div>";
}
} else if (Config.timerStyle === "flash_mini") {
let displayTime = DateTime.secondsToString(maxtime - time);
if (maxtime === 0) {
displayTime = DateTime.secondsToString(time);
}
if (miniEl !== null) {
if ((maxtime - time) % 15 !== 0) {
miniEl.style.opacity = "0";
} else {
miniEl.style.opacity = "1";
}
miniEl.innerHTML = "<div>" + displayTime + "</div>";
}
} else if (Config.timerStyle === "flash_text") {
let displayTime = DateTime.secondsToString(maxtime - time);
if (maxtime === 0) {
displayTime = DateTime.secondsToString(time);
}
if (textEl !== null) {
textEl.innerHTML =
"<div>" +
`${(maxtime - time) % 15 !== 0 ? "" : displayTime}` +
"</div>";
}
} else if (Config.timerStyle === "mini") {
let displayTime = DateTime.secondsToString(maxtime - time);
if (maxtime === 0) {
displayTime = DateTime.secondsToString(time);
}
if (miniEl !== null) {
miniEl.innerHTML = displayTime;
}
}
} else if (
Config.mode === "words" ||
Config.mode === "custom" ||
Config.mode === "quote"
) {
let outof = TestWords.words.length;
if (Config.mode === "words") {
outof = Config.words;
}
if (Config.mode === "custom") {
outof = CustomText.getLimitValue();
}
if (Config.mode === "quote") {
outof = TestWords.currentQuote?.textSplit.length ?? 1;
}
if (Config.timerStyle === "bar") {
const percent = Math.floor(
((TestState.activeWordIndex + 1) / outof) * 100,
);
animate(barEl, {
width: percent + "vw",
duration: 250,
});
} else if (Config.timerStyle === "text") {
if (outof === 0) {
textEl.innerHTML = `<div>${TestInput.input.getHistory().length}</div>`;
} else {
textEl.innerHTML = `<div>${getCurrentCount()}/${outof}</div>`;
}
} else if (Config.timerStyle === "flash_mini") {
if (outof === 0) {
miniEl.innerHTML = `${TestInput.input.getHistory().length}`;
} else {
miniEl.innerHTML = `${getCurrentCount()}/${outof}`;
}
} else if (Config.timerStyle === "flash_text") {
if (outof === 0) {
textEl.innerHTML = `<div>${TestInput.input.getHistory().length}</div>`;
} else {
textEl.innerHTML = `<div>${getCurrentCount()}/${outof}</div>`;
}
} else if (Config.timerStyle === "mini") {
if (outof === 0) {
miniEl.innerHTML = `${TestInput.input.getHistory().length}`;
} else {
miniEl.innerHTML = `${getCurrentCount()}/${outof}`;
}
}
} else if (Config.mode === "zen") {
if (Config.timerStyle === "text") {
textEl.innerHTML = `<div>${TestInput.input.getHistory().length}</div>`;
} else if (Config.timerStyle === "flash_mini") {
miniEl.innerHTML = `${TestInput.input.getHistory().length}`;
} else if (Config.timerStyle === "flash_text") {
textEl.innerHTML = `<div>${TestInput.input.getHistory().length}</div>`;
} else {
miniEl.innerHTML = `${TestInput.input.getHistory().length}`;
}
}
});
}
export function updateStyle(): void {
if (!TestState.isActive()) return;
hide();
update();
if (Config.timerStyle === "off") return;
setTimeout(() => {
show();
}, 125);
}
ConfigEvent.subscribe(({ key }) => {
if (key === "timerStyle") updateStyle();
});