mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-11 08:05:56 +08:00
Added tape mode (#2862)
* added config param for tape mode * added settings section * added commands * moving caret to the correct place with tape mode is active * only jumping line when not using tape mode * prettier * scrolling tape * restarting test when tape mode changed * showing one line when tape mode is active * added function for scrolling words horizontally * scrolling tape when layout emulator is enabled * removed left margin when tape is active * disable tape for some challenges * fixed incorrect caret position when going back to a previous word * ignoring no-this-alias * fixed lines scrolling to often when tape mode was disabled * storing caret width in a variable * not allowing tape mode and show all lines together * removed unnecessary comment
This commit is contained in:
parent
d65fcff79d
commit
9ca477da54
13 changed files with 283 additions and 24 deletions
|
@ -88,6 +88,7 @@ const CONFIG_SCHEMA = joi.object({
|
|||
minWpm: joi.string().valid("off", "custom"),
|
||||
minWpmCustomSpeed: joi.number().min(0),
|
||||
highlightMode: joi.string().valid("off", "letter", "word"),
|
||||
tapeMode: joi.string().valid("off", "letter", "word"),
|
||||
alwaysShowCPM: joi.boolean(),
|
||||
enableAds: joi.string().valid("off", "on", "max"),
|
||||
hideExtraLetters: joi.boolean(),
|
||||
|
|
|
@ -528,6 +528,11 @@ export function setCapsLockWarning(val: boolean, nosave?: boolean): boolean {
|
|||
export function setShowAllLines(sal: boolean, nosave?: boolean): boolean {
|
||||
if (!isConfigValueValid("show all lines", sal, ["boolean"])) return false;
|
||||
|
||||
if (sal && config.tapeMode !== "off") {
|
||||
Notifications.add("Show all lines doesn't support tape mode", 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
config.showAllLines = sal;
|
||||
saveToLocalStorage("showAllLines", nosave);
|
||||
ConfigEvent.dispatch("showAllLines", config.showAllLines, nosave);
|
||||
|
@ -832,6 +837,25 @@ export function setHighlightMode(
|
|||
return true;
|
||||
}
|
||||
|
||||
export function setTapeMode(
|
||||
mode: MonkeyTypes.TapeMode,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (!isConfigValueValid("tape mode", mode, [["off", "letter", "word"]])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode !== "off" && config.showAllLines === true) {
|
||||
setShowAllLines(false, true);
|
||||
}
|
||||
|
||||
config.tapeMode = mode;
|
||||
saveToLocalStorage("tapeMode", nosave);
|
||||
ConfigEvent.dispatch("tapeMode", config.tapeMode);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function setHideExtraLetters(val: boolean, nosave?: boolean): boolean {
|
||||
if (!isConfigValueValid("hide extra letters", val, ["boolean"])) return false;
|
||||
|
||||
|
@ -1793,6 +1817,7 @@ export function apply(
|
|||
setBritishEnglish(configObj.britishEnglish, true);
|
||||
setLazyMode(configObj.lazyMode, true);
|
||||
setShowAverage(configObj.showAverage, true);
|
||||
setTapeMode(configObj.tapeMode, true);
|
||||
|
||||
try {
|
||||
setEnableAds(configObj.enableAds, true);
|
||||
|
|
|
@ -96,4 +96,5 @@ export default <MonkeyTypes.Config>{
|
|||
britishEnglish: false,
|
||||
lazyMode: false,
|
||||
showAverage: "off",
|
||||
tapeMode: "off",
|
||||
};
|
||||
|
|
|
@ -255,7 +255,11 @@ function handleSpace(): void {
|
|||
nextTop = 0;
|
||||
}
|
||||
|
||||
if (nextTop > currentTop && !TestUI.lineTransition) {
|
||||
if (
|
||||
Config.tapeMode === "off" &&
|
||||
nextTop > currentTop &&
|
||||
!TestUI.lineTransition
|
||||
) {
|
||||
TestUI.lineJump(currentTop);
|
||||
}
|
||||
} //end of line wrap
|
||||
|
@ -816,6 +820,9 @@ $(document).keydown(async (event) => {
|
|||
handleChar(char, TestInput.input.current.length);
|
||||
updateUI();
|
||||
setWordsInput(" " + TestInput.input.current);
|
||||
if (Config.tapeMode !== "off") {
|
||||
TestUI.scrollTape();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -888,6 +895,9 @@ $("#wordsInput").on("input", (event) => {
|
|||
|
||||
setWordsInput(" " + TestInput.input.current);
|
||||
updateUI();
|
||||
if (Config.tapeMode !== "off") {
|
||||
TestUI.scrollTape();
|
||||
}
|
||||
|
||||
// force caret at end of input
|
||||
// doing it on next cycle because Chromium on Android won't let me edit
|
||||
|
|
|
@ -1729,6 +1729,37 @@ const commandsHighlightMode: MonkeyTypes.CommandsGroup = {
|
|||
],
|
||||
};
|
||||
|
||||
const commandsTapeMode: MonkeyTypes.CommandsGroup = {
|
||||
title: "Tape mode...",
|
||||
configKey: "tapeMode",
|
||||
list: [
|
||||
{
|
||||
id: "setTapeModeOff",
|
||||
display: "off",
|
||||
configValue: "off",
|
||||
exec: (): void => {
|
||||
UpdateConfig.setTapeMode("off");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "setTapeModeLetter",
|
||||
display: "letter",
|
||||
configValue: "letter",
|
||||
exec: (): void => {
|
||||
UpdateConfig.setTapeMode("letter");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "setTapeModeWord",
|
||||
display: "word",
|
||||
configValue: "word",
|
||||
exec: (): void => {
|
||||
UpdateConfig.setTapeMode("word");
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const commandsTimerStyle: MonkeyTypes.CommandsGroup = {
|
||||
title: "Timer/progress style...",
|
||||
configKey: "timerStyle",
|
||||
|
@ -2948,6 +2979,12 @@ export const defaultCommands: MonkeyTypes.CommandsGroup = {
|
|||
icon: "fa-highlighter",
|
||||
subgroup: commandsHighlightMode,
|
||||
},
|
||||
{
|
||||
id: "changeTapeMode",
|
||||
display: "Tape mode...",
|
||||
icon: "fa-tape",
|
||||
subgroup: commandsTapeMode,
|
||||
},
|
||||
{
|
||||
id: "changeShowAverage",
|
||||
display: "Show average...",
|
||||
|
|
|
@ -334,6 +334,11 @@ async function initGroups(): Promise<void> {
|
|||
UpdateConfig.setHighlightMode,
|
||||
"button"
|
||||
);
|
||||
groups["tapeMode"] = new SettingsGroup(
|
||||
"tapeMode",
|
||||
UpdateConfig.setTapeMode,
|
||||
"button"
|
||||
);
|
||||
groups["timerOpacity"] = new SettingsGroup(
|
||||
"timerOpacity",
|
||||
UpdateConfig.setTimerOpacity,
|
||||
|
|
|
@ -36,6 +36,9 @@ export async function updatePosition(): Promise<void> {
|
|||
// }
|
||||
|
||||
const caret = $("#caret");
|
||||
const caretWidth = Math.round(
|
||||
document.querySelector("#caret")?.getBoundingClientRect().width ?? 0
|
||||
);
|
||||
|
||||
let inputLen = TestInput.input.current.length;
|
||||
inputLen = Misc.trailingComposeChars.test(TestInput.input.current)
|
||||
|
@ -78,18 +81,43 @@ export async function updatePosition(): Promise<void> {
|
|||
let newLeft = 0;
|
||||
|
||||
newTop = currentLetterPosTop - Math.round(letterHeight / 5);
|
||||
if (inputLen == 0) {
|
||||
newLeft = isLanguageLeftToRight
|
||||
? currentLetterPosLeft - (caret.width() as number) / 2
|
||||
: currentLetterPosLeft + (caret.width() as number) / 2;
|
||||
|
||||
if (Config.tapeMode === "letter") {
|
||||
newLeft =
|
||||
($(<HTMLElement>document.querySelector("#wordsWrapper")).width() ?? 0) /
|
||||
2 -
|
||||
caretWidth / 2;
|
||||
} else if (Config.tapeMode === "word") {
|
||||
if (inputLen == 0) {
|
||||
newLeft =
|
||||
($(<HTMLElement>document.querySelector("#wordsWrapper")).width() ?? 0) /
|
||||
2 -
|
||||
caretWidth / 2;
|
||||
} else {
|
||||
let inputWidth = 0;
|
||||
for (let i = 0; i < inputLen; i++) {
|
||||
inputWidth += $(currentWordNodeList[i]).outerWidth(true) as number;
|
||||
}
|
||||
newLeft =
|
||||
($(<HTMLElement>document.querySelector("#wordsWrapper")).width() ?? 0) /
|
||||
2 +
|
||||
inputWidth -
|
||||
caretWidth / 2;
|
||||
}
|
||||
} else {
|
||||
newLeft = isLanguageLeftToRight
|
||||
? currentLetterPosLeft +
|
||||
($(currentLetter).width() as number) -
|
||||
(caret.width() as number) / 2
|
||||
: currentLetterPosLeft -
|
||||
($(currentLetter).width() as number) +
|
||||
(caret.width() as number) / 2;
|
||||
if (inputLen == 0) {
|
||||
newLeft = isLanguageLeftToRight
|
||||
? currentLetterPosLeft - caretWidth / 2
|
||||
: currentLetterPosLeft + caretWidth / 2;
|
||||
} else {
|
||||
newLeft = isLanguageLeftToRight
|
||||
? currentLetterPosLeft +
|
||||
($(currentLetter).width() as number) -
|
||||
caretWidth / 2
|
||||
: currentLetterPosLeft -
|
||||
($(currentLetter).width() as number) +
|
||||
caretWidth / 2;
|
||||
}
|
||||
}
|
||||
|
||||
let smoothlinescroll = $("#words .smoothScroller").height();
|
||||
|
|
|
@ -1736,8 +1736,9 @@ $(document).on("click", "#top #menu #startTestButton, #top .logo", () => {
|
|||
|
||||
ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
|
||||
if (eventKey === "difficulty" && !nosave) restart(false, nosave);
|
||||
if (eventKey === "showAllLines" && !nosave) restart();
|
||||
if (eventKey === "showAllLines" && !nosave) restart(false, nosave);
|
||||
if (eventKey === "keymapMode" && !nosave) restart(false, nosave);
|
||||
if (eventKey === "tapeMode" && !nosave) restart(false, nosave);
|
||||
if (eventKey === "lazyMode" && eventValue === false && !nosave) {
|
||||
rememberLazyMode = false;
|
||||
}
|
||||
|
|
|
@ -167,13 +167,29 @@ export function showWords(): void {
|
|||
}
|
||||
$(".outOfFocusWarning").css("line-height", nh + "px");
|
||||
} else {
|
||||
$("#words")
|
||||
.css("height", wordHeight * 4 + "px")
|
||||
.css("overflow", "hidden");
|
||||
$("#wordsWrapper")
|
||||
.css("height", wordHeight * 3 + "px")
|
||||
.css("overflow", "hidden");
|
||||
$(".outOfFocusWarning").css("line-height", wordHeight * 3 + "px");
|
||||
if (Config.tapeMode !== "off") {
|
||||
$("#words")
|
||||
.css("height", wordHeight * 2 + "px")
|
||||
.css("overflow", "hidden")
|
||||
.css("width", "200%")
|
||||
.css("margin-left", "50%");
|
||||
$("#words").addClass("tape");
|
||||
$("#wordsWrapper")
|
||||
.css("height", wordHeight * 1 + "px")
|
||||
.css("overflow", "hidden");
|
||||
$(".outOfFocusWarning").css("line-height", wordHeight * 1 + "px");
|
||||
} else {
|
||||
$("#words")
|
||||
.css("height", wordHeight * 4 + "px")
|
||||
.css("overflow", "hidden")
|
||||
.css("width", "100%")
|
||||
.css("margin-left", "unset");
|
||||
$("#words").removeClass("tape");
|
||||
$("#wordsWrapper")
|
||||
.css("height", wordHeight * 3 + "px")
|
||||
.css("overflow", "hidden");
|
||||
$(".outOfFocusWarning").css("line-height", wordHeight * 3 + "px");
|
||||
}
|
||||
}
|
||||
|
||||
if (Config.mode === "zen") {
|
||||
|
@ -481,6 +497,60 @@ export function updateWordElement(showError = !Config.blindMode): void {
|
|||
if (newlineafter) $("#words").append("<div class='newline'></div>");
|
||||
}
|
||||
|
||||
export function scrollTape(): void {
|
||||
const wordsWrapperWidth = (<HTMLElement>(
|
||||
document.querySelector("#wordsWrapper")
|
||||
)).offsetWidth;
|
||||
let fullWordsWidth = 0;
|
||||
const toHide: JQuery<HTMLElement>[] = [];
|
||||
let widthToHide = 0;
|
||||
if (currentWordElementIndex > 0) {
|
||||
for (let i = 0; i < currentWordElementIndex; i++) {
|
||||
const word = <HTMLElement>document.querySelectorAll("#words .word")[i];
|
||||
fullWordsWidth += $(word).outerWidth(true) ?? 0;
|
||||
const forWordLeft = Math.floor(word.offsetLeft);
|
||||
const forWordWidth = Math.floor(word.offsetWidth);
|
||||
if (forWordLeft < 0 - forWordWidth) {
|
||||
const toPush = $($("#words .word")[i]);
|
||||
toHide.push(toPush);
|
||||
widthToHide += toPush.outerWidth(true) ?? 0;
|
||||
}
|
||||
}
|
||||
if (toHide.length > 0) {
|
||||
currentWordElementIndex -= toHide.length;
|
||||
toHide.forEach((e) => e.remove());
|
||||
fullWordsWidth -= widthToHide;
|
||||
const currentMargin = parseInt($("#words").css("margin-left"), 10);
|
||||
$("#words").css("margin-left", `${currentMargin + widthToHide}px`);
|
||||
}
|
||||
}
|
||||
let currentWordWidth = 0;
|
||||
if (Config.tapeMode === "letter") {
|
||||
if (TestInput.input.current.length > 0) {
|
||||
for (let i = 0; i < TestInput.input.current.length; i++) {
|
||||
const words = document.querySelectorAll("#words .word");
|
||||
currentWordWidth +=
|
||||
$(
|
||||
words[currentWordElementIndex].querySelectorAll("letter")[i]
|
||||
).outerWidth(true) ?? 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
const newMargin = wordsWrapperWidth / 2 - (fullWordsWidth + currentWordWidth);
|
||||
if (Config.smoothLineScroll) {
|
||||
$("#words")
|
||||
.stop(true, false)
|
||||
.animate(
|
||||
{
|
||||
marginLeft: newMargin,
|
||||
},
|
||||
SlowTimer.get() ? 0 : 125
|
||||
);
|
||||
} else {
|
||||
$("#words").css("margin-left", `${newMargin}px`);
|
||||
}
|
||||
}
|
||||
|
||||
export function lineJump(currentTop: number): void {
|
||||
//last word of the line
|
||||
if (currentTestLine > 0) {
|
||||
|
|
3
frontend/src/scripts/types/types.d.ts
vendored
3
frontend/src/scripts/types/types.d.ts
vendored
|
@ -71,6 +71,8 @@ declare namespace MonkeyTypes {
|
|||
|
||||
type ShowAverage = "off" | "wpm" | "acc" | "both";
|
||||
|
||||
type TapeMode = "off" | "letter" | "word";
|
||||
|
||||
type SingleListCommandLine = "manual" | "on";
|
||||
|
||||
/*
|
||||
|
@ -377,6 +379,7 @@ declare namespace MonkeyTypes {
|
|||
britishEnglish: boolean;
|
||||
lazyMode: boolean;
|
||||
showAverage: ShowAverage;
|
||||
tapeMode: TapeMode;
|
||||
}
|
||||
|
||||
type ConfigValues =
|
||||
|
|
|
@ -253,6 +253,33 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.tape {
|
||||
&.size125 .word {
|
||||
margin: 0.31rem;
|
||||
margin: 0.31rem 0.62rem 0.31rem 0;
|
||||
}
|
||||
|
||||
&.size15 .word {
|
||||
margin: 0.37rem;
|
||||
margin: 0.37rem 0.74rem 0.37rem 0;
|
||||
}
|
||||
|
||||
&.size2 .word {
|
||||
margin: 0.5rem;
|
||||
margin: 0.5rem 1rem 0.5rem 0;
|
||||
}
|
||||
|
||||
&.size3 .word {
|
||||
margin: 0.75rem;
|
||||
margin: 0.75rem 1.5rem 0.75rem 0;
|
||||
}
|
||||
|
||||
&.size4 .word {
|
||||
margin: 1rem;
|
||||
margin: 1rem 2rem 1rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.word {
|
||||
|
|
|
@ -246,19 +246,34 @@
|
|||
"name": "inAGalaxyFarFarAway",
|
||||
"display": "In a galaxy far far away",
|
||||
"type": "script",
|
||||
"parameters": ["episode4.txt",null,"space_balls"]
|
||||
"parameters": ["episode4.txt",null,"space_balls"],
|
||||
"requirements": {
|
||||
"config": {
|
||||
"tapeMode": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
,{
|
||||
"name": "whosYourDaddy",
|
||||
"display": "Who's your daddy?",
|
||||
"type": "script",
|
||||
"parameters": ["episode5.txt",null,"space_balls"]
|
||||
"parameters": ["episode5.txt",null,"space_balls"],
|
||||
"requirements": {
|
||||
"config": {
|
||||
"tapeMode": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
,{
|
||||
"name": "itsATrap",
|
||||
"display": "It's a trap!",
|
||||
"type": "script",
|
||||
"parameters": ["episode6.txt",null,"space_balls"]
|
||||
"parameters": ["episode6.txt",null,"space_balls"],
|
||||
"requirements": {
|
||||
"config": {
|
||||
"tapeMode": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
,{
|
||||
"name": "jolly",
|
||||
|
@ -399,7 +414,12 @@
|
|||
"name": "mnemonist",
|
||||
"display": "Mnemonist",
|
||||
"type": "funbox",
|
||||
"parameters": ["memory","words",25,"master"]
|
||||
"parameters": ["memory","words",25,"master"],
|
||||
"requirements": {
|
||||
"config": {
|
||||
"tapeMode": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
,{
|
||||
"name": "earfquake",
|
||||
|
@ -461,6 +481,9 @@
|
|||
},
|
||||
"funbox": {
|
||||
"exact": "read_ahead"
|
||||
},
|
||||
"config": {
|
||||
"tapeMode": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -479,6 +502,9 @@
|
|||
},
|
||||
"funbox": {
|
||||
"exact": "read_ahead_hard"
|
||||
},
|
||||
"config": {
|
||||
"tapeMode": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1257,6 +1257,31 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section tapeMode" section="">
|
||||
<h1>tape mode</h1>
|
||||
<div class="text">
|
||||
Only shows one line which scrolls horizontally. Setting this to 'word'
|
||||
will make it scroll after every word and 'letter' will scroll after
|
||||
every keypress. Works best with smooth line scroll enabled and a
|
||||
monospace font.
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div class="button" tapeMode="off" tabindex="0" onclick="this.blur();">
|
||||
off
|
||||
</div>
|
||||
<div
|
||||
class="button"
|
||||
tapeMode="letter"
|
||||
tabindex="0"
|
||||
onclick="this.blur();"
|
||||
>
|
||||
letter
|
||||
</div>
|
||||
<div class="button" tapeMode="word" tabindex="0" onclick="this.blur();">
|
||||
word
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section smoothLineScroll">
|
||||
<h1>smooth line scroll</h1>
|
||||
<div class="text">
|
||||
|
|
Loading…
Add table
Reference in a new issue