mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2026-02-05 15:09:22 +08:00
refactor: test word centering behavior, words input position update (@miodc) (#6962)
brrr
This commit is contained in:
parent
10130d7348
commit
16ad0e285a
5 changed files with 94 additions and 81 deletions
|
|
@ -4,7 +4,6 @@ import * as TestInput from "./test-input";
|
|||
import * as SlowTimer from "../states/slow-timer";
|
||||
import * as TestState from "../test/test-state";
|
||||
import * as TestWords from "./test-words";
|
||||
import { prefersReducedMotion } from "../utils/misc";
|
||||
import { convertRemToPixels } from "../utils/numbers";
|
||||
import { splitIntoCharacters, getWordDirection } from "../utils/strings";
|
||||
import { safeNumber } from "@monkeytype/util/numbers";
|
||||
|
|
@ -262,25 +261,6 @@ export async function updatePosition(noAnim = false): Promise<void> {
|
|||
jqcaret
|
||||
.stop(true, false)
|
||||
.animate(animation, SlowTimer.get() || noAnim ? 0 : smoothCaretSpeed);
|
||||
|
||||
if (Config.showAllLines) {
|
||||
const browserHeight = window.innerHeight;
|
||||
const middlePos = browserHeight / 2 - (jqcaret.outerHeight() as number) / 2;
|
||||
const contentHeight = document.body.scrollHeight;
|
||||
|
||||
if (
|
||||
newTop >= middlePos &&
|
||||
contentHeight > browserHeight &&
|
||||
TestState.isActive
|
||||
) {
|
||||
const newscrolltop = newTop - middlePos / 2;
|
||||
window.scrollTo({
|
||||
left: 0,
|
||||
top: newscrolltop,
|
||||
behavior: prefersReducedMotion() ? "instant" : "smooth",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateStyle(): void {
|
||||
|
|
|
|||
|
|
@ -1074,7 +1074,11 @@ export async function update(
|
|||
$("#result"),
|
||||
250,
|
||||
async () => {
|
||||
$("#result").trigger("focus");
|
||||
const result = document.querySelector<HTMLElement>("#result");
|
||||
result?.focus({
|
||||
preventScroll: true,
|
||||
});
|
||||
Misc.scrollToCenterOrTop(result);
|
||||
void AdController.renderResult();
|
||||
TestUI.setResultCalculating(false);
|
||||
$("#words").empty();
|
||||
|
|
|
|||
|
|
@ -155,13 +155,45 @@ export function reset(): void {
|
|||
}
|
||||
|
||||
export function focusWords(): void {
|
||||
$("#wordsInput").trigger("focus");
|
||||
const wordsInput = document.querySelector<HTMLElement>("#wordsInput");
|
||||
wordsInput?.blur();
|
||||
wordsInput?.focus({
|
||||
preventScroll: true,
|
||||
});
|
||||
if (TestState.isActive) {
|
||||
keepWordsInputInTheCenter(true);
|
||||
} else {
|
||||
const typingTest = document.querySelector<HTMLElement>("#typingTest");
|
||||
Misc.scrollToCenterOrTop(typingTest);
|
||||
}
|
||||
}
|
||||
|
||||
export function blurWords(): void {
|
||||
$("#wordsInput").trigger("blur");
|
||||
}
|
||||
|
||||
export function keepWordsInputInTheCenter(force = false): void {
|
||||
const wordsInput = document.querySelector<HTMLElement>("#wordsInput");
|
||||
const wordsWrapper = document.querySelector<HTMLElement>("#wordsWrapper");
|
||||
if (!wordsInput || !wordsWrapper) return;
|
||||
|
||||
const wordsWrapperHeight = wordsWrapper.offsetHeight;
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
// dont do anything if the wrapper can fit on screen
|
||||
if (wordsWrapperHeight < windowHeight) return;
|
||||
|
||||
const wordsInputRect = wordsInput.getBoundingClientRect();
|
||||
const wordsInputBelowCenter = wordsInputRect.top > windowHeight / 2;
|
||||
|
||||
// dont do anything if its above or at the center unless forced
|
||||
if (!wordsInputBelowCenter && !force) return;
|
||||
|
||||
wordsInput.scrollIntoView({
|
||||
block: "center",
|
||||
});
|
||||
}
|
||||
|
||||
export function getWordElement(index: number): HTMLElement | null {
|
||||
const el = document.querySelector<HTMLElement>(
|
||||
`#words .word[data-wordindex='${index}']`
|
||||
|
|
@ -197,9 +229,8 @@ export function updateActiveElement(
|
|||
|
||||
activeWordTop = newActiveWord.offsetTop;
|
||||
|
||||
if (!initial && shouldUpdateWordsInputPosition()) {
|
||||
void updateWordsInputPosition();
|
||||
}
|
||||
void updateWordsInputPosition();
|
||||
|
||||
if (!initial && Config.tapeMode !== "off") {
|
||||
void scrollTape();
|
||||
}
|
||||
|
|
@ -448,7 +479,7 @@ function updateWordWrapperClasses(): void {
|
|||
|
||||
updateWordsWidth();
|
||||
updateWordsWrapperHeight(true);
|
||||
updateWordsMargin(updateWordsInputPosition, [true]);
|
||||
updateWordsMargin(updateWordsInputPosition, []);
|
||||
}
|
||||
|
||||
export function showWords(): void {
|
||||
|
|
@ -478,65 +509,54 @@ export function appendEmptyWordElement(
|
|||
`<div class='word' data-wordindex='${index}'><letter class='invisible'>_</letter></div>`
|
||||
);
|
||||
}
|
||||
|
||||
const posUpdateLangList = ["japanese", "chinese", "korean"];
|
||||
function shouldUpdateWordsInputPosition(): boolean {
|
||||
const language = posUpdateLangList.some((l) => Config.language.startsWith(l));
|
||||
return language || (Config.mode !== "time" && Config.showAllLines);
|
||||
}
|
||||
|
||||
export async function updateWordsInputPosition(initial = false): Promise<void> {
|
||||
if (ActivePage.get() !== "test") return;
|
||||
|
||||
const currentLanguage = await JSONData.getCurrentLanguage(Config.language);
|
||||
const isLanguageRTL = currentLanguage.rightToLeft;
|
||||
|
||||
const el = document.querySelector<HTMLElement>("#wordsInput");
|
||||
|
||||
if (!el) return;
|
||||
|
||||
const activeWord = getActiveWordElement();
|
||||
|
||||
if (!activeWord) {
|
||||
el.style.top = "0px";
|
||||
el.style.left = "0px";
|
||||
return;
|
||||
let updateWordsInputPositionAnimationFrameId: null | number = null;
|
||||
export async function updateWordsInputPosition(): Promise<void> {
|
||||
if (updateWordsInputPositionAnimationFrameId !== null) {
|
||||
cancelAnimationFrame(updateWordsInputPositionAnimationFrameId);
|
||||
}
|
||||
updateWordsInputPositionAnimationFrameId = requestAnimationFrame(async () => {
|
||||
updateWordsInputPositionAnimationFrameId = null;
|
||||
if (ActivePage.get() !== "test") return;
|
||||
const currentLanguage = await JSONData.getCurrentLanguage(Config.language);
|
||||
const isLanguageRTL = currentLanguage.rightToLeft;
|
||||
|
||||
const computed = window.getComputedStyle(activeWord);
|
||||
const activeWordMargin =
|
||||
parseInt(computed.marginTop) + parseInt(computed.marginBottom);
|
||||
const el = document.querySelector<HTMLElement>("#wordsInput");
|
||||
|
||||
const letterHeight = convertRemToPixels(Config.fontSize);
|
||||
const targetTop =
|
||||
activeWord.offsetTop + letterHeight / 2 - el.offsetHeight / 2 + 1; //+1 for half of border
|
||||
if (!el) return;
|
||||
|
||||
if (Config.tapeMode !== "off") {
|
||||
el.style.maxWidth = `${100 - Config.tapeMargin}%`;
|
||||
} else {
|
||||
el.style.maxWidth = "";
|
||||
}
|
||||
if (activeWord.offsetWidth < letterHeight) {
|
||||
el.style.width = letterHeight + "px";
|
||||
} else {
|
||||
el.style.width = activeWord.offsetWidth + "px";
|
||||
}
|
||||
const activeWord = getActiveWordElement();
|
||||
|
||||
if (!activeWord) {
|
||||
el.style.top = "0px";
|
||||
el.style.left = "0px";
|
||||
return;
|
||||
}
|
||||
|
||||
const letterHeight = convertRemToPixels(Config.fontSize);
|
||||
const targetTop =
|
||||
activeWord.offsetTop + letterHeight / 2 - el.offsetHeight / 2 + 1; //+1 for half of border
|
||||
|
||||
if (Config.tapeMode !== "off") {
|
||||
el.style.maxWidth = `${100 - Config.tapeMargin}%`;
|
||||
} else {
|
||||
el.style.maxWidth = "";
|
||||
}
|
||||
if (activeWord.offsetWidth < letterHeight) {
|
||||
el.style.width = letterHeight + "px";
|
||||
} else {
|
||||
el.style.width = activeWord.offsetWidth + "px";
|
||||
}
|
||||
|
||||
if (
|
||||
initial &&
|
||||
!shouldUpdateWordsInputPosition() &&
|
||||
Config.tapeMode === "off"
|
||||
) {
|
||||
el.style.top = targetTop + letterHeight + activeWordMargin + 4 + "px";
|
||||
} else {
|
||||
el.style.top = targetTop + "px";
|
||||
}
|
||||
|
||||
if (activeWord.offsetWidth < letterHeight && isLanguageRTL) {
|
||||
el.style.left = activeWord.offsetLeft - letterHeight + "px";
|
||||
} else {
|
||||
el.style.left = Math.max(0, activeWord.offsetLeft) + "px";
|
||||
}
|
||||
if (activeWord.offsetWidth < letterHeight && isLanguageRTL) {
|
||||
el.style.left = activeWord.offsetLeft - letterHeight + "px";
|
||||
} else {
|
||||
el.style.left = Math.max(0, activeWord.offsetLeft) + "px";
|
||||
}
|
||||
|
||||
keepWordsInputInTheCenter();
|
||||
});
|
||||
}
|
||||
|
||||
let centeringActiveLine: Promise<void> = Promise.resolve();
|
||||
|
|
|
|||
|
|
@ -107,9 +107,7 @@ const debouncedEvent = debounce(250, () => {
|
|||
}
|
||||
setTimeout(() => {
|
||||
void TestUI.updateWordsInputPosition();
|
||||
if ($("#wordsInput").is(":focus")) {
|
||||
Caret.show(true);
|
||||
}
|
||||
TestUI.focusWords();
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -746,4 +746,15 @@ export function isMacLike(): boolean {
|
|||
return isPlatform(/Mac|iPod|iPhone|iPad/);
|
||||
}
|
||||
|
||||
export function scrollToCenterOrTop(el: HTMLElement | null): void {
|
||||
if (!el) return;
|
||||
|
||||
const elementHeight = el.offsetHeight;
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
el.scrollIntoView({
|
||||
block: elementHeight < windowHeight ? "center" : "start",
|
||||
});
|
||||
}
|
||||
|
||||
// DO NOT ALTER GLOBAL OBJECTSONSTRUCTOR, IT WILL BREAK RESULT HASHES
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue