mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2024-09-20 23:36:37 +08:00
Merge branch 'master' of https://github.com/Miodec/monkeytype
This commit is contained in:
commit
f495fb3f36
|
@ -1,4 +1,5 @@
|
|||
import _ from "lodash";
|
||||
import { getCurrentDayTimestamp, MILLISECONDS_IN_DAY } from "../../utils/misc";
|
||||
import { MonkeyResponse } from "../../utils/monkey-response";
|
||||
import * as LeaderboardsDAL from "../../dal/leaderboards";
|
||||
import MonkeyError from "../../utils/error";
|
||||
|
@ -72,13 +73,21 @@ export async function getRankFromLeaderboard(
|
|||
function getDailyLeaderboardWithError(
|
||||
req: MonkeyTypes.Request
|
||||
): DailyLeaderboards.DailyLeaderboard {
|
||||
const { language, mode, mode2 } = req.query;
|
||||
const { language, mode, mode2, daysBefore } = req.query;
|
||||
|
||||
const normalizedDayBefore = parseInt(daysBefore as string, 10);
|
||||
const currentDayTimestamp = getCurrentDayTimestamp();
|
||||
const dayBeforeTimestamp =
|
||||
currentDayTimestamp - normalizedDayBefore * MILLISECONDS_IN_DAY;
|
||||
|
||||
const customTimestamp = _.isNil(daysBefore) ? -1 : dayBeforeTimestamp;
|
||||
|
||||
const dailyLeaderboard = DailyLeaderboards.getDailyLeaderboard(
|
||||
language as string,
|
||||
mode as string,
|
||||
mode2 as string,
|
||||
req.ctx.configuration.dailyLeaderboards
|
||||
req.ctx.configuration.dailyLeaderboards,
|
||||
customTimestamp
|
||||
);
|
||||
if (!dailyLeaderboard) {
|
||||
throw new MonkeyError(404, "There is no daily leaderboard for this mode");
|
||||
|
|
|
@ -22,6 +22,11 @@ const LEADERBOARD_VALIDATION_SCHEMA_WITH_LIMIT = {
|
|||
limit: joi.number().min(0).max(50),
|
||||
};
|
||||
|
||||
const DAILY_LEADERBOARD_VALIDATION_SCHEMA = {
|
||||
...LEADERBOARD_VALIDATION_SCHEMA_WITH_LIMIT,
|
||||
daysBefore: joi.number().min(1).max(1),
|
||||
};
|
||||
|
||||
const router = Router();
|
||||
|
||||
const requireDailyLeaderboardsEnabled = validateConfiguration({
|
||||
|
@ -58,7 +63,7 @@ router.get(
|
|||
RateLimit.leaderboardsGet,
|
||||
authenticateRequest({ isPublic: true }),
|
||||
validateRequest({
|
||||
query: LEADERBOARD_VALIDATION_SCHEMA_WITH_LIMIT,
|
||||
query: DAILY_LEADERBOARD_VALIDATION_SCHEMA,
|
||||
}),
|
||||
asyncHandler(LeaderboardController.getDailyLeaderboard)
|
||||
);
|
||||
|
@ -69,7 +74,7 @@ router.get(
|
|||
RateLimit.leaderboardsGet,
|
||||
authenticateRequest(),
|
||||
validateRequest({
|
||||
query: BASE_LEADERBOARD_VALIDATION_SCHEMA,
|
||||
query: DAILY_LEADERBOARD_VALIDATION_SCHEMA,
|
||||
}),
|
||||
asyncHandler(LeaderboardController.getDailyLeaderboardRank)
|
||||
);
|
||||
|
|
|
@ -41,10 +41,10 @@ async function bootServer(port: number): Promise<Server> {
|
|||
Logger.info("Initializing task queues...");
|
||||
initJobQueue(RedisClient.getConnection());
|
||||
Logger.success("Task queues initialized");
|
||||
|
||||
initializeDailyLeaderboardsCache(liveConfiguration.dailyLeaderboards);
|
||||
}
|
||||
|
||||
initializeDailyLeaderboardsCache(liveConfiguration.dailyLeaderboards);
|
||||
|
||||
Logger.info("Starting cron jobs...");
|
||||
jobs.forEach((job) => job.start());
|
||||
Logger.success("Cron jobs started");
|
||||
|
|
|
@ -173,7 +173,8 @@ export function getDailyLeaderboard(
|
|||
language: string,
|
||||
mode: string,
|
||||
mode2: string,
|
||||
dailyLeaderboardsConfig: MonkeyTypes.Configuration["dailyLeaderboards"]
|
||||
dailyLeaderboardsConfig: MonkeyTypes.Configuration["dailyLeaderboards"],
|
||||
customTimestamp = -1
|
||||
): DailyLeaderboard | null {
|
||||
const { validModeRules, enabled } = dailyLeaderboardsConfig;
|
||||
|
||||
|
@ -188,10 +189,15 @@ export function getDailyLeaderboard(
|
|||
return null;
|
||||
}
|
||||
|
||||
const key = `${language}:${mode}:${mode2}`;
|
||||
const key = `${language}:${mode}:${mode2}:${customTimestamp}`;
|
||||
|
||||
if (!DAILY_LEADERBOARDS.has(key)) {
|
||||
const dailyLeaderboard = new DailyLeaderboard(language, mode, mode2);
|
||||
const dailyLeaderboard = new DailyLeaderboard(
|
||||
language,
|
||||
mode,
|
||||
mode2,
|
||||
customTimestamp
|
||||
);
|
||||
DAILY_LEADERBOARDS.set(key, dailyLeaderboard);
|
||||
}
|
||||
|
||||
|
|
|
@ -81,9 +81,11 @@ export function padNumbers(
|
|||
);
|
||||
}
|
||||
|
||||
export const MILLISECONDS_IN_DAY = 86400000;
|
||||
|
||||
export function getCurrentDayTimestamp(): number {
|
||||
const currentTime = Date.now();
|
||||
return currentTime - (currentTime % 86400000);
|
||||
return currentTime - (currentTime % MILLISECONDS_IN_DAY);
|
||||
}
|
||||
|
||||
export function matchesAPattern(text: string, pattern: string): boolean {
|
||||
|
@ -91,8 +93,6 @@ export function matchesAPattern(text: string, pattern: string): boolean {
|
|||
return regex.test(text);
|
||||
}
|
||||
|
||||
export const MILLISECONDS_IN_DAY = 86400000;
|
||||
|
||||
export function kogascore(wpm: number, acc: number, timestamp: number): number {
|
||||
const normalizedWpm = Math.floor(wpm * 100);
|
||||
const normalizedAcc = Math.floor(acc * 100);
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
min-width: 100%;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
grid-template-columns: max-content auto 20rem;
|
||||
grid-template-areas:
|
||||
"title buttons"
|
||||
"subtitle buttons";
|
||||
"title yesterday buttons"
|
||||
"subtitle subtitle buttons";
|
||||
|
||||
.buttons {
|
||||
grid-area: buttons;
|
||||
|
@ -38,6 +38,11 @@
|
|||
grid-area: title;
|
||||
}
|
||||
|
||||
.showYesterdayButton {
|
||||
grid-area: "yesterday";
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
color: var(--sub-color);
|
||||
grid-area: subtitle;
|
||||
|
@ -102,7 +107,7 @@
|
|||
|
||||
.leftTableWrapper,
|
||||
.rightTableWrapper {
|
||||
height: calc(100vh - 12rem);
|
||||
height: calc(100vh - 14rem);
|
||||
@extend .ffscroll;
|
||||
overflow-y: scroll;
|
||||
overflow-x: auto;
|
||||
|
@ -208,9 +213,9 @@
|
|||
.buttons {
|
||||
.buttonGroup {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-flow: row;
|
||||
gap: 0.5rem;
|
||||
grid-auto-columns: 1fr;
|
||||
gap: 1rem;
|
||||
grid-area: buttons;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
font-size: 1rem;
|
||||
}
|
||||
.leaderboardsTop {
|
||||
grid-template-columns: max-content;
|
||||
.buttonGroup {
|
||||
grid-auto-flow: row;
|
||||
gap: 0.5rem;
|
||||
|
@ -211,6 +212,17 @@
|
|||
.leaderboardsTop {
|
||||
flex-direction: column;
|
||||
align-items: baseline;
|
||||
grid-template-areas:
|
||||
"title title"
|
||||
"subtitle subtitle"
|
||||
"yesterday yesterday"
|
||||
"buttons buttons";
|
||||
.buttonGroup {
|
||||
grid-auto-flow: column;
|
||||
}
|
||||
.showYesterdayButton {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
.tables {
|
||||
grid-template-columns: unset;
|
||||
|
@ -225,7 +237,7 @@
|
|||
}
|
||||
.tables .rightTableWrapper,
|
||||
.tables .leftTableWrapper {
|
||||
height: calc(50vh - 8rem);
|
||||
height: calc(50vh - 10rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -551,7 +563,7 @@
|
|||
width: 85vw;
|
||||
.tables .rightTableWrapper,
|
||||
.tables .leftTableWrapper {
|
||||
height: calc(50vh - 7rem);
|
||||
height: calc(50vh - 10rem);
|
||||
}
|
||||
.tables {
|
||||
grid-template-columns: unset;
|
||||
|
|
|
@ -5,6 +5,7 @@ interface LeaderboardQuery {
|
|||
mode: MonkeyTypes.Mode;
|
||||
mode2: string | number;
|
||||
isDaily?: boolean;
|
||||
daysBefore?: number;
|
||||
}
|
||||
|
||||
interface LeadeboardQueryWithPagination extends LeaderboardQuery {
|
||||
|
@ -18,7 +19,16 @@ export default class Leaderboards {
|
|||
}
|
||||
|
||||
async get(query: LeadeboardQueryWithPagination): Ape.EndpointData {
|
||||
const { language, mode, mode2, isDaily, skip = 0, limit = 50 } = query;
|
||||
const {
|
||||
language,
|
||||
mode,
|
||||
mode2,
|
||||
isDaily,
|
||||
skip = 0,
|
||||
limit = 50,
|
||||
daysBefore,
|
||||
} = query;
|
||||
const includeDaysBefore = isDaily && daysBefore;
|
||||
|
||||
const searchQuery = {
|
||||
language,
|
||||
|
@ -26,6 +36,7 @@ export default class Leaderboards {
|
|||
mode2,
|
||||
skip: Math.max(skip, 0),
|
||||
limit: Math.max(Math.min(limit, 50), 0),
|
||||
...(includeDaysBefore && { daysBefore }),
|
||||
};
|
||||
|
||||
const endpointPath = `${BASE_PATH}/${isDaily ? "daily" : ""}`;
|
||||
|
@ -34,12 +45,14 @@ export default class Leaderboards {
|
|||
}
|
||||
|
||||
async getRank(query: LeaderboardQuery): Ape.EndpointData {
|
||||
const { language, mode, mode2, isDaily } = query;
|
||||
const { language, mode, mode2, isDaily, daysBefore } = query;
|
||||
const includeDaysBefore = isDaily && daysBefore;
|
||||
|
||||
const searchQuery = {
|
||||
language,
|
||||
mode,
|
||||
mode2,
|
||||
...(includeDaysBefore && { daysBefore }),
|
||||
};
|
||||
|
||||
const endpointPath = `${BASE_PATH}${isDaily ? "/daily" : ""}/rank`;
|
||||
|
|
|
@ -339,6 +339,7 @@ export function hide(): void {
|
|||
clearFoot(60);
|
||||
reset();
|
||||
stopTimer();
|
||||
$("leaderboardsWrapper .showYesterdayButton").removeClass("active");
|
||||
$("#leaderboardsWrapper").addClass("hidden");
|
||||
}
|
||||
);
|
||||
|
@ -352,18 +353,35 @@ function updateTitle(): void {
|
|||
el.text(`${timeRangeString} English Leaderboards`);
|
||||
}
|
||||
|
||||
function updateYesterdayButton(): void {
|
||||
$("#leaderboardsWrapper .showYesterdayButton").addClass("hidden");
|
||||
if (currentTimeRange === "daily") {
|
||||
$("#leaderboardsWrapper .showYesterdayButton").removeClass("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
async function update(): Promise<void> {
|
||||
showLoader(15);
|
||||
showLoader(60);
|
||||
|
||||
const timeModes = ["15", "60"];
|
||||
|
||||
let daysBefore = 0;
|
||||
|
||||
if (
|
||||
currentTimeRange === "daily" &&
|
||||
$("#leaderboardsWrapper .showYesterdayButton").hasClass("active")
|
||||
) {
|
||||
daysBefore = 1;
|
||||
}
|
||||
|
||||
const leaderboardRequests = timeModes.map((mode2) => {
|
||||
return Ape.leaderboards.get({
|
||||
language: "english",
|
||||
mode: "time",
|
||||
mode2,
|
||||
isDaily: currentTimeRange === "daily",
|
||||
daysBefore,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -375,6 +393,7 @@ async function update(): Promise<void> {
|
|||
mode: "time",
|
||||
mode2,
|
||||
isDaily: currentTimeRange === "daily",
|
||||
daysBefore,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
@ -415,6 +434,7 @@ async function update(): Promise<void> {
|
|||
$("#leaderboardsWrapper .rightTableWrapper").removeClass("invisible");
|
||||
|
||||
updateTitle();
|
||||
updateYesterdayButton();
|
||||
$("#leaderboardsWrapper .buttons .button").removeClass("active");
|
||||
$(
|
||||
`#leaderboardsWrapper .buttonGroup.timeRange .button.` + currentTimeRange
|
||||
|
@ -674,6 +694,12 @@ $(
|
|||
"#leaderboardsWrapper #leaderboards .leaderboardsTop .buttonGroup.timeRange .daily"
|
||||
).on("click", () => {
|
||||
currentTimeRange = "daily";
|
||||
$("#leaderboardsWrapper .showYesterdayButton").removeClass("active");
|
||||
update();
|
||||
});
|
||||
|
||||
$("#leaderboardsWrapper .showYesterdayButton").on("click", () => {
|
||||
$("#leaderboardsWrapper .showYesterdayButton").toggleClass("active");
|
||||
update();
|
||||
});
|
||||
|
||||
|
|
|
@ -676,6 +676,10 @@
|
|||
<div class="leaderboardsTop">
|
||||
<div class="mainTitle">All-Time English Leaderboards</div>
|
||||
<div class="subTitle">Next update in: --:--</div>
|
||||
<div class="text-button showYesterdayButton hidden">
|
||||
<i class="fas fa-calendar-day"></i>
|
||||
Show Yesterday
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div class="buttonGroup timeRange">
|
||||
<div class="button allTime">all-time</div>
|
||||
|
|
Loading…
Reference in a new issue