Skip to content

Commit

Permalink
impr(tape mode): support RTL languages (@NadAlaba)
Browse files Browse the repository at this point in the history
  • Loading branch information
NadAlaba committed Sep 21, 2024
1 parent 23cf24f commit 87dab9f
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 57 deletions.
12 changes: 6 additions & 6 deletions frontend/src/ts/controllers/input-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function updateUI(): void {
}
}

function backspaceToPrevious(): void {
async function backspaceToPrevious(): Promise<void> {
if (!TestState.isActive) return;

if (
Expand Down Expand Up @@ -152,7 +152,7 @@ function backspaceToPrevious(): void {
}
TestWords.words.decreaseCurrentIndex();
TestUI.setActiveWordElementIndex(TestUI.activeWordElementIndex - 1);
TestUI.updateActiveElement(true);
await TestUI.updateActiveElement(true);
Funbox.toggleScript(TestWords.words.getCurrent());
void TestUI.updateActiveWordLetters();

Expand Down Expand Up @@ -337,7 +337,7 @@ async function handleSpace(): Promise<void> {
}
}
TestUI.setActiveWordElementIndex(TestUI.activeWordElementIndex + 1);
TestUI.updateActiveElement();
await TestUI.updateActiveElement();
void Caret.updatePosition();

if (
Expand Down Expand Up @@ -1050,7 +1050,7 @@ $(document).on("keydown", async (event) => {
Monkey.type();

if (event.key === "Backspace" && TestInput.input.current.length === 0) {
backspaceToPrevious();
await backspaceToPrevious();
if (TestInput.input.current) {
setWordsInput(" " + TestInput.input.current + " ");
}
Expand Down Expand Up @@ -1247,7 +1247,7 @@ $("#wordsInput").on("beforeinput", (event) => {
}
});

$("#wordsInput").on("input", (event) => {
$("#wordsInput").on("input", async (event) => {
if (!event.originalEvent?.isTrusted || TestUI.testRestarting) {
(event.target as HTMLInputElement).value = " ";
return;
Expand Down Expand Up @@ -1316,7 +1316,7 @@ $("#wordsInput").on("input", (event) => {

if (realInputValue.length === 0 && currTestInput.length === 0) {
// fallback for when no Backspace keydown event (mobile)
backspaceToPrevious();
await backspaceToPrevious();
} else if (inputValue.length < currTestInput.length) {
if (containsChinese) {
if (
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/ts/test/caret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,17 @@ function getTargetPositionLeft(

if (Config.tapeMode === "word" && inputLen > 0) {
let currentWordWidth = 0;
let lastPositiveLetterWidth = 0;
for (let i = 0; i < inputLen; i++) {
if (invisibleExtraLetters && i >= wordLen) break;
currentWordWidth +=
$(currentWordNodeList[i] as HTMLElement).outerWidth(true) ?? 0;
const letterOuterWidth =
$(currentWordNodeList[i] as Element).outerWidth(true) ?? 0;
currentWordWidth += letterOuterWidth;
if (letterOuterWidth > 0) lastPositiveLetterWidth = letterOuterWidth;
}
// if current letter has zero width move the caret to previous positive width letter
if ($(currentWordNodeList[inputLen] as Element).outerWidth(true) === 0)
currentWordWidth -= lastPositiveLetterWidth;
if (isLanguageRightToLeft) currentWordWidth *= -1;
result += currentWordWidth;
}
Expand Down
9 changes: 1 addition & 8 deletions frontend/src/ts/test/test-logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,13 +419,6 @@ export async function init(): Promise<void> {
}
}

if (Config.tapeMode !== "off" && language.rightToLeft) {
Notifications.add("This language does not support tape mode.", 0, {
important: true,
});
UpdateConfig.setTapeMode("off");
}

const allowLazyMode = !language.noLazyMode || Config.mode === "custom";
if (Config.lazyMode && !allowLazyMode) {
rememberLazyMode = true;
Expand Down Expand Up @@ -508,7 +501,7 @@ export async function init(): Promise<void> {
Funbox.toggleScript(TestWords.words.getCurrent());
TestUI.setRightToLeft(language.rightToLeft);
TestUI.setLigatures(language.ligatures ?? false);
TestUI.showWords();
await TestUI.showWords();
console.debug("Test initialized with words", generatedWords);
console.debug(
"Test initialized with section indexes",
Expand Down
99 changes: 61 additions & 38 deletions frontend/src/ts/test/test-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ const debouncedZipfCheck = debounce(250, async () => {
}
});

ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
ConfigEvent.subscribe(async (eventKey, eventValue, nosave) => {
if (
(eventKey === "language" || eventKey === "funbox") &&
Config.funbox.split("#").includes("zipf")
Expand All @@ -149,7 +149,7 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
if (eventKey === "fontSize" && !nosave) {
OutOfFocus.hide();
updateWordsWrapperHeight(true);
updateWordsMargin();
await updateWordsMargin();
void updateWordsInputPosition(true);
}
if (
Expand All @@ -166,7 +166,7 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {

if (eventValue === undefined) return;
if (eventKey === "highlightMode") {
if (ActivePage.get() === "test") updateActiveElement();
if (ActivePage.get() === "test") await updateActiveElement();
}

if (
Expand All @@ -178,7 +178,7 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
"hideExtraLetters",
].includes(eventKey)
) {
updateWordWrapperClasses();
await updateWordWrapperClasses();
}

if (eventKey === "showAllLines") {
Expand Down Expand Up @@ -246,10 +246,10 @@ export function blurWords(): void {
$("#wordsInput").trigger("blur");
}

export function updateActiveElement(
export async function updateActiveElement(
backspace?: boolean,
initial = false
): void {
): Promise<void> {
const active = document.querySelector("#words .active");
if (!backspace) {
active?.classList.add("typed");
Expand Down Expand Up @@ -277,7 +277,7 @@ export function updateActiveElement(
void updateWordsInputPosition();
}
if (!initial && Config.tapeMode !== "off") {
scrollTape();
await scrollTape();
}
}

Expand Down Expand Up @@ -353,16 +353,18 @@ function getWordHTML(word: string): string {
return retval;
}

function updateWordsMargin(): void {
async function updateWordsMargin(): Promise<void> {
if (Config.tapeMode !== "off") {
scrollTape(true);
await scrollTape(true);
} else {
$("#words").css("margin-left", "unset");
$(".afterNewline").css("margin-left", "unset");
setTimeout(() => {
$("#words").css("margin-left", "unset");
$(".afterNewline").css("margin-left", "unset");
}, 125);
}
}

function updateWordWrapperClasses(): void {
async function updateWordWrapperClasses(): Promise<void> {
if (Config.tapeMode !== "off") {
$("#words").addClass("tape");
$("#wordsWrapper").addClass("tape");
Expand Down Expand Up @@ -408,13 +410,13 @@ function updateWordWrapperClasses(): void {

updateWordsWidth();
updateWordsWrapperHeight(true);
updateWordsMargin();
await updateWordsMargin();
setTimeout(() => {
void updateWordsInputPosition(true);
}, 250);
}

export function showWords(): void {
export async function showWords(): Promise<void> {
$("#words").empty();

let wordsHTML = "";
Expand All @@ -428,12 +430,12 @@ export function showWords(): void {

$("#words").html(wordsHTML);

updateActiveElement(undefined, true);
await updateActiveElement(undefined, true);
setTimeout(() => {
void Caret.updatePosition();
}, 125);

updateWordWrapperClasses();
await updateWordWrapperClasses();
}

const posUpdateLangList = ["japanese", "chinese", "korean"];
Expand Down Expand Up @@ -917,17 +919,20 @@ export async function updateActiveWordLetters(
"<div class='beforeNewline'></div><div class='newline'></div><div class='afterNewline'></div>"
);
if (Config.tapeMode !== "off") {
scrollTape();
await scrollTape();
}
}

let allowWordRemoval = true;
export function scrollTape(noRemove = false): void {
export async function scrollTape(noRemove = false): Promise<void> {
if (ActivePage.get() !== "test" || resultVisible) return;

const waitForLineJumpAnimation = lineTransition && !allowWordRemoval;
if (waitForLineJumpAnimation) noRemove = true;

const currentLang = await JSONData.getCurrentLanguage(Config.language);
const isLanguageRTL = currentLang.rightToLeft;

const wordsWrapperWidth = (
document.querySelector("#wordsWrapper") as HTMLElement
).offsetWidth;
Expand Down Expand Up @@ -997,7 +1002,11 @@ export function scrollTape(noRemove = false): void {
const wordOuterWidth = $(child).outerWidth(true) ?? 0;
const forWordLeft = Math.floor(child.offsetLeft);
const forWordWidth = Math.floor(child.offsetWidth);
if (!noRemove && forWordLeft < 0 - forWordWidth) {
if (
!noRemove &&
((!isLanguageRTL && forWordLeft < 0 - forWordWidth) ||
(isLanguageRTL && forWordLeft > wordsWrapperWidth))
) {
toRemove.push(child);
widthToRemove += wordOuterWidth;
wordsToRemoveCount++;
Expand Down Expand Up @@ -1025,30 +1034,44 @@ export function scrollTape(noRemove = false): void {
const currentChildMargin = parseInt(element.style.marginLeft) || 0;
element.style.marginLeft = `${currentChildMargin - widthToRemove}px`;
}
if (isLanguageRTL) widthToRemove *= -1;
const currentWordsMargin = parseInt(wordsEl.style.marginLeft) || 0;
wordsEl.style.marginLeft = `${currentWordsMargin + widthToRemove}px`;
}

const inputLength = TestInput.input.current.length;
let currentWordWidth = 0;
if (Config.tapeMode === "letter") {
if (TestInput.input.current.length > 0) {
const letters = activeWordEl.querySelectorAll("letter");
for (let i = 0; i < TestInput.input.current.length; i++) {
const letter = letters[i] as HTMLElement;
if (
(Config.blindMode || Config.hideExtraLetters) &&
letter.classList.contains("extra")
) {
continue;
}
currentWordWidth += $(letter).outerWidth(true) ?? 0;
if (Config.tapeMode === "letter" && inputLength > 0) {
const letters = activeWordEl.querySelectorAll("letter");
let lastPositiveLetterWidth = 0;
for (let i = 0; i < inputLength; i++) {
const letter = letters[i] as HTMLElement;
if (
(Config.blindMode || Config.hideExtraLetters) &&
letter.classList.contains("extra")
) {
continue;
}
const letterOuterWidth = $(letter).outerWidth(true) ?? 0;
currentWordWidth += letterOuterWidth;
if (letterOuterWidth > 0) lastPositiveLetterWidth = letterOuterWidth;
}
}
let newMargin =
wordsWrapperWidth / 2 - (wordsWidthBeforeActive + currentWordWidth);
// if current letter has zero width move the tape to previous positive width letter
if ($(letters[inputLength] as Element).outerWidth(true) === 0)
currentWordWidth -= lastPositiveLetterWidth;
}

let newMargin = wordsWrapperWidth / 2;
if (isLanguageRTL)
newMargin +=
wordsWidthBeforeActive +
currentWordWidth -
wordsEl.offsetWidth +
wordRightMargin;
else newMargin -= wordsWidthBeforeActive + currentWordWidth;
if (waitForLineJumpAnimation)
newMargin = parseInt(wordsEl.style.marginLeft) || 0;

const jqWords = $(wordsEl);
if (Config.smoothLineScroll) {
jqWords.stop("leftMargin", true, false).animate(
Expand All @@ -1058,10 +1081,10 @@ export function scrollTape(noRemove = false): void {
{
duration: SlowTimer.get() ? 0 : 125,
queue: "leftMargin",
complete: () => {
complete: async () => {
if (noRemove) {
if (waitForLineJumpAnimation) scrollTape(true);
else scrollTape();
if (waitForLineJumpAnimation) await scrollTape(true);
else await scrollTape();
}
},
}
Expand All @@ -1082,7 +1105,7 @@ export function scrollTape(noRemove = false): void {
linesWidths.forEach((width, index) => {
(afterNewLineEls[index] as HTMLElement).style.marginLeft = `${width}px`;
});
if (noRemove) scrollTape();
if (noRemove) await scrollTape();
}
}

Expand Down
6 changes: 3 additions & 3 deletions frontend/src/ts/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ window.addEventListener("beforeunload", (event) => {
}
});

const debouncedEvent = debounce(250, () => {
const debouncedEvent = debounce(250, async () => {
void Caret.updatePosition();
if (getActivePage() === "test" && !TestUI.resultVisible) {
if (Config.tapeMode !== "off") {
TestUI.scrollTape();
await TestUI.scrollTape();
} else {
TestUI.updateTestLine();
}
Expand All @@ -112,7 +112,7 @@ const throttledEvent = throttle(250, () => {

$(window).on("resize", () => {
throttledEvent();
debouncedEvent();
void debouncedEvent();
});

ConfigEvent.subscribe((eventKey) => {
Expand Down

0 comments on commit 87dab9f

Please sign in to comment.