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:
Jack 2022-06-24 22:12:52 +02:00 committed by GitHub
parent 7782b11b12
commit 6499cedc28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1029 additions and 1009 deletions

View 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;
}
}
}

View file

@ -59,6 +59,14 @@ a {
}
}
a[data-link] * {
pointer-events: none;
}
a.button {
text-decoration: none;
}
body {
margin: 0;
padding: 0;

View file

@ -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";

View file

@ -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) {

View file

@ -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);

View file

@ -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;
}

View file

@ -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");
});

View file

@ -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("/");
});

View file

@ -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;

View file

@ -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();
}

View file

@ -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);
},

View file

@ -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();

View file

@ -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);

View file

@ -0,0 +1,19 @@
import Page from "./page";
export const page = new Page(
"404",
$(".page.page404"),
"/404",
async () => {
//
},
async () => {
//
},
async () => {
//
},
async () => {
//
}
);

View file

@ -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

View file

@ -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 () => {
//
}
);

View file

@ -297,16 +297,16 @@ export const page = new Page(
"login",
$(".page.pageLogin"),
"/login",
() => {
async () => {
//
},
() => {
async () => {
//
},
() => {
async () => {
//
},
() => {
async () => {
//
}
);

View file

@ -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;

View file

@ -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 () => {
//
}
);

View file

@ -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 () => {
//
}
);

View file

@ -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();
}
);

View file

@ -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 = [];

View file

@ -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> {

View file

@ -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();
});

View file

@ -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;
}

View file

@ -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,

View file

@ -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";

View file

@ -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;

View file

@ -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
);
}
}

View file

@ -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

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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">

View file

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

View file

@ -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>

View file

@ -17,6 +17,9 @@ const DEV_CONFIG = {
overlay: false,
},
},
output: {
publicPath: "/",
},
plugins: [
new ExtraWatchWebpackPlugin({
dirs: [resolve(__dirname, "../static/html")],