mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-09-11 00:57:01 +08:00
refactor(caret): move active word element offset to test-state and remove .smoothScroller (@NadAlaba) (#6541)
This commit is contained in:
parent
ced4b6e162
commit
33a6bc09e3
6 changed files with 84 additions and 107 deletions
|
@ -122,7 +122,7 @@ function backspaceToPrevious(): void {
|
|||
if (!TestState.isActive) return;
|
||||
|
||||
const wordElementIndex =
|
||||
TestState.activeWordIndex - TestUI.activeWordElementOffset;
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount;
|
||||
|
||||
if (TestInput.input.getHistory().length === 0 || wordElementIndex === 0) {
|
||||
return;
|
||||
|
@ -269,12 +269,12 @@ async function handleSpace(): Promise<void> {
|
|||
if (Config.blindMode) {
|
||||
if (Config.highlightMode !== "off") {
|
||||
TestUI.highlightAllLettersAsCorrect(
|
||||
TestState.activeWordIndex - TestUI.activeWordElementOffset
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount
|
||||
);
|
||||
}
|
||||
} else {
|
||||
TestUI.highlightBadWord(
|
||||
TestState.activeWordIndex - TestUI.activeWordElementOffset
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount
|
||||
);
|
||||
}
|
||||
TestInput.input.pushHistory();
|
||||
|
@ -343,14 +343,14 @@ async function handleSpace(): Promise<void> {
|
|||
if (!Config.showAllLines || shouldLimitToThreeLines) {
|
||||
const currentTop: number = Math.floor(
|
||||
document.querySelectorAll<HTMLElement>("#words .word")[
|
||||
TestState.activeWordIndex - TestUI.activeWordElementOffset - 1
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount - 1
|
||||
]?.offsetTop ?? 0
|
||||
);
|
||||
|
||||
const { data: nextTop } = tryCatchSync(() =>
|
||||
Math.floor(
|
||||
document.querySelectorAll<HTMLElement>("#words .word")[
|
||||
TestState.activeWordIndex - TestUI.activeWordElementOffset
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount
|
||||
]?.offsetTop ?? 0
|
||||
)
|
||||
);
|
||||
|
@ -668,7 +668,7 @@ function handleChar(
|
|||
);
|
||||
|
||||
const activeWord = document.querySelectorAll("#words .word")?.[
|
||||
TestState.activeWordIndex - TestUI.activeWordElementOffset
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount
|
||||
] as HTMLElement;
|
||||
|
||||
const testInputLength: number = !isCharKorean
|
||||
|
@ -1113,7 +1113,7 @@ $(document).on("keydown", async (event) => {
|
|||
const activeWord: HTMLElement | null = document.querySelectorAll(
|
||||
"#words .word"
|
||||
)?.[
|
||||
TestState.activeWordIndex - TestUI.activeWordElementOffset
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount
|
||||
] as HTMLElement;
|
||||
const len: number = TestInput.input.current.length; // have to do this because prettier wraps the line and causes an error
|
||||
|
||||
|
|
|
@ -35,12 +35,15 @@ export function hide(): void {
|
|||
caret.classList.add("hidden");
|
||||
}
|
||||
|
||||
export function getSpaceWidth(wordElement?: HTMLElement): number {
|
||||
if (!wordElement)
|
||||
wordElement = document
|
||||
.getElementById("words")
|
||||
?.querySelectorAll(".word")?.[0] as HTMLElement | undefined;
|
||||
if (!wordElement) return 0;
|
||||
function getSpaceWidth(wordElement?: HTMLElement): number {
|
||||
if (!wordElement) {
|
||||
const el = document.querySelector<HTMLElement>("#words .word");
|
||||
if (el) {
|
||||
wordElement = el;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
const wordComputedStyle = window.getComputedStyle(wordElement);
|
||||
return (
|
||||
parseInt(wordComputedStyle.marginRight) +
|
||||
|
@ -52,8 +55,7 @@ function getTargetPositionLeft(
|
|||
fullWidthCaret: boolean,
|
||||
isLanguageRightToLeft: boolean,
|
||||
activeWordElement: HTMLElement,
|
||||
activeWordEmpty: boolean,
|
||||
currentWordNodeList: NodeListOf<Element>,
|
||||
currentWordNodeList: NodeListOf<HTMLElement>,
|
||||
fullWidthCaretWidth: number,
|
||||
wordLen: number,
|
||||
inputLen: number
|
||||
|
@ -64,21 +66,15 @@ function getTargetPositionLeft(
|
|||
if (Config.tapeMode === "off") {
|
||||
let positionOffsetToWord = 0;
|
||||
|
||||
const currentLetter = currentWordNodeList[inputLen] as
|
||||
| HTMLElement
|
||||
| undefined;
|
||||
const lastWordLetter = currentWordNodeList[wordLen - 1] as
|
||||
| HTMLElement
|
||||
| undefined;
|
||||
const lastInputLetter = currentWordNodeList[inputLen - 1] as
|
||||
| HTMLElement
|
||||
| undefined;
|
||||
const currentLetter = currentWordNodeList[inputLen];
|
||||
const lastWordLetter = currentWordNodeList[wordLen - 1];
|
||||
const lastInputLetter = currentWordNodeList[inputLen - 1];
|
||||
|
||||
if (isLanguageRightToLeft) {
|
||||
if (inputLen < wordLen && currentLetter) {
|
||||
if (inputLen <= wordLen && currentLetter) {
|
||||
// at word beginning in zen mode both lengths are 0, but currentLetter is defined "_"
|
||||
positionOffsetToWord =
|
||||
currentLetter?.offsetLeft +
|
||||
(fullWidthCaret ? 0 : fullWidthCaretWidth);
|
||||
currentLetter.offsetLeft + (fullWidthCaret ? 0 : fullWidthCaretWidth);
|
||||
} else if (!invisibleExtraLetters) {
|
||||
positionOffsetToWord =
|
||||
(lastInputLetter?.offsetLeft ?? 0) -
|
||||
|
@ -90,7 +86,7 @@ function getTargetPositionLeft(
|
|||
}
|
||||
} else {
|
||||
if (inputLen < wordLen && currentLetter) {
|
||||
positionOffsetToWord = currentLetter?.offsetLeft;
|
||||
positionOffsetToWord = currentLetter.offsetLeft;
|
||||
} else if (!invisibleExtraLetters) {
|
||||
positionOffsetToWord =
|
||||
(lastInputLetter?.offsetLeft ?? 0) +
|
||||
|
@ -102,8 +98,6 @@ function getTargetPositionLeft(
|
|||
}
|
||||
}
|
||||
result = activeWordElement.offsetLeft + positionOffsetToWord;
|
||||
if (activeWordEmpty && isLanguageRightToLeft)
|
||||
result += activeWordElement.offsetWidth;
|
||||
} else {
|
||||
const wordsWrapperWidth =
|
||||
$(document.querySelector("#wordsWrapper") as HTMLElement).width() ?? 0;
|
||||
|
@ -150,22 +144,18 @@ export async function updatePosition(noAnim = false): Promise<void> {
|
|||
let wordLen = splitIntoCharacters(TestWords.words.getCurrent()).length;
|
||||
const inputLen = splitIntoCharacters(TestInput.input.current).length;
|
||||
if (Config.mode === "zen") wordLen = inputLen;
|
||||
const activeWordEl = document?.querySelector("#words .active") as HTMLElement;
|
||||
let activeWordEmpty = false;
|
||||
if (Config.mode === "zen") {
|
||||
wordLen = inputLen;
|
||||
if (inputLen === 0) activeWordEmpty = true;
|
||||
}
|
||||
const activeWordEl =
|
||||
document.querySelectorAll<HTMLElement>("#words .word")[
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount
|
||||
];
|
||||
if (!activeWordEl) return;
|
||||
|
||||
const currentWordNodeList = activeWordEl?.querySelectorAll("letter");
|
||||
const currentWordNodeList =
|
||||
activeWordEl.querySelectorAll<HTMLElement>("letter");
|
||||
if (!currentWordNodeList?.length) return;
|
||||
|
||||
const currentLetter = currentWordNodeList[inputLen] as
|
||||
| HTMLElement
|
||||
| undefined;
|
||||
const lastWordLetter = currentWordNodeList[wordLen - 1] as
|
||||
| HTMLElement
|
||||
| undefined;
|
||||
const currentLetter = currentWordNodeList[inputLen];
|
||||
const lastWordLetter = currentWordNodeList[wordLen - 1];
|
||||
|
||||
const currentLanguage = await JSONData.getCurrentLanguage(Config.language);
|
||||
const isLanguageRightToLeft = currentLanguage.rightToLeft;
|
||||
|
@ -187,11 +177,11 @@ export async function updatePosition(noAnim = false): Promise<void> {
|
|||
}
|
||||
|
||||
let letterWidth = currentLetter?.offsetWidth;
|
||||
if (letterWidth === undefined || activeWordEmpty) {
|
||||
if (letterWidth === undefined || wordLen === 0) {
|
||||
// at word beginning in zen mode current letter is defined "_" but wordLen is 0
|
||||
letterWidth = getSpaceWidth(activeWordEl);
|
||||
} else if (letterWidth === 0) {
|
||||
// current letter is a zero-width character e.g, diacritics)
|
||||
letterWidth = 0;
|
||||
for (let i = inputLen; i >= 0; i--) {
|
||||
letterWidth = (currentWordNodeList[i] as HTMLElement)?.offsetWidth;
|
||||
if (letterWidth) break;
|
||||
|
@ -203,7 +193,6 @@ export async function updatePosition(noAnim = false): Promise<void> {
|
|||
fullWidthCaret,
|
||||
isLanguageRightToLeft,
|
||||
activeWordEl,
|
||||
activeWordEmpty,
|
||||
currentWordNodeList,
|
||||
letterWidth,
|
||||
wordLen,
|
||||
|
@ -211,15 +200,12 @@ export async function updatePosition(noAnim = false): Promise<void> {
|
|||
);
|
||||
const newLeft = letterPosLeft - (fullWidthCaret ? 0 : caretWidth / 2);
|
||||
|
||||
let smoothlinescroll = $("#words .smoothScroller").height();
|
||||
if (smoothlinescroll === undefined) smoothlinescroll = 0;
|
||||
|
||||
const jqcaret = $(caret);
|
||||
|
||||
jqcaret.css("display", "block"); //for some goddamn reason adding width animation sets the display to none ????????
|
||||
|
||||
const animation: { top: number; left: number; width?: string } = {
|
||||
top: newTop - smoothlinescroll,
|
||||
top: newTop - TestState.lineScrollDistance,
|
||||
left: newLeft,
|
||||
};
|
||||
|
||||
|
|
|
@ -200,8 +200,7 @@ export async function update(expectedStepEnd: number): Promise<void> {
|
|||
let newTop;
|
||||
let newLeft;
|
||||
try {
|
||||
const newIndex =
|
||||
settings.currentWordIndex - TestUI.activeWordElementOffset;
|
||||
const newIndex = settings.currentWordIndex - TestState.removedUIWordCount;
|
||||
const word = document.querySelectorAll("#words .word")[
|
||||
newIndex
|
||||
] as HTMLElement;
|
||||
|
@ -257,11 +256,8 @@ export async function update(expectedStepEnd: number): Promise<void> {
|
|||
const duration = expectedStepEnd - performance.now();
|
||||
|
||||
if (newTop !== undefined) {
|
||||
let smoothlinescroll = $("#words .smoothScroller").height();
|
||||
if (smoothlinescroll === undefined) smoothlinescroll = 0;
|
||||
|
||||
$("#paceCaret").css({
|
||||
top: newTop - smoothlinescroll,
|
||||
top: newTop - TestState.lineScrollDistance,
|
||||
});
|
||||
|
||||
if (Config.smoothCaret !== "off") {
|
||||
|
|
|
@ -423,7 +423,7 @@ export async function init(): Promise<void | null> {
|
|||
Replay.stopReplayRecording();
|
||||
TestWords.words.reset();
|
||||
TestState.setActiveWordIndex(0);
|
||||
TestUI.setActiveWordElementOffset(0);
|
||||
TestState.setRemovedUIWordCount(0);
|
||||
TestInput.input.resetHistory();
|
||||
TestInput.input.current = "";
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ export let bailedOut = false;
|
|||
export let selectedQuoteId = 1;
|
||||
export let activeWordIndex = 0;
|
||||
export let testInitSuccess = true;
|
||||
export let removedUIWordCount = 0;
|
||||
export let lineScrollDistance = 0;
|
||||
|
||||
export function setRepeated(tf: boolean): void {
|
||||
isRepeated = tf;
|
||||
|
@ -53,3 +55,15 @@ export function decreaseActiveWordIndex(): void {
|
|||
export function setTestInitSuccess(tf: boolean): void {
|
||||
testInitSuccess = tf;
|
||||
}
|
||||
|
||||
export function setRemovedUIWordCount(val: number): void {
|
||||
removedUIWordCount = val;
|
||||
}
|
||||
|
||||
export function incrementRemovedUIWordCount(by: number = 1): void {
|
||||
removedUIWordCount += by;
|
||||
}
|
||||
|
||||
export function setLineScrollDistance(val: number): void {
|
||||
lineScrollDistance = val;
|
||||
}
|
||||
|
|
|
@ -187,7 +187,6 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
|
|||
if (eventKey === "burstHeatmap") void applyBurstHeatmap();
|
||||
});
|
||||
|
||||
export let activeWordElementOffset = 0;
|
||||
export let resultVisible = false;
|
||||
export let activeWordTop = 0;
|
||||
export let testRestarting = false;
|
||||
|
@ -199,10 +198,6 @@ export function setResultVisible(val: boolean): void {
|
|||
resultVisible = val;
|
||||
}
|
||||
|
||||
export function setActiveWordElementOffset(val: number): void {
|
||||
activeWordElementOffset = val;
|
||||
}
|
||||
|
||||
export function setActiveWordTop(val: number): void {
|
||||
activeWordTop = val;
|
||||
}
|
||||
|
@ -228,7 +223,7 @@ export function setResultCalculating(val: boolean): void {
|
|||
|
||||
export function reset(): void {
|
||||
currentTestLine = 0;
|
||||
activeWordElementOffset = 0;
|
||||
TestState.setRemovedUIWordCount(0);
|
||||
}
|
||||
|
||||
export function focusWords(): void {
|
||||
|
@ -253,7 +248,7 @@ export function updateActiveElement(
|
|||
active.classList.remove("active");
|
||||
}
|
||||
const newActiveWord = document.querySelectorAll("#words .word")[
|
||||
TestState.activeWordIndex - activeWordElementOffset
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount
|
||||
] as HTMLElement | undefined;
|
||||
|
||||
if (newActiveWord === undefined) {
|
||||
|
@ -443,7 +438,7 @@ export async function updateWordsInputPosition(initial = false): Promise<void> {
|
|||
|
||||
const activeWord =
|
||||
document.querySelectorAll<HTMLElement>("#words .word")[
|
||||
TestState.activeWordIndex - activeWordElementOffset
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount
|
||||
];
|
||||
|
||||
if (!activeWord) {
|
||||
|
@ -503,7 +498,8 @@ export async function centerActiveLine(): Promise<void> {
|
|||
centeringActiveLine = promise;
|
||||
|
||||
const wordElements = document.querySelectorAll<HTMLElement>("#words .word");
|
||||
const activeWordIndex = TestState.activeWordIndex - activeWordElementOffset;
|
||||
const activeWordIndex =
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount;
|
||||
const activeWordEl = wordElements[activeWordIndex];
|
||||
if (!activeWordEl) {
|
||||
resolve();
|
||||
|
@ -533,7 +529,7 @@ export function updateWordsWrapperHeight(force = false): void {
|
|||
) as HTMLElement;
|
||||
const wordElements = wrapperEl.querySelectorAll<HTMLElement>("#words .word");
|
||||
const activeWordEl =
|
||||
wordElements[TestState.activeWordIndex - activeWordElementOffset];
|
||||
wordElements[TestState.activeWordIndex - TestState.removedUIWordCount];
|
||||
if (!activeWordEl) return;
|
||||
|
||||
wrapperEl.classList.remove("hidden");
|
||||
|
@ -655,7 +651,7 @@ export async function updateActiveWordLetters(
|
|||
let ret = "";
|
||||
const activeWord =
|
||||
document.querySelectorAll<HTMLElement>("#words .word")?.[
|
||||
TestState.activeWordIndex - activeWordElementOffset
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount
|
||||
];
|
||||
if (!activeWord) return;
|
||||
const hintIndices: number[][] = [];
|
||||
|
@ -844,7 +840,8 @@ export async function scrollTape(noRemove = false): Promise<void> {
|
|||
const isLanguageRTL = currentLang.rightToLeft;
|
||||
|
||||
// index of the active word in the collection of .word elements
|
||||
const wordElementIndex = TestState.activeWordIndex - activeWordElementOffset;
|
||||
const wordElementIndex =
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount;
|
||||
const wordsWrapperWidth = (
|
||||
document.querySelector("#wordsWrapper") as HTMLElement
|
||||
).offsetWidth;
|
||||
|
@ -960,8 +957,8 @@ export async function scrollTape(noRemove = false): Promise<void> {
|
|||
|
||||
/* remove overflown elements */
|
||||
if (toRemove.length > 0 && !noRemove) {
|
||||
activeWordElementOffset += wordsToRemoveCount;
|
||||
for (const el of toRemove) el.remove();
|
||||
TestState.incrementRemovedUIWordCount(wordsToRemoveCount);
|
||||
for (let i = 0; i < widthRemovedFromLine.length; i++) {
|
||||
const afterNewlineEl = afterNewLineEls[i] as HTMLElement;
|
||||
const currentLineIndent =
|
||||
|
@ -1062,7 +1059,7 @@ function removeElementsBeforeWord(
|
|||
const child = wordsChildren[i];
|
||||
if (!child || !child.isConnected) continue;
|
||||
if (child.classList.contains("word")) removedWords++;
|
||||
if (!child.classList.contains("smoothScroller")) child.remove();
|
||||
child.remove();
|
||||
}
|
||||
return removedWords;
|
||||
}
|
||||
|
@ -1081,7 +1078,7 @@ export async function lineJump(
|
|||
|
||||
// index of the active word in the collection of .word elements
|
||||
const wordElementIndex =
|
||||
TestState.activeWordIndex - activeWordElementOffset;
|
||||
TestState.activeWordIndex - TestState.removedUIWordCount;
|
||||
const wordsEl = document.getElementById("words") as HTMLElement;
|
||||
const wordsChildrenArr = [...wordsEl.children];
|
||||
const wordElements = wordsEl.querySelectorAll(".word");
|
||||
|
@ -1121,29 +1118,7 @@ export async function lineJump(
|
|||
resolve();
|
||||
} else if (Config.smoothLineScroll) {
|
||||
lineTransition = true;
|
||||
const smoothScroller = $("#words .smoothScroller");
|
||||
if (smoothScroller.length === 0) {
|
||||
wordsEl.insertAdjacentHTML(
|
||||
"afterbegin",
|
||||
`<div class="smoothScroller" style="position: fixed;height:${wordHeight}px;width:100%"></div>`
|
||||
);
|
||||
} else {
|
||||
smoothScroller.css(
|
||||
"height",
|
||||
`${(smoothScroller.outerHeight(true) ?? 0) + wordHeight}px`
|
||||
);
|
||||
}
|
||||
$("#words .smoothScroller")
|
||||
.stop(true, false)
|
||||
.animate(
|
||||
{
|
||||
height: 0,
|
||||
},
|
||||
SlowTimer.get() ? 0 : 125,
|
||||
() => {
|
||||
$("#words .smoothScroller").remove();
|
||||
}
|
||||
);
|
||||
|
||||
$(paceCaretElement)
|
||||
.stop(true, false)
|
||||
.animate(
|
||||
|
@ -1153,25 +1128,32 @@ export async function lineJump(
|
|||
SlowTimer.get() ? 0 : 125
|
||||
);
|
||||
|
||||
const scrollDistance = TestState.lineScrollDistance + wordHeight;
|
||||
TestState.setLineScrollDistance(scrollDistance);
|
||||
currentLinesAnimating++;
|
||||
const newCss: Record<string, string> = {
|
||||
marginTop: `-${wordHeight * (currentLinesAnimating + 1)}px`,
|
||||
marginTop: `-${wordHeight * currentLinesAnimating}px`,
|
||||
};
|
||||
|
||||
currentLinesAnimating++;
|
||||
const jqWords = $(wordsEl);
|
||||
jqWords.stop("topMargin", true, false).animate(newCss, {
|
||||
duration: SlowTimer.get() ? 0 : 125,
|
||||
queue: "topMargin",
|
||||
step: (now, fx) => {
|
||||
const completionRate = (now - fx.start) / (fx.end - fx.start);
|
||||
TestState.setLineScrollDistance(
|
||||
scrollDistance * (1 - completionRate)
|
||||
);
|
||||
},
|
||||
complete: () => {
|
||||
currentLinesAnimating = 0;
|
||||
activeWordTop = (
|
||||
document.querySelectorAll("#words .word")?.[
|
||||
TestState.setLineScrollDistance(0);
|
||||
activeWordTop =
|
||||
document.querySelectorAll<HTMLElement>("#words .word")?.[
|
||||
wordElementIndex
|
||||
] as HTMLElement
|
||||
)?.offsetTop;
|
||||
activeWordElementOffset += removeElementsBeforeWord(
|
||||
lastElementToRemoveIndex,
|
||||
wordsChildrenArr
|
||||
]?.offsetTop ?? 0;
|
||||
TestState.incrementRemovedUIWordCount(
|
||||
removeElementsBeforeWord(lastElementToRemoveIndex, wordsChildrenArr)
|
||||
);
|
||||
wordsEl.style.marginTop = "0";
|
||||
lineTransition = false;
|
||||
|
@ -1180,9 +1162,8 @@ export async function lineJump(
|
|||
});
|
||||
jqWords.dequeue("topMargin");
|
||||
} else {
|
||||
activeWordElementOffset += removeElementsBeforeWord(
|
||||
lastElementToRemoveIndex,
|
||||
wordsChildrenArr
|
||||
TestState.incrementRemovedUIWordCount(
|
||||
removeElementsBeforeWord(lastElementToRemoveIndex, wordsChildrenArr)
|
||||
);
|
||||
paceCaretElement.style.top = `${
|
||||
paceCaretElement.offsetTop - wordHeight
|
||||
|
|
Loading…
Add table
Reference in a new issue