feat: add tape margin

This commit is contained in:
Miodec 2025-01-06 13:37:52 +01:00
parent b490056e36
commit c9789d6b1b
10 changed files with 153 additions and 8 deletions

View file

@ -826,6 +826,30 @@
<button data-config-value="word">word</button>
</div>
</div>
<div class="section" data-config-name="tapeMargin">
<div class="groupTitle">
<i class="fas fa-tape"></i>
<span>tape margin</span>
</div>
<div class="text">
When in tape mode, set the carets position from the left edge of the
typing test as a percentage (for example, 50% centers it).
</div>
<div class="inputs">
<div class="inputAndButton">
<input
type="number"
placeholder="tape margin"
class="input"
min="10"
max="90"
/>
<button class="save no-auto-handle">
<i class="fas fa-save fa-fw"></i>
</button>
</div>
</div>
</div>
<div class="section" data-config-name="smoothLineScroll">
<div class="groupTitle">
<i class="fas fa-align-left"></i>

View file

@ -1254,6 +1254,8 @@
}
}
#liveStatsMini {
width: 0;
justify-content: start;
height: 0;
margin-left: 0.25em;
display: flex;

View file

@ -51,6 +51,7 @@ import TimerColorCommands from "./lists/timer-color";
import TimerOpacityCommands from "./lists/timer-opacity";
import HighlightModeCommands from "./lists/highlight-mode";
import TapeModeCommands from "./lists/tape-mode";
import TapeMarginCommands from "./lists/tape-margin";
import BritishEnglishCommands from "./lists/british-english";
import KeymapModeCommands from "./lists/keymap-mode";
import KeymapStyleCommands from "./lists/keymap-style";
@ -273,6 +274,7 @@ export const commands: CommandsSubgroup = {
...TimerOpacityCommands,
...HighlightModeCommands,
...TapeModeCommands,
...TapeMarginCommands,
...SmoothLineScrollCommands,
...ShowAllLinesCommands,
...TypingSpeedUnitCommands,

View file

@ -0,0 +1,19 @@
import Config, * as UpdateConfig from "../../config";
import { Command } from "../types";
const commands: Command[] = [
{
id: "changeTapeMargin",
display: "Tape margin...",
icon: "fa-tape",
input: true,
defaultValue: (): string => {
return Config.tapeMargin.toString();
},
exec: ({ input }): void => {
if (input === undefined || input === "") return;
UpdateConfig.setTapeMargin(parseFloat(input));
},
},
];
export default commands;

View file

@ -918,6 +918,34 @@ export function setTapeMode(
return true;
}
export function setTapeMargin(
value: ConfigSchemas.TapeMargin,
nosave?: boolean
): boolean {
if (value < 10) {
value = 10;
}
if (value > 90) {
value = 90;
}
if (
!isConfigValueValid("max line width", value, ConfigSchemas.TapeMarginSchema)
) {
return false;
}
config.tapeMargin = value;
saveToLocalStorage("tapeMargin", nosave);
ConfigEvent.dispatch("tapeMargin", config.tapeMargin, nosave);
// trigger a resize event to update the layout - handled in ui.ts:108
$(window).trigger("resize");
return true;
}
export function setHideExtraLetters(val: boolean, nosave?: boolean): boolean {
if (!isConfigValueValidBoolean("hide extra letters", val)) return false;
@ -2053,6 +2081,7 @@ export async function apply(
setLazyMode(configObj.lazyMode, true);
setShowAverage(configObj.showAverage, true);
setTapeMode(configObj.tapeMode, true);
setTapeMargin(configObj.tapeMargin, true);
ConfigEvent.dispatch(
"configApplied",

View file

@ -101,6 +101,7 @@ const obj = {
lazyMode: false,
showAverage: "off",
tapeMode: "off",
tapeMargin: 10,
maxLineWidth: 0,
} as Config;

View file

@ -379,6 +379,11 @@ async function initGroups(): Promise<void> {
UpdateConfig.setTapeMode,
"button"
) as SettingsGroup<ConfigValue>;
groups["tapeMargin"] = new SettingsGroup(
"tapeMargin",
UpdateConfig.setTapeMargin,
"button"
) as SettingsGroup<ConfigValue>;
groups["timerOpacity"] = new SettingsGroup(
"timerOpacity",
UpdateConfig.setTimerOpacity,
@ -694,6 +699,10 @@ async function fillSettingsPage(): Promise<void> {
Config.customLayoutfluid.replace(/#/g, " ")
);
$(".pageSettings .section[data-config-name='tapeMargin'] input").val(
Config.tapeMargin
);
setEventDisabled(true);
if (!groupsInitialized) {
await initGroups();
@ -1159,6 +1168,42 @@ $(
}
});
$(
".pageSettings .section[data-config-name='tapeMargin'] .inputAndButton button.save"
).on("click", () => {
const didConfigSave = UpdateConfig.setTapeMargin(
parseFloat(
$(
".pageSettings .section[data-config-name='tapeMargin'] .inputAndButton input"
).val() as string
)
);
if (didConfigSave) {
Notifications.add("Saved", 1, {
duration: 1,
});
}
});
$(
".pageSettings .section[data-config-name='tapeMargin'] .inputAndButton input"
).on("keypress", (e) => {
if (e.key === "Enter") {
const didConfigSave = UpdateConfig.setTapeMargin(
parseFloat(
$(
".pageSettings .section[data-config-name='tapeMargin'] .inputAndButton input"
).val() as string
)
);
if (didConfigSave) {
Notifications.add("Saved", 1, {
duration: 1,
});
}
}
});
$(
".pageSettings .section[data-config-name='maxLineWidth'] .inputAndButton button.save"
).on("click", () => {

View file

@ -106,8 +106,10 @@ function getTargetPositionLeft(
} else {
const wordsWrapperWidth =
$(document.querySelector("#wordsWrapper") as HTMLElement).width() ?? 0;
const tapeMargin = wordsWrapperWidth * (Config.tapeMargin / 100);
result =
wordsWrapperWidth / 2 -
tapeMargin -
(fullWidthCaret && isLanguageRightToLeft ? fullWidthCaretWidth : 0);
if (Config.tapeMode === "word" && inputLen > 0) {

View file

@ -184,13 +184,14 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
if (eventKey === "tapeMode" && !nosave) {
if (eventValue === "off") {
$("#words").css("margin-left", "unset");
$("#liveStatsMini").css("display", "").css("justify-content", "");
} else {
scrollTape();
$("#liveStatsMini")
.css("display", "flex")
.css("justify-content", "center");
}
updateLiveStatsMargin();
}
if (eventKey === "tapeMargin" && !nosave) {
updateLiveStatsMargin();
}
if (typeof eventValue !== "boolean") return;
@ -474,7 +475,7 @@ export async function updateWordsInputPosition(initial = false): Promise<void> {
if (Config.tapeMode !== "off") {
el.style.top = targetTop + "px";
el.style.left = "50%";
el.style.left = Config.tapeMargin + "%";
return;
}
@ -949,7 +950,7 @@ export function scrollTape(): void {
.stop(true, false)
.animate(
{
marginLeft: "50%",
marginLeft: Config.tapeMargin + "%",
},
SlowTimer.get() ? 0 : 125
);
@ -1000,7 +1001,9 @@ export function scrollTape(): void {
}
}
}
const newMargin = wordsWrapperWidth / 2 - (fullWordsWidth + currentWordWidth);
const tapeMargin = wordsWrapperWidth * (Config.tapeMargin / 100);
const newMargin = tapeMargin - (fullWordsWidth + currentWordWidth);
if (Config.smoothLineScroll) {
$("#words")
.stop(true, false)
@ -1478,6 +1481,20 @@ function updateWordsWidth(): void {
}
}
function updateLiveStatsMargin(): void {
if (Config.tapeMode === "off") {
$("#liveStatsMini").css({
"justify-content": "start",
"margin-left": "unset",
});
} else {
$("#liveStatsMini").css({
"justify-content": "center",
"margin-left": Config.tapeMargin + "%",
});
}
}
function updateLiveStatsOpacity(value: TimerOpacity): void {
$("#barTimerProgress").css("opacity", parseFloat(value as string));
$("#liveStatsTextTop").css("opacity", parseFloat(value as string));

View file

@ -158,6 +158,9 @@ export type HighlightMode = z.infer<typeof HighlightModeSchema>;
export const TapeModeSchema = z.enum(["off", "letter", "word"]);
export type TapeMode = z.infer<typeof TapeModeSchema>;
export const TapeMarginSchema = z.number().min(10).max(90);
export type TapeMargin = z.infer<typeof TapeMarginSchema>;
export const TypingSpeedUnitSchema = z.enum([
"wpm",
"cpm",
@ -354,6 +357,7 @@ export const ConfigSchema = z
minWpmCustomSpeed: MinWpmCustomSpeedSchema,
highlightMode: HighlightModeSchema,
tapeMode: TapeModeSchema,
tapeMargin: TapeMarginSchema,
typingSpeedUnit: TypingSpeedUnitSchema,
ads: AdsSchema,
hideExtraLetters: z.boolean(),