Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion frontend/src/html/pages/test-result.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@
data-balloon-pos="up"
class="bottom"
>
-
<div class="text">-</div>
<div class="score"></div>
</div>
</div>

Expand Down
6 changes: 6 additions & 0 deletions frontend/src/styles/test.scss
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,12 @@
&.dailyLeaderboard {
max-width: 13rem;
white-space: nowrap;

.score {
color: var(--sub-color);
font-size: 0.75rem;
line-height: 0.75rem;
}
}

&.source {
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/ts/commandline/commandline-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,12 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = {
},
alias: "pb",
},
showDailyLbStanding: {
subgroup: {
options: "fromSchema",
},
alias: "daily standing",
},
monkeyPowerLevel: {
alias: "powermode",
isVisible: false,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/ts/commandline/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export const commands: CommandsSubgroup = {
"capsLockWarning",
"showAverage",
"showPb",
"showDailyLbStanding",
"monkeyPowerLevel",
"monkey",
),
Expand Down
1 change: 1 addition & 0 deletions frontend/src/ts/components/pages/settings/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export function SettingsPage(): JSXElement {
<SearchableAutoSetting key="showOutOfFocusWarning" />
<SearchableAutoSetting key="capsLockWarning" />
<SearchableAutoSetting key="showAverage" />
<SearchableAutoSetting key="showDailyLbStanding" />
</Section>
<Section title="danger zone">
<ImportExport />
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/ts/config/metadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,15 @@ export const configMetadata: ConfigMetadataObject = {
changeRequiresRestart: false,
group: "hideElements",
},
showDailyLbStanding: {
key: "showDailyLbStanding",
fa: { icon: "fa-list-ol" },
displayString: "show daily leaderboard standing",
changeRequiresRestart: false,
group: "hideElements",
description:
"On the result page, show your current daily leaderboard standing even when the completed test did not improve your daily best. Only applies to modes with a daily leaderboard.",
},

// other (hidden)
accountChart: {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/ts/constants/default-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const obj: Config = {
lazyMode: false,
showAverage: "off",
showPb: false,
showDailyLbStanding: false,
tapeMode: "off",
tapeMargin: 50,
maxLineWidth: 0,
Expand Down
44 changes: 44 additions & 0 deletions frontend/src/ts/test/daily-lb-standing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { LeaderboardEntry } from "@monkeytype/schemas/leaderboards";
import { Language } from "@monkeytype/schemas/languages";
import { Mode, Mode2 } from "@monkeytype/schemas/shared";

import Ape from "../ape";
import { tryCatch } from "@monkeytype/util/trycatch";

// Daily leaderboards only exist for these modes. This is a client-side
// pre-filter to avoid pointless requests - the server still validates against
// its own configuration and responds with an error for unsupported modes.
export function canHaveDailyLeaderboard(
mode: Mode,
mode2: Mode2<Mode>,
): boolean {
return mode === "time" && (mode2 === "15" || mode2 === "60");
}

/**
* Get the current user's standing on today's daily leaderboard, regardless of
* whether the last completed test improved it.
* @returns the leaderboard entry, or null if the user has no entry today, the
* mode has no daily leaderboard or the request failed.
*/
export async function getCurrentStanding(
mode: Mode,
mode2: Mode2<Mode>,
language: Language,
): Promise<LeaderboardEntry | null> {
if (!canHaveDailyLeaderboard(mode, mode2)) {
return null;
}

const { data: response, error } = await tryCatch(
Ape.leaderboards.getDailyRank({
query: { mode, mode2, language },
}),
);

if (error !== null || response.status !== 200) {
return null;
}

return response.body.data;
}
68 changes: 64 additions & 4 deletions frontend/src/ts/test/test-logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ import { getAuthenticatedUser } from "../firebase";
import { highlight } from "../events/keymap";
import * as LazyModeState from "../legacy-states/remember-lazy-mode";
import Format from "../singletons/format";
import { Mode } from "@monkeytype/schemas/shared";
import { Mode, Mode2 } from "@monkeytype/schemas/shared";
import { Language } from "@monkeytype/schemas/languages";
import * as DailyLbStanding from "./daily-lb-standing";
import {
CompletedEvent,
CompletedEventCustomText,
Expand Down Expand Up @@ -1203,18 +1205,29 @@ async function saveResult(

if (data.dailyLeaderboardRank === undefined) {
dailyLeaderboardEl.classList.add("hidden");
if (Config.showDailyLbStanding) {
void showCurrentDailyStanding(
dailyLeaderboardEl,
result.mode,
result.mode2,
result.language,
);
}
} else {
dailyLeaderboardEl.classList.remove("hidden");
dailyLeaderboardEl.style.maxWidth = "13rem";

// undo the dimming/label a previous "current standing" render may have set
const valueEl = qs("#result .stats .dailyLeaderboard .bottom");
valueEl?.native.style.removeProperty("opacity");
valueEl?.setAttribute("aria-label", "Show daily leaderboard");

animate(dailyLeaderboardEl, {
opacity: [0, 1],
duration: Misc.applyReducedMotion(250),
});

qs("#result .stats .dailyLeaderboard .bottom")?.setHtml(
Format.rank(data.dailyLeaderboardRank, { fallback: "" }),
);
setDailyLeaderboardValue(data.dailyLeaderboardRank, result.wpm, result.acc);
}

qs("#retrySavingResultButton")?.hide();
Expand All @@ -1225,6 +1238,53 @@ async function saveResult(
return response;
}

// Shown when the completed test did not improve the user's daily best (so the
// server returned no rank) but the user still has an entry on today's
// leaderboard. Dimmed to distinguish it from a placement made by this test.
async function showCurrentDailyStanding(
dailyLeaderboardEl: HTMLElement,
mode: Mode,
mode2: Mode2<Mode>,
language: Language,
): Promise<void> {
const standing = await DailyLbStanding.getCurrentStanding(
mode,
mode2,
language,
);
if (standing === null || !getResultVisible()) return;

const valueEl = qs("#result .stats .dailyLeaderboard .bottom");
dailyLeaderboardEl.classList.remove("hidden");
dailyLeaderboardEl.style.maxWidth = "13rem";
valueEl?.native.style.setProperty("opacity", "0.5");
valueEl?.setAttribute("aria-label", "current standing");

animate(dailyLeaderboardEl, {
opacity: [0, 1],
duration: Misc.applyReducedMotion(250),
});

setDailyLeaderboardValue(standing.rank, standing.wpm, standing.acc);
}

// The rank sits in .text, with the score that holds it below in .score, so the
// user can see what they need to beat without opening the leaderboard.
function setDailyLeaderboardValue(
rank: number,
wpm: number,
acc: number,
): void {
qs("#result .stats .dailyLeaderboard .bottom .text")?.setHtml(
Format.rank(rank, { fallback: "" }),
);
qs("#result .stats .dailyLeaderboard .bottom .score")?.setText(
`${Format.typingSpeed(wpm, {
suffix: ` ${Config.typingSpeedUnit}`,
})} ${Format.accuracy(acc)}`,
);
}

export function fail(reason: string): void {
failReason = reason;
void finish(true);
Expand Down
4 changes: 4 additions & 0 deletions packages/schemas/src/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ export type ShowAverage = z.infer<typeof ShowAverageSchema>;
export const ShowPbSchema = z.boolean();
export type ShowPb = z.infer<typeof ShowPbSchema>;

export const ShowDailyLbStandingSchema = z.boolean();
export type ShowDailyLbStanding = z.infer<typeof ShowDailyLbStandingSchema>;

export const ColorHexValueSchema = z.string().regex(/^#([\da-f]{3}){1,2}$/i);
export type ColorHexValue = z.infer<typeof ColorHexValueSchema>;

Expand Down Expand Up @@ -498,6 +501,7 @@ export const ConfigSchema = z
capsLockWarning: z.boolean(),
showAverage: ShowAverageSchema,
showPb: ShowPbSchema,
showDailyLbStanding: ShowDailyLbStandingSchema,

// other (hidden)
accountChart: AccountChartSchema,
Expand Down
Loading