mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-12-25 01:21:44 +08:00
Reworked route controller (#3220) miodec
* rewrote route controller * showing loading by default * links which are not external need data line attribute * need to rewrite this * page controller takes a page as parameter removed page type * default export * going through the route controller instead of changing page directly * resolving all code paths * using navigate * added 404, leaderboards route * changed leaderboards button to a link * removed click handler * added page about route * removed default export, added settings page route * removing pointer events from everything inside links * navigating to account when on login page * fixed console logs, using async * added login and account pages * moved code to their own functions * allowing async functions * defaulting content visible * async * fixed 404 not navigating correctly * setting public path to root * fixed paths * using uid passed in through url params * added 404 page, profile routes * removed comment * moved discord link flow to url handler * allowing html * not resetting state * removed function * handling logo click * removed comments * reomoved comments * removed comments * removed comments * using new router * basic 404 page * buttons whicha are links have no underline * correctly handling the take me back button * updated button * removed comments * fixed account page profile link button * updated 404 * removed comments * removed comments * removed comments
This commit is contained in:
parent
7782b11b12
commit
6499cedc28
39 changed files with 1029 additions and 1009 deletions
26
frontend/src/styles/404.scss
Normal file
26
frontend/src/styles/404.scss
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
.page404 {
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
.content {
|
||||
justify-items: center;
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
width: min-content;
|
||||
text-align: center;
|
||||
.image {
|
||||
width: 300px;
|
||||
background-image: url("./../images/monkeymeme.png");
|
||||
aspect-ratio: 1210/800;
|
||||
background-size: contain;
|
||||
}
|
||||
.big {
|
||||
font-size: 10rem;
|
||||
line-height: 10rem;
|
||||
color: var(--sub-color);
|
||||
}
|
||||
.button {
|
||||
padding: 1rem 2rem;
|
||||
width: max-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,14 @@ a {
|
|||
}
|
||||
}
|
||||
|
||||
a[data-link] * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
a.button {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import "about", "account", "animations", "banners", "caret", "commandline",
|
||||
"core", "footer", "inputs", "keymap", "leaderboards", "login", "monkey", "nav",
|
||||
"notifications", "popups", "profile", "scroll", "settings", "test",
|
||||
"z_media-queries";
|
||||
@import "404", "about", "account", "animations", "banners", "caret",
|
||||
"commandline", "core", "footer", "inputs", "keymap", "leaderboards", "login",
|
||||
"monkey", "nav", "notifications", "popups", "profile", "scroll", "settings",
|
||||
"test", "z_media-queries";
|
||||
|
|
|
|||
|
|
@ -833,6 +833,18 @@
|
|||
.pageLogin .side input {
|
||||
width: 90vw;
|
||||
}
|
||||
.page404 {
|
||||
.content {
|
||||
width: 100%;
|
||||
.image {
|
||||
width: 100%;
|
||||
}
|
||||
.big {
|
||||
font-size: 7rem;
|
||||
line-height: 7rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,11 @@ import Ape from "../ape";
|
|||
import * as Notifications from "../elements/notifications";
|
||||
import Config, * as UpdateConfig from "../config";
|
||||
import * as AccountButton from "../elements/account-button";
|
||||
import * as VerificationController from "./verification-controller";
|
||||
import * as Misc from "../utils/misc";
|
||||
import * as Settings from "../pages/settings";
|
||||
import * as AllTimeStats from "../account/all-time-stats";
|
||||
import * as DB from "../db";
|
||||
import * as TestLogic from "../test/test-logic";
|
||||
import * as PageController from "./page-controller";
|
||||
import * as PSA from "../elements/psa";
|
||||
import * as Focus from "../test/focus";
|
||||
import * as Loader from "../elements/loader";
|
||||
|
|
@ -49,6 +47,7 @@ import {
|
|||
hideFavoriteQuoteLength,
|
||||
showFavoriteQuoteLength,
|
||||
} from "../test/test-config";
|
||||
import { navigate } from "./route-controller";
|
||||
|
||||
export const gmailProvider = new GoogleAuthProvider();
|
||||
let canCall = true;
|
||||
|
|
@ -72,7 +71,7 @@ export function sendVerificationEmail(): void {
|
|||
export async function getDataAndInit(): Promise<boolean> {
|
||||
try {
|
||||
console.log("getting account data");
|
||||
if (ActivePage.get() === "loading") {
|
||||
if (window.location.pathname !== "/account") {
|
||||
LoadingPage.updateBar(90);
|
||||
} else {
|
||||
LoadingPage.updateBar(45);
|
||||
|
|
@ -210,14 +209,16 @@ export async function getDataAndInit(): Promise<boolean> {
|
|||
TagController.loadActiveFromLocalStorage();
|
||||
ResultTagsPopup.updateButtons();
|
||||
Settings.showAccountSection();
|
||||
if (ActivePage.get() === "account") {
|
||||
Account.update();
|
||||
if (window.location.pathname === "/account") {
|
||||
await Account.downloadResults();
|
||||
} else {
|
||||
Focus.set(false);
|
||||
}
|
||||
await PageController.change(undefined, true);
|
||||
PageTransition.set(false);
|
||||
console.log("account loading finished");
|
||||
if (window.location.pathname === "/login") {
|
||||
navigate("/account");
|
||||
} else {
|
||||
navigate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -247,10 +248,6 @@ export async function loadUser(user: UserType): Promise<void> {
|
|||
|
||||
// showFavouriteThemesAtTheTop();
|
||||
|
||||
if (VerificationController.data !== null) {
|
||||
VerificationController.verify();
|
||||
}
|
||||
|
||||
if (TestLogic.notSignedInLastResult !== null) {
|
||||
TestLogic.setNotSignedInUid(user.uid);
|
||||
|
||||
|
|
@ -271,6 +268,7 @@ export async function loadUser(user: UserType): Promise<void> {
|
|||
const authListener = Auth.onAuthStateChanged(async function (user) {
|
||||
// await UpdateConfig.loadPromise;
|
||||
const search = window.location.search;
|
||||
const hash = window.location.hash;
|
||||
console.log(`auth state changed, user ${user ? true : false}`);
|
||||
if (user) {
|
||||
await loadUser(user);
|
||||
|
|
@ -281,7 +279,7 @@ const authListener = Auth.onAuthStateChanged(async function (user) {
|
|||
PageTransition.set(false);
|
||||
}
|
||||
if (!user) {
|
||||
PageController.change();
|
||||
navigate();
|
||||
setTimeout(() => {
|
||||
Focus.set(false);
|
||||
}, 125 / 2);
|
||||
|
|
@ -289,6 +287,7 @@ const authListener = Auth.onAuthStateChanged(async function (user) {
|
|||
|
||||
URLHandler.loadCustomThemeFromUrl(search);
|
||||
URLHandler.loadTestSettingsFromUrl(search);
|
||||
URLHandler.linkDiscord(hash);
|
||||
|
||||
if (/challenge_.+/g.test(window.location.pathname)) {
|
||||
Notifications.add(
|
||||
|
|
@ -470,7 +469,7 @@ export function signOut(): void {
|
|||
AllTimeStats.clear();
|
||||
Settings.hideAccountSection();
|
||||
AccountButton.update();
|
||||
PageController.change("login");
|
||||
navigate("/login");
|
||||
DB.setSnapshot(defaultSnap);
|
||||
$(".pageLogin .button").removeClass("disabled");
|
||||
$(".pageLogin input").prop("disabled", false);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import * as Caret from "../test/caret";
|
|||
import * as ManualRestart from "../test/manual-restart-tracker";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
import * as CustomText from "../test/custom-text";
|
||||
import * as PageController from "./page-controller";
|
||||
import * as Settings from "../pages/settings";
|
||||
import * as LayoutEmulator from "../test/layout-emulator";
|
||||
import * as PaceCaret from "../test/pace-caret";
|
||||
|
|
@ -27,6 +26,7 @@ import * as ActivePage from "../states/active-page";
|
|||
import * as TestActive from "../states/test-active";
|
||||
import * as TestInput from "../test/test-input";
|
||||
import * as TestWords from "../test/test-words";
|
||||
import { navigate } from "./route-controller";
|
||||
|
||||
let dontInsertSpace = false;
|
||||
let correctShiftUsed = true;
|
||||
|
|
@ -620,7 +620,7 @@ function handleTab(event: JQuery.KeyDownEvent, popupVisible: boolean): void {
|
|||
|
||||
// change page if not on test page
|
||||
if (ActivePage.get() !== "test") {
|
||||
PageController.change("test");
|
||||
navigate("/");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -702,7 +702,7 @@ $(document).keydown(async (event) => {
|
|||
|
||||
// change page if not on test page
|
||||
if (ActivePage.get() !== "test") {
|
||||
PageController.change("test");
|
||||
navigate("/");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,72 +2,33 @@ import * as Misc from "../utils/misc";
|
|||
import * as ActivePage from "../states/active-page";
|
||||
import * as Settings from "../pages/settings";
|
||||
import * as Account from "../pages/account";
|
||||
import * as ManualRestart from "../test/manual-restart-tracker";
|
||||
import * as PageTest from "../pages/test";
|
||||
import * as PageAbout from "../pages/about";
|
||||
import * as PageLogin from "../pages/login";
|
||||
import * as PageLoading from "../pages/loading";
|
||||
import * as PageProfile from "../pages/profile";
|
||||
import * as Page404 from "../pages/404";
|
||||
import * as PageTransition from "../states/page-transition";
|
||||
import { Auth } from "../firebase";
|
||||
import type Page from "../pages/page";
|
||||
|
||||
export async function change(
|
||||
page?: MonkeyTypes.Page | "",
|
||||
force = false
|
||||
): Promise<void> {
|
||||
page: Page,
|
||||
force = false,
|
||||
params?: { [key: string]: string }
|
||||
): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
if (PageTransition.get()) {
|
||||
console.log(`change page ${page} stopped`);
|
||||
return;
|
||||
console.log(`change page ${page.name} stopped`);
|
||||
return resolve(false);
|
||||
}
|
||||
console.log(`change page ${page}`);
|
||||
console.log(`change page ${page.name}`);
|
||||
|
||||
if (page === "") page = "test";
|
||||
if (page == undefined) {
|
||||
//use window loacation
|
||||
const pages: {
|
||||
[key: string]: MonkeyTypes.Page;
|
||||
} = {
|
||||
"/": "test",
|
||||
"/login": "login",
|
||||
"/settings": "settings",
|
||||
"/about": "about",
|
||||
"/account": "account",
|
||||
"/profile": "profile",
|
||||
};
|
||||
let path = pages[window.location.pathname as keyof typeof pages];
|
||||
if (!path) {
|
||||
path = "test";
|
||||
}
|
||||
page = path;
|
||||
|
||||
if (Auth.currentUser && page === "login") {
|
||||
page = "account";
|
||||
}
|
||||
|
||||
if (
|
||||
!Auth.currentUser &&
|
||||
window.location.search === "" &&
|
||||
page === "profile"
|
||||
) {
|
||||
page = "login";
|
||||
}
|
||||
if (!force && ActivePage.get() === page.name) {
|
||||
console.log(`page ${page.name} already active`);
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
if (
|
||||
Auth.currentUser &&
|
||||
window.location.pathname === "/profile" &&
|
||||
window.location.search === ""
|
||||
) {
|
||||
page = "account";
|
||||
}
|
||||
|
||||
if (!force && ActivePage.get() === page) {
|
||||
console.log(`page ${page} already active`);
|
||||
return;
|
||||
}
|
||||
|
||||
const pages = {
|
||||
const pages: Record<string, Page> = {
|
||||
loading: PageLoading.page,
|
||||
test: PageTest.page,
|
||||
settings: Settings.page,
|
||||
|
|
@ -75,52 +36,30 @@ export async function change(
|
|||
account: Account.page,
|
||||
login: PageLogin.page,
|
||||
profile: PageProfile.page,
|
||||
404: Page404.page,
|
||||
};
|
||||
|
||||
const previousPage = pages[ActivePage.get() as MonkeyTypes.Page];
|
||||
const nextPage = pages[page];
|
||||
|
||||
const historyUrl =
|
||||
nextPage.pathname +
|
||||
(nextPage.pathname === "/profile" ? window.location.search : "");
|
||||
const previousPage = pages[ActivePage.get()];
|
||||
const nextPage = page;
|
||||
|
||||
previousPage?.beforeHide();
|
||||
PageTransition.set(true);
|
||||
ActivePage.set(undefined);
|
||||
$(".page").removeClass("active");
|
||||
Misc.swapElements(
|
||||
previousPage.element,
|
||||
nextPage.element,
|
||||
250,
|
||||
() => {
|
||||
async () => {
|
||||
PageTransition.set(false);
|
||||
ActivePage.set(nextPage.name);
|
||||
previousPage?.afterHide();
|
||||
nextPage.element.addClass("active");
|
||||
resolve();
|
||||
history.pushState(nextPage.pathname, "", historyUrl);
|
||||
resolve(true);
|
||||
nextPage?.afterShow();
|
||||
},
|
||||
async () => {
|
||||
await nextPage?.beforeShow();
|
||||
await nextPage?.beforeShow(params);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("click", "#top .logo", () => {
|
||||
change("test");
|
||||
});
|
||||
|
||||
$(document).on("click", "#top #menu .text-button", (e) => {
|
||||
if (!$(e.currentTarget).hasClass("leaderboards")) {
|
||||
const href = $(e.currentTarget).attr("href") as string;
|
||||
ManualRestart.set();
|
||||
change(href.replace("/", "") as MonkeyTypes.Page);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$(".pageTest .loginTip .link").on("click", async () => {
|
||||
change("login");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,56 +1,142 @@
|
|||
// import * as Funbox from "../test/funbox";
|
||||
import * as PageController from "./page-controller";
|
||||
// import Config from "../config";
|
||||
import * as PageTest from "../pages/test";
|
||||
import * as PageAbout from "../pages/about";
|
||||
import * as PageSettings from "../pages/settings";
|
||||
import * as PageAccount from "../pages/account";
|
||||
import * as PageLogin from "../pages/login";
|
||||
import * as Page404 from "../pages/404";
|
||||
import * as PageProfile from "../pages/profile";
|
||||
import * as Leaderboards from "../elements/leaderboards";
|
||||
import * as ActivePage from "../states/active-page";
|
||||
import { Auth } from "../firebase";
|
||||
|
||||
const mappedRoutes = {
|
||||
"/": "pageLoading",
|
||||
"/login": "pageLoading",
|
||||
"/settings": "pageLoading",
|
||||
"/about": "pageLoading",
|
||||
"/account": "pageAccount",
|
||||
"/verify": "pageLoading",
|
||||
"/profile": "pageLoading",
|
||||
};
|
||||
//source: https://www.youtube.com/watch?v=OstALBk-jTc
|
||||
// https://www.youtube.com/watch?v=OstALBk-jTc
|
||||
|
||||
export function handleInitialPageClasses(pathname: string): void {
|
||||
if (!mappedRoutes[pathname as keyof typeof mappedRoutes]) {
|
||||
pathname = "/";
|
||||
}
|
||||
const el = $(".page." + mappedRoutes[pathname as keyof typeof mappedRoutes]);
|
||||
$(el).removeClass("hidden");
|
||||
$(el).addClass("active");
|
||||
let pageName = "loading";
|
||||
if (pathname === "/account") pageName = "account";
|
||||
ActivePage.set(pageName as MonkeyTypes.Page);
|
||||
function pathToRegex(path: string): RegExp {
|
||||
return new RegExp(
|
||||
"^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$"
|
||||
);
|
||||
}
|
||||
|
||||
// honestly im not sure what this does
|
||||
// (function (history): void {
|
||||
// const pushState = history.pushState;
|
||||
// history.pushState = function (state): void {
|
||||
// if (Config.funbox === "memory" && state !== "/") {
|
||||
// Funbox.resetMemoryTimer();
|
||||
// }
|
||||
// // @ts-ignore
|
||||
// return pushState.apply(history, arguments);
|
||||
// };
|
||||
// })(window.history);
|
||||
function getParams(match: { route: Route; result: RegExpMatchArray }): {
|
||||
[key: string]: string;
|
||||
} {
|
||||
const values = match.result.slice(1);
|
||||
const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(
|
||||
(result) => result[1]
|
||||
);
|
||||
|
||||
$(window).on("popstate", (e) => {
|
||||
const state = (e.originalEvent as unknown as PopStateEvent).state;
|
||||
if (state == "" || state == "/") {
|
||||
// show test
|
||||
PageController.change("test");
|
||||
} else if (state == "about") {
|
||||
// show about
|
||||
PageController.change("about");
|
||||
} else if (state === "account" || state === "login") {
|
||||
if (Auth.currentUser) {
|
||||
PageController.change("account");
|
||||
} else {
|
||||
PageController.change("login");
|
||||
}
|
||||
return Object.fromEntries(keys.map((key, index) => [key, values[index]]));
|
||||
}
|
||||
|
||||
interface Route {
|
||||
path: string;
|
||||
load: (params: { [key: string]: string }) => void;
|
||||
}
|
||||
|
||||
const routes: Route[] = [
|
||||
{
|
||||
path: "/",
|
||||
load: (): void => {
|
||||
PageController.change(PageTest.page);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/verify",
|
||||
load: (): void => {
|
||||
PageController.change(PageTest.page);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/leaderboards",
|
||||
load: (): void => {
|
||||
if (ActivePage.get() === "loading") {
|
||||
PageController.change(PageTest.page);
|
||||
}
|
||||
Leaderboards.show();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
load: (): void => {
|
||||
PageController.change(PageAbout.page);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/settings",
|
||||
load: (): void => {
|
||||
PageController.change(PageSettings.page);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
load: (): void => {
|
||||
PageController.change(PageLogin.page);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/account",
|
||||
load: (): void => {
|
||||
PageController.change(PageAccount.page);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/profile",
|
||||
load: (): void => {
|
||||
if (Auth.currentUser) {
|
||||
navigate("/account");
|
||||
} else {
|
||||
navigate("/");
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/profile/:uid",
|
||||
load: (params): void => {
|
||||
PageController.change(PageProfile.page, undefined, params);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function navigate(url = window.location.pathname): void {
|
||||
history.pushState(null, "", url);
|
||||
router();
|
||||
}
|
||||
|
||||
async function router(): Promise<void> {
|
||||
const matches = routes.map((r) => {
|
||||
return {
|
||||
route: r,
|
||||
result: location.pathname.match(pathToRegex(r.path)),
|
||||
};
|
||||
});
|
||||
|
||||
const match = matches.find((m) => m.result !== null) as {
|
||||
route: Route;
|
||||
result: RegExpMatchArray;
|
||||
};
|
||||
|
||||
if (!match) {
|
||||
PageController.change(Page404.page);
|
||||
return;
|
||||
}
|
||||
|
||||
match.route.load(getParams(match));
|
||||
}
|
||||
|
||||
window.addEventListener("popstate", router);
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.body.addEventListener("click", (e) => {
|
||||
const target = e?.target as HTMLLinkElement;
|
||||
if (target.matches("[data-link]") && target?.href) {
|
||||
e.preventDefault();
|
||||
navigate(target.href);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#top .logo").click(() => {
|
||||
navigate("/");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -80,9 +80,9 @@ const loadStyle = async function (name: string): Promise<void> {
|
|||
resolve();
|
||||
};
|
||||
if (name === "custom") {
|
||||
link.href = `themes/serika_dark.css`;
|
||||
link.href = `/./themes/serika_dark.css`;
|
||||
} else {
|
||||
link.href = `themes/${name}.css`;
|
||||
link.href = `/./themes/${name}.css`;
|
||||
}
|
||||
|
||||
const headScript = document.querySelector("#currentTheme") as Element;
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
import Ape from "../ape";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
import * as Settings from "../pages/settings";
|
||||
import * as DB from "../db";
|
||||
import * as Loader from "../elements/loader";
|
||||
import * as AccountButton from "../elements/account-button";
|
||||
|
||||
interface Data {
|
||||
accessToken: string;
|
||||
tokenType: string;
|
||||
}
|
||||
|
||||
export let data: Data | null = null;
|
||||
|
||||
export function set(val: Data): void {
|
||||
data = val;
|
||||
}
|
||||
|
||||
export async function verify(): Promise<void> {
|
||||
if (data === null) return;
|
||||
Loader.show();
|
||||
|
||||
const { accessToken, tokenType } = data;
|
||||
|
||||
const response = await Ape.users.linkDiscord(tokenType, accessToken);
|
||||
Loader.hide();
|
||||
|
||||
if (response.status !== 200) {
|
||||
return Notifications.add("Failed to link Discord: " + response.message, -1);
|
||||
}
|
||||
|
||||
Notifications.add(response.message, 1);
|
||||
|
||||
const snapshot = DB.getSnapshot();
|
||||
|
||||
const { discordId, discordAvatar } = response.data;
|
||||
if (discordId) {
|
||||
snapshot.discordId = discordId;
|
||||
} else {
|
||||
snapshot.discordAvatar = discordAvatar;
|
||||
}
|
||||
|
||||
DB.setSnapshot(snapshot);
|
||||
|
||||
AccountButton.update(discordId, discordAvatar);
|
||||
|
||||
Settings.updateDiscordSection();
|
||||
}
|
||||
|
|
@ -21,9 +21,9 @@ import * as ModesNotice from "../elements/modes-notice";
|
|||
import * as ConfigEvent from "../observables/config-event";
|
||||
import * as ShareTestSettingsPopup from "../popups/share-test-settings-popup";
|
||||
import { Auth } from "../firebase";
|
||||
import * as PageController from "../controllers/page-controller";
|
||||
import * as EditPresetPopup from "../popups/edit-preset-popup";
|
||||
import * as EditTagPopup from "../popups/edit-tags-popup";
|
||||
import { navigate } from "../controllers/route-controller";
|
||||
|
||||
export let current: MonkeyTypes.CommandsGroup[] = [];
|
||||
|
||||
|
|
@ -2516,7 +2516,7 @@ Misc.getChallengeList().then((challenges) => {
|
|||
noIcon: true,
|
||||
display: challenge.display,
|
||||
exec: (): void => {
|
||||
PageController.change("test");
|
||||
navigate("/");
|
||||
ChallengeController.setup(challenge.name);
|
||||
TestLogic.restart(false, true);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import * as Notifications from "./notifications";
|
|||
import format from "date-fns/format";
|
||||
import { Auth } from "../firebase";
|
||||
import differenceInSeconds from "date-fns/differenceInSeconds";
|
||||
import { change } from "../controllers/page-controller";
|
||||
import { getHTMLById as getBadgeHTMLbyId } from "../controllers/badge-controller";
|
||||
import { navigate } from "../controllers/route-controller";
|
||||
|
||||
let currentTimeRange: "allTime" | "daily" = "allTime";
|
||||
let currentLanguage = "english";
|
||||
|
|
@ -353,8 +353,7 @@ async function fillTable(lb: LbKey, prepend?: number): Promise<void> {
|
|||
$(".entryName").on("click", (e) => {
|
||||
const uid = $(e.target).attr("uid");
|
||||
if (uid) {
|
||||
window.history.replaceState(null, "", "/profile?uid=" + uid);
|
||||
change("profile", true);
|
||||
navigate(`/profile/${uid}`);
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
|
@ -793,13 +792,6 @@ $("#leaderboardsWrapper .showYesterdayButton").on("click", () => {
|
|||
update();
|
||||
});
|
||||
|
||||
$(document).on("click", "#top #menu .text-button", (e) => {
|
||||
if ($(e.currentTarget).hasClass("leaderboards")) {
|
||||
show();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).on("keydown", (event) => {
|
||||
if (event.key === "Escape" && !$("#leaderboardsWrapper").hasClass("hidden")) {
|
||||
hide();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import * as Notifications from "./notifications";
|
||||
// import * as VersionPopup from "./version-popup";
|
||||
|
||||
function setMemory(v: string): void {
|
||||
window.localStorage.setItem("lastSeenVersion", v);
|
||||
|
|
|
|||
19
frontend/src/ts/pages/404.ts
Normal file
19
frontend/src/ts/pages/404.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import Page from "./page";
|
||||
|
||||
export const page = new Page(
|
||||
"404",
|
||||
$(".page.page404"),
|
||||
"/404",
|
||||
async () => {
|
||||
//
|
||||
},
|
||||
async () => {
|
||||
//
|
||||
},
|
||||
async () => {
|
||||
//
|
||||
},
|
||||
async () => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import * as Misc from "../utils/misc";
|
||||
import Page from "./page";
|
||||
|
||||
export function reset(): void {
|
||||
function reset(): void {
|
||||
$(".pageAbout .contributors").empty();
|
||||
$(".pageAbout .supporters").empty();
|
||||
}
|
||||
|
||||
export async function fill(): Promise<void> {
|
||||
async function fill(): Promise<void> {
|
||||
const supporters = await Misc.getSupportersList();
|
||||
const contributors = await Misc.getContributorsList();
|
||||
supporters.forEach((supporter) => {
|
||||
|
|
@ -25,16 +25,16 @@ export const page = new Page(
|
|||
"about",
|
||||
$(".page.pageAbout"),
|
||||
"/about",
|
||||
() => {
|
||||
async () => {
|
||||
//
|
||||
},
|
||||
async () => {
|
||||
reset();
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
fill();
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -23,7 +23,7 @@ export function showBar(): Promise<void> {
|
|||
$(".pageLoading .icon"),
|
||||
$(".pageLoading .barWrapper"),
|
||||
125,
|
||||
() => {
|
||||
async () => {
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
|
|
@ -31,7 +31,7 @@ export function showBar(): Promise<void> {
|
|||
$(".pageAccount .icon"),
|
||||
$(".pageAccount .barWrapper"),
|
||||
125,
|
||||
() => {
|
||||
async () => {
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
|
|
@ -48,10 +48,10 @@ export const page = new Page(
|
|||
async () => {
|
||||
//
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
//
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -297,16 +297,16 @@ export const page = new Page(
|
|||
"login",
|
||||
$(".page.pageLogin"),
|
||||
"/login",
|
||||
() => {
|
||||
async () => {
|
||||
//
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
//
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
//
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
export default class Page {
|
||||
public name: MonkeyTypes.Page;
|
||||
public name: string;
|
||||
public element: JQuery;
|
||||
public pathname: string;
|
||||
public beforeHide: () => void;
|
||||
public afterHide: () => void;
|
||||
public beforeShow: () => void;
|
||||
public afterShow: () => void;
|
||||
public beforeHide: () => Promise<void>;
|
||||
public afterHide: () => Promise<void>;
|
||||
public beforeShow: (params?: { [key: string]: string }) => Promise<void>;
|
||||
public afterShow: () => Promise<void>;
|
||||
constructor(
|
||||
name: MonkeyTypes.Page,
|
||||
name: string,
|
||||
element: JQuery,
|
||||
pathname: string,
|
||||
beforeHide: () => void,
|
||||
afterHide: () => void,
|
||||
beforeShow: () => void,
|
||||
afterShow: () => void
|
||||
beforeHide: () => Promise<void>,
|
||||
afterHide: () => Promise<void>,
|
||||
beforeShow: (params?: { [key: string]: string }) => Promise<void>,
|
||||
afterShow: () => Promise<void>
|
||||
) {
|
||||
this.name = name;
|
||||
this.element = element;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import Ape from "../ape";
|
||||
import Page from "./page";
|
||||
import * as Misc from "../utils/misc";
|
||||
import * as Profile from "../elements/profile";
|
||||
import * as PbTables from "../account/pb-tables";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
|
|
@ -119,9 +118,7 @@ function reset(): void {
|
|||
</div>`);
|
||||
}
|
||||
|
||||
async function update(): Promise<void> {
|
||||
const userId = Misc.findGetParameter("uid");
|
||||
|
||||
async function update(userId: string): Promise<void> {
|
||||
const response = await Ape.users.getProfile(userId ?? "");
|
||||
$(".page.pageProfile .preloader").addClass("hidden");
|
||||
|
||||
|
|
@ -138,17 +135,17 @@ export const page = new Page(
|
|||
"profile",
|
||||
$(".page.pageProfile"),
|
||||
"/profile",
|
||||
() => {
|
||||
async () => {
|
||||
//
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
reset();
|
||||
},
|
||||
() => {
|
||||
async (params) => {
|
||||
reset();
|
||||
update();
|
||||
update(params?.["uid"] ?? "");
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1052,7 +1052,7 @@ export const page = new Page(
|
|||
"settings",
|
||||
$(".page.pageSettings"),
|
||||
"/settings",
|
||||
() => {
|
||||
async () => {
|
||||
//
|
||||
},
|
||||
async () => {
|
||||
|
|
@ -1062,7 +1062,7 @@ export const page = new Page(
|
|||
await fillSettingsPage();
|
||||
update();
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@ export const page = new Page(
|
|||
async () => {
|
||||
//
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
TestConfig.show();
|
||||
TestStats.resetIncomplete();
|
||||
ManualRestart.set();
|
||||
TestLogic.restart(undefined, undefined, undefined, undefined, true);
|
||||
Funbox.activate(Config.funbox);
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
TestUI.focusWords();
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
// import Config, * as UpdateConfig from "./config";
|
||||
// import * as Notifications from "./notifications";
|
||||
// import * as ThemePicker from "./theme-picker";
|
||||
|
||||
export function show(value: string): void {
|
||||
if ($("#customThemeShareWrapper").hasClass("hidden")) {
|
||||
// let save = [];
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import Ape from "../ape";
|
||||
import * as Loader from "../elements/loader";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
// import Config from "../config";
|
||||
// import * as Misc from "../misc";
|
||||
|
||||
// let dropdownReady = false;
|
||||
// async function initDropdown(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import * as ManualRestart from "./test/manual-restart-tracker";
|
||||
import Config, * as UpdateConfig from "./config";
|
||||
import * as Misc from "./utils/misc";
|
||||
import * as VerificationController from "./controllers/verification-controller";
|
||||
import * as RouteController from "./controllers/route-controller";
|
||||
import * as PageController from "./controllers/page-controller";
|
||||
import * as MonkeyPower from "./elements/monkey-power";
|
||||
import * as NewVersionNotification from "./elements/version-check";
|
||||
import * as Notifications from "./elements/notifications";
|
||||
|
|
@ -41,11 +38,7 @@ $("#nocss .requestedStylesheets").html(
|
|||
);
|
||||
|
||||
Focus.set(true, true);
|
||||
RouteController.handleInitialPageClasses(window.location.pathname);
|
||||
$(document).ready(() => {
|
||||
if (window.location.pathname === "/") {
|
||||
// $("#top .config").removeClass("hidden");
|
||||
}
|
||||
CookiePopup.check();
|
||||
$("body").css("transition", "all .25s, transform .05s");
|
||||
if (Config.quickRestart === "tab" || Config.quickRestart === "esc") {
|
||||
|
|
@ -63,48 +56,11 @@ $(document).ready(() => {
|
|||
true
|
||||
);
|
||||
}
|
||||
// if (!window.localStorage.getItem("dasbannerclosed")) {
|
||||
// Notifications.addBanner(
|
||||
// `Looking to buy a new keyboard? Check out <a target="_blank" href="https://www.monkeytype.com/das">DasKeyboard</a>. `,
|
||||
// 1,
|
||||
// "images/dasbanner.png",
|
||||
// false,
|
||||
// () => {
|
||||
// window.localStorage.setItem("dasbannerclosed", "true");
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
$("#centerContent")
|
||||
.css("opacity", "0")
|
||||
.removeClass("hidden")
|
||||
.stop(true, true)
|
||||
.animate({ opacity: 1 }, 250, () => {
|
||||
if (window.location.pathname === "/verify") {
|
||||
const fragment = new URLSearchParams(window.location.hash.slice(1));
|
||||
if (fragment.has("access_token")) {
|
||||
const accessToken = fragment.get("access_token") as string;
|
||||
const tokenType = fragment.get("token_type") as string;
|
||||
VerificationController.set({
|
||||
accessToken: accessToken,
|
||||
tokenType: tokenType,
|
||||
});
|
||||
history.replaceState("/", "", "/");
|
||||
}
|
||||
const page = window.location.pathname.replace(
|
||||
"/",
|
||||
""
|
||||
) as MonkeyTypes.Page;
|
||||
PageController.change(page);
|
||||
} else if (window.location.pathname === "/account") {
|
||||
// history.replaceState("/", null, "/");
|
||||
} else if (/challenge_.+/g.test(window.location.pathname)) {
|
||||
//do nothing
|
||||
// }
|
||||
} else if (window.location.pathname !== "/") {
|
||||
// let page = window.location.pathname.replace("/", "");
|
||||
// PageController.change(page);
|
||||
}
|
||||
});
|
||||
// Settings.settingsFillPromise.then(Settings.update);
|
||||
.animate({ opacity: 1 }, 250);
|
||||
|
||||
MonkeyPower.init();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
let activePage: MonkeyTypes.Page | undefined = "loading";
|
||||
let activePage = "loading";
|
||||
|
||||
export function get(): MonkeyTypes.Page | undefined {
|
||||
export function get(): string {
|
||||
return activePage;
|
||||
}
|
||||
|
||||
export function set(active: MonkeyTypes.Page | undefined): void {
|
||||
export function set(active: string): void {
|
||||
activePage = active;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ export async function update(
|
|||
$("#typingTest"),
|
||||
$("#result"),
|
||||
250,
|
||||
() => {
|
||||
async () => {
|
||||
TestUI.setResultCalculating(false);
|
||||
$("#words").empty();
|
||||
ChartController.result.resize();
|
||||
|
|
@ -721,7 +721,7 @@ export async function update(
|
|||
window.scrollTo({ top: 0 });
|
||||
$("#testModesNotice").addClass("hidden");
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
$("#resultExtraButtons").removeClass("hidden").css("opacity", 0).animate(
|
||||
{
|
||||
opacity: 1,
|
||||
|
|
|
|||
11
frontend/src/ts/types/types.d.ts
vendored
11
frontend/src/ts/types/types.d.ts
vendored
|
|
@ -687,17 +687,6 @@ declare namespace MonkeyTypes {
|
|||
colorfulErrorExtra: string;
|
||||
}
|
||||
|
||||
type Page =
|
||||
| "loading"
|
||||
| "test"
|
||||
| "about"
|
||||
| "settings"
|
||||
| "account"
|
||||
| "login"
|
||||
| "profile";
|
||||
|
||||
// type ActivePage = `page${Page}` | undefined;
|
||||
|
||||
interface Layout {
|
||||
keymapShowTopRow: boolean;
|
||||
type: "iso" | "ansi" | "ortho" | "matrix";
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ interface Theme {
|
|||
let themesList: Theme[] = [];
|
||||
export async function getThemesList(): Promise<Theme[]> {
|
||||
if (themesList.length == 0) {
|
||||
return $.getJSON("themes/_list.json", function (data) {
|
||||
return $.getJSON("/./themes/_list.json", function (data) {
|
||||
const list = data.sort(function (a: Theme, b: Theme) {
|
||||
const nameA = a.name.toLowerCase();
|
||||
const nameB = b.name.toLowerCase();
|
||||
|
|
@ -121,7 +121,7 @@ export async function getSortedThemesList(): Promise<Theme[]> {
|
|||
let funboxList: MonkeyTypes.FunboxObject[] = [];
|
||||
export async function getFunboxList(): Promise<MonkeyTypes.FunboxObject[]> {
|
||||
if (funboxList.length === 0) {
|
||||
return $.getJSON("funbox/_list.json", function (data) {
|
||||
return $.getJSON("/./funbox/_list.json", function (data) {
|
||||
funboxList = data.sort(function (
|
||||
a: MonkeyTypes.FunboxObject,
|
||||
b: MonkeyTypes.FunboxObject
|
||||
|
|
@ -151,7 +151,7 @@ export async function getFunbox(
|
|||
let layoutsList: MonkeyTypes.Layouts = {};
|
||||
export async function getLayoutsList(): Promise<MonkeyTypes.Layouts> {
|
||||
if (Object.keys(layoutsList).length === 0) {
|
||||
return $.getJSON("layouts/_list.json", function (data) {
|
||||
return $.getJSON("/./layouts/_list.json", function (data) {
|
||||
layoutsList = data;
|
||||
return layoutsList;
|
||||
});
|
||||
|
|
@ -177,7 +177,7 @@ interface Font {
|
|||
let fontsList: Font[] = [];
|
||||
export async function getFontsList(): Promise<Font[]> {
|
||||
if (fontsList.length === 0) {
|
||||
return $.getJSON("fonts/_list.json", function (data) {
|
||||
return $.getJSON("/./fonts/_list.json", function (data) {
|
||||
fontsList = data.sort(function (a: Font, b: Font) {
|
||||
const nameA = a.name.toLowerCase();
|
||||
const nameB = b.name.toLowerCase();
|
||||
|
|
@ -219,7 +219,7 @@ export async function getContributorsList(): Promise<string[]> {
|
|||
let languageList: string[] = [];
|
||||
export async function getLanguageList(): Promise<string[]> {
|
||||
if (languageList.length === 0) {
|
||||
return $.getJSON("languages/_list.json", function (data) {
|
||||
return $.getJSON("/./languages/_list.json", function (data) {
|
||||
languageList = data;
|
||||
return languageList;
|
||||
});
|
||||
|
|
@ -288,7 +288,7 @@ export async function findCurrentGroup(
|
|||
let challengeList: MonkeyTypes.Challenge[] = [];
|
||||
export async function getChallengeList(): Promise<MonkeyTypes.Challenge[]> {
|
||||
if (challengeList.length === 0) {
|
||||
return $.getJSON("challenges/_list.json", function (data) {
|
||||
return $.getJSON("/./challenges/_list.json", function (data) {
|
||||
challengeList = data;
|
||||
return challengeList;
|
||||
});
|
||||
|
|
@ -927,25 +927,25 @@ export function convertRemToPixels(rem: number): number {
|
|||
return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
}
|
||||
|
||||
export function swapElements(
|
||||
export async function swapElements(
|
||||
el1: JQuery,
|
||||
el2: JQuery,
|
||||
totalDuration: number,
|
||||
callback = function (): void {
|
||||
return;
|
||||
callback = function (): Promise<void> {
|
||||
return Promise.resolve();
|
||||
},
|
||||
middleCallback = function (): void {
|
||||
return;
|
||||
middleCallback = function (): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
): boolean | undefined {
|
||||
): Promise<boolean | undefined> {
|
||||
if (
|
||||
(el1.hasClass("hidden") && !el2.hasClass("hidden")) ||
|
||||
(!el1.hasClass("hidden") && el2.hasClass("hidden"))
|
||||
) {
|
||||
//one of them is hidden and the other is visible
|
||||
if (el1.hasClass("hidden")) {
|
||||
middleCallback();
|
||||
callback();
|
||||
await middleCallback();
|
||||
await callback();
|
||||
return false;
|
||||
}
|
||||
$(el1)
|
||||
|
|
@ -956,8 +956,8 @@ export function swapElements(
|
|||
opacity: 0,
|
||||
},
|
||||
totalDuration / 2,
|
||||
() => {
|
||||
middleCallback();
|
||||
async () => {
|
||||
await middleCallback();
|
||||
$(el1).addClass("hidden");
|
||||
$(el2)
|
||||
.removeClass("hidden")
|
||||
|
|
@ -975,7 +975,7 @@ export function swapElements(
|
|||
);
|
||||
} else if (el1.hasClass("hidden") && el2.hasClass("hidden")) {
|
||||
//both are hidden, only fade in the second
|
||||
middleCallback();
|
||||
await middleCallback();
|
||||
$(el2)
|
||||
.removeClass("hidden")
|
||||
.css("opacity", 0)
|
||||
|
|
@ -984,13 +984,13 @@ export function swapElements(
|
|||
opacity: 1,
|
||||
},
|
||||
totalDuration,
|
||||
() => {
|
||||
callback();
|
||||
async () => {
|
||||
await callback();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
middleCallback();
|
||||
callback();
|
||||
await middleCallback();
|
||||
await callback();
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,52 @@ import { decompressFromURI } from "lz-ts";
|
|||
import * as QuoteSearchPopup from "../popups/quote-search-popup";
|
||||
import * as ManualRestart from "../test/manual-restart-tracker";
|
||||
import * as CustomText from "../test/custom-text";
|
||||
import Ape from "../ape";
|
||||
import * as Settings from "../pages/settings";
|
||||
import * as DB from "../db";
|
||||
import * as Loader from "../elements/loader";
|
||||
import * as AccountButton from "../elements/account-button";
|
||||
import { restart as restartTest } from "../test/test-logic";
|
||||
|
||||
export async function linkDiscord(hashOverride: string): Promise<void> {
|
||||
if (!hashOverride) return;
|
||||
const fragment = new URLSearchParams(hashOverride.slice(1));
|
||||
if (fragment.has("access_token")) {
|
||||
history.replaceState(null, "", "/");
|
||||
const accessToken = fragment.get("access_token") as string;
|
||||
const tokenType = fragment.get("token_type") as string;
|
||||
|
||||
Loader.show();
|
||||
|
||||
const response = await Ape.users.linkDiscord(tokenType, accessToken);
|
||||
Loader.hide();
|
||||
|
||||
if (response.status !== 200) {
|
||||
return Notifications.add(
|
||||
"Failed to link Discord: " + response.message,
|
||||
-1
|
||||
);
|
||||
}
|
||||
|
||||
Notifications.add(response.message, 1);
|
||||
|
||||
const snapshot = DB.getSnapshot();
|
||||
|
||||
const { discordId, discordAvatar } = response.data;
|
||||
if (discordId) {
|
||||
snapshot.discordId = discordId;
|
||||
} else {
|
||||
snapshot.discordAvatar = discordAvatar;
|
||||
}
|
||||
|
||||
DB.setSnapshot(snapshot);
|
||||
|
||||
AccountButton.update(discordId, discordAvatar);
|
||||
|
||||
Settings.updateDiscordSection();
|
||||
}
|
||||
}
|
||||
|
||||
export function loadCustomThemeFromUrl(getOverride?: string): void {
|
||||
const getValue = Misc.findGetParameter("customTheme", getOverride);
|
||||
if (getValue === null) return;
|
||||
|
|
@ -130,7 +174,11 @@ export function loadTestSettingsFromUrl(getOverride?: string): void {
|
|||
Notifications.add(
|
||||
"Settings applied from URL:<br><br>" + appliedString,
|
||||
1,
|
||||
10
|
||||
10,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Monkeytype</title>
|
||||
<link rel="stylesheet" href="css/select2.min.css" />
|
||||
<link rel="stylesheet" href="css/balloon.min.css" />
|
||||
<link rel="stylesheet" href="themes/serika_dark.css" id="currentTheme" />
|
||||
<link rel="stylesheet" href="/./css/select2.min.css" />
|
||||
<link rel="stylesheet" href="/./css/balloon.min.css" />
|
||||
<link rel="stylesheet" href="/./themes/serika_dark.css" id="currentTheme" />
|
||||
<link rel="stylesheet" href="" id="funBoxTheme" />
|
||||
<link id="favicon" rel="shortcut icon" href="/images/favicon/favicon.ico" />
|
||||
<link id="favicon" rel="shortcut icon" href="/./images/favicon/favicon.ico" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"
|
||||
onerror="this.onerror=null;this.href='css/fa.min.css';"
|
||||
/>
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<link rel="manifest" href="/./manifest.json" />
|
||||
<meta name="name" content="Monkeytype" />
|
||||
<meta name="image" content="https://monkeytype.com/images/mtsocial.png" />
|
||||
<meta
|
||||
|
|
|
|||
10
frontend/static/html/pages/404.html
Normal file
10
frontend/static/html/pages/404.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<div class="page page404 hidden">
|
||||
<div class="content">
|
||||
<div class="image"></div>
|
||||
<div>Ooops! Looks like you found a page that doesn't exist.</div>
|
||||
<a href="/" class="button" data-link>
|
||||
<i class="fas fa-home"></i>
|
||||
Go Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="scrollToTopButton">
|
||||
<i class="fas fa-angle-double-up"></i>
|
||||
</div>
|
||||
<div class="preloader">
|
||||
<div class="preloader hidden">
|
||||
<div class="icon">
|
||||
<i class="fas fa-fw fa-spin fa-circle-notch"></i>
|
||||
</div>
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
<div class="text"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content hidden">
|
||||
<div class="content">
|
||||
<div class="miniResultChartWrapper">
|
||||
<canvas id="miniResultChart"></canvas>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="page pageLoading hidden">
|
||||
<div class="page pageLoading">
|
||||
<div class="preloader">
|
||||
<div class="icon">
|
||||
<i class="fas fa-fw fa-spin fa-circle-notch"></i>
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@
|
|||
<canvas id="wpmChart"></canvas>
|
||||
</div>
|
||||
<div class="loginTip">
|
||||
<span class="link">Sign in</span>
|
||||
<a href="/login" data-link>Sign in</a>
|
||||
to save your result
|
||||
</div>
|
||||
<div class="bottom" style="grid-column: 1/3">
|
||||
|
|
|
|||
|
|
@ -50,25 +50,29 @@
|
|||
tabindex="2"
|
||||
href="/"
|
||||
onclick="this.blur();"
|
||||
data-link
|
||||
>
|
||||
<div class="icon">
|
||||
<i class="fas fa-fw fa-keyboard"></i>
|
||||
</div>
|
||||
</a>
|
||||
<div
|
||||
<a
|
||||
class="text-button leaderboards view-leaderboards"
|
||||
tabindex="2"
|
||||
onclick="this.blur();"
|
||||
href="/leaderboards"
|
||||
data-link
|
||||
>
|
||||
<div class="icon">
|
||||
<i class="fas fa-fw fa-crown"></i>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
class="text-button view-about"
|
||||
tabindex="2"
|
||||
href="/about"
|
||||
onclick="this.blur();"
|
||||
data-link
|
||||
>
|
||||
<div class="icon">
|
||||
<i class="fas fa-fw fa-info"></i>
|
||||
|
|
@ -91,6 +95,7 @@
|
|||
tabindex="2"
|
||||
href="/settings"
|
||||
onclick="this.blur();"
|
||||
data-link
|
||||
>
|
||||
<div class="icon">
|
||||
<i class="fas fa-fw fa-cog"></i>
|
||||
|
|
@ -101,6 +106,7 @@
|
|||
tabindex="2"
|
||||
href="/account"
|
||||
onclick="this.blur();"
|
||||
data-link
|
||||
>
|
||||
<div class="icon">
|
||||
<i class="fas fa-fw fa-user"></i>
|
||||
|
|
@ -113,6 +119,7 @@
|
|||
tabindex="2"
|
||||
href="/login"
|
||||
onclick="this.blur();"
|
||||
data-link
|
||||
>
|
||||
<div class="icon">
|
||||
<i class="far fa-fw fa-user"></i>
|
||||
|
|
|
|||
BIN
frontend/static/images/monkeymeme.png
Normal file
BIN
frontend/static/images/monkeymeme.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 378 KiB |
|
|
@ -41,7 +41,8 @@
|
|||
compilation.assets["html/pages/settings.html"].source() %> <%=
|
||||
compilation.assets["html/pages/login.html"].source() %> <%=
|
||||
compilation.assets["html/pages/account.html"].source() %> <%=
|
||||
compilation.assets["html/pages/profile.html"].source() %>
|
||||
compilation.assets["html/pages/profile.html"].source() %> <%=
|
||||
compilation.assets["html/pages/404.html"].source() %>
|
||||
</div>
|
||||
|
||||
<%= compilation.assets["html/bottom.html"].source() %>
|
||||
|
|
@ -60,10 +61,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="js/jquery-3.5.1.min.js"></script>
|
||||
<script src="js/jquery.color.min.js"></script>
|
||||
<script src="js/easing.min.js"></script>
|
||||
<script src="js/html2canvas.min.js"></script>
|
||||
<script src="js/select2.min.js"></script>
|
||||
<script src="/./js/jquery-3.5.1.min.js"></script>
|
||||
<script src="/./js/jquery.color.min.js"></script>
|
||||
<script src="/./js/easing.min.js"></script>
|
||||
<script src="/./js/html2canvas.min.js"></script>
|
||||
<script src="/./js/select2.min.js"></script>
|
||||
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ const DEV_CONFIG = {
|
|||
overlay: false,
|
||||
},
|
||||
},
|
||||
output: {
|
||||
publicPath: "/",
|
||||
},
|
||||
plugins: [
|
||||
new ExtraWatchWebpackPlugin({
|
||||
dirs: [resolve(__dirname, "../static/html")],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue