mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-11-10 06:01:28 +08:00
impr: loading page improvements (@miodec) (#6893)
- Refactor the loading page and the functions responsible for showing elements - Navigate loading options no longer override but they are used BEFORE the page loading options. Keyframes are scaled accordingly to transition smoothly - Removed the error element from the account page - Added a rejection / error handler to the loading page - Removed one more dependency from the account controller
This commit is contained in:
parent
b6959552ab
commit
725fde1ae1
9 changed files with 197 additions and 168 deletions
|
|
@ -1,10 +1,4 @@
|
|||
<div class="page pageAccount hidden full-width" id="pageAccount">
|
||||
<div class="error hidden content-grid">
|
||||
<div class="icon">
|
||||
<i class="fas fa-fw fa-times"></i>
|
||||
</div>
|
||||
<div class="text">Error</div>
|
||||
</div>
|
||||
<div class="content full-width content-grid">
|
||||
<div class="profile">
|
||||
<div class="details both">
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
<div class="page pageLoading" id="pageLoading">
|
||||
<div class="preloader">
|
||||
<div class="icon">
|
||||
<i class="fas fa-fw fa-spin fa-circle-notch"></i>
|
||||
</div>
|
||||
<div class="barWrapper hidden">
|
||||
<div class="bar">
|
||||
<div class="fill"></div>
|
||||
</div>
|
||||
<div class="text"></div>
|
||||
</div>
|
||||
<div class="spinner">
|
||||
<i class="fas fa-fw fa-spin fa-circle-notch"></i>
|
||||
</div>
|
||||
<div class="error hidden">
|
||||
<i class="fas fa-fw fa-times"></i>
|
||||
</div>
|
||||
<div class="bar hidden">
|
||||
<div class="fill"></div>
|
||||
</div>
|
||||
<div class="text hidden">Loading...</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,6 @@
|
|||
.pageAccount {
|
||||
height: 100%;
|
||||
|
||||
.error {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
align-content: center;
|
||||
height: 100%;
|
||||
.icon {
|
||||
font-size: 2rem;
|
||||
color: var(--error-color);
|
||||
}
|
||||
.text {
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.accountVerificatinNotice {
|
||||
background: var(--bg-color);
|
||||
border-radius: var(--roundness);
|
||||
|
|
|
|||
|
|
@ -1,38 +1,40 @@
|
|||
.pageLoading {
|
||||
.preloader {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
place-self: center;
|
||||
align-content: center;
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
|
||||
.spinner,
|
||||
.error {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
color: var(--main-color);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.text {
|
||||
height: 1.25em;
|
||||
}
|
||||
|
||||
.bar {
|
||||
max-width: 20rem;
|
||||
width: 100%;
|
||||
height: 0.5rem;
|
||||
background: var(--sub-alt-color);
|
||||
border-radius: var(--roundness);
|
||||
justify-self: center;
|
||||
display: grid;
|
||||
.barWrapper {
|
||||
justify-content: center;
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
.text {
|
||||
height: 1.25em;
|
||||
}
|
||||
.bar {
|
||||
width: 20rem;
|
||||
height: 0.5rem;
|
||||
background: var(--sub-alt-color);
|
||||
border-radius: var(--roundness);
|
||||
justify-self: center;
|
||||
.fill {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: var(--main-color);
|
||||
border-radius: var(--roundness);
|
||||
// transition: 1s;
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
font-size: 2rem;
|
||||
color: var(--main-color);
|
||||
margin-bottom: 1rem;
|
||||
.fill {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
background: var(--main-color);
|
||||
border-radius: var(--roundness);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import * as DB from "../db";
|
|||
import * as Loader from "../elements/loader";
|
||||
import * as LoginPage from "../pages/login";
|
||||
import * as RegisterCaptchaModal from "../modals/register-captcha";
|
||||
import * as Account from "../pages/account";
|
||||
import {
|
||||
GoogleAuthProvider,
|
||||
GithubAuthProvider,
|
||||
|
|
@ -97,9 +96,6 @@ async function getDataAndInit(): Promise<boolean> {
|
|||
fb.functions.applyGlobalCSS();
|
||||
}
|
||||
}
|
||||
if (window.location.pathname === "/account") {
|
||||
await Account.downloadResults();
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
|
@ -170,28 +166,10 @@ export async function onAuthStateChanged(
|
|||
},
|
||||
];
|
||||
|
||||
if (
|
||||
window.location.pathname === "/account" ||
|
||||
window.location.pathname === "/login"
|
||||
) {
|
||||
keyframes = [
|
||||
{
|
||||
percentage: 40,
|
||||
durationMs: 1000,
|
||||
text: "Downloading user data...",
|
||||
},
|
||||
{
|
||||
percentage: 90,
|
||||
durationMs: 1000,
|
||||
text: "Downloading results...",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
//undefined means navigate to whatever the current window.location.pathname is
|
||||
await navigate(undefined, {
|
||||
force: true,
|
||||
overrideLoadingOptions: {
|
||||
loadingOptions: {
|
||||
shouldLoad: () => {
|
||||
return user !== null;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ type ChangeOptions = {
|
|||
force?: boolean;
|
||||
params?: Record<string, string>;
|
||||
data?: unknown;
|
||||
overrideLoadingOptions?: LoadingOptions;
|
||||
loadingOptions?: LoadingOptions;
|
||||
};
|
||||
|
||||
function updateOpenGraphUrl(): void {
|
||||
|
|
@ -50,11 +50,77 @@ function updateTitle(nextPage: { id: string; display?: string }): void {
|
|||
}
|
||||
}
|
||||
|
||||
async function showLoading({
|
||||
loadingOptions,
|
||||
totalDuration,
|
||||
easingMethod,
|
||||
}: {
|
||||
loadingOptions: LoadingOptions[];
|
||||
totalDuration: number;
|
||||
easingMethod: Misc.JQueryEasing;
|
||||
}): Promise<void> {
|
||||
PageLoading.page.element.removeClass("hidden").css("opacity", 0);
|
||||
await PageLoading.page.beforeShow({});
|
||||
|
||||
const fillDivider = loadingOptions.length;
|
||||
const fillOffset = 100 / fillDivider;
|
||||
|
||||
//void here to run the loading promise as soon as possible
|
||||
void Misc.promiseAnimation(
|
||||
PageLoading.page.element,
|
||||
{
|
||||
opacity: "1",
|
||||
},
|
||||
totalDuration / 2,
|
||||
easingMethod
|
||||
);
|
||||
|
||||
for (let i = 0; i < loadingOptions.length; i++) {
|
||||
const currentOffset = fillOffset * i;
|
||||
const options = loadingOptions[i] as LoadingOptions;
|
||||
if (options.style === "bar") {
|
||||
await PageLoading.showBar();
|
||||
if (i === 0) {
|
||||
await PageLoading.updateBar(0, 0);
|
||||
PageLoading.updateText("");
|
||||
}
|
||||
} else {
|
||||
PageLoading.showSpinner();
|
||||
}
|
||||
|
||||
if (options.style === "bar") {
|
||||
await getLoadingPromiseWithBarKeyframes(
|
||||
options,
|
||||
fillDivider,
|
||||
currentOffset
|
||||
);
|
||||
void PageLoading.updateBar(100, 125);
|
||||
PageLoading.updateText("Done");
|
||||
} else {
|
||||
await options.waitFor();
|
||||
}
|
||||
}
|
||||
|
||||
await Misc.promiseAnimation(
|
||||
PageLoading.page.element,
|
||||
{
|
||||
opacity: "0",
|
||||
},
|
||||
totalDuration / 2,
|
||||
easingMethod
|
||||
);
|
||||
|
||||
await PageLoading.page.afterHide();
|
||||
PageLoading.page.element.addClass("hidden");
|
||||
}
|
||||
|
||||
async function getLoadingPromiseWithBarKeyframes(
|
||||
loadingOptions: Extract<
|
||||
NonNullable<Page<unknown>["loadingOptions"]>,
|
||||
{ style: "bar" }
|
||||
>
|
||||
>,
|
||||
fillDivider: number,
|
||||
fillOffset: number
|
||||
): Promise<void> {
|
||||
let aborted = false;
|
||||
let loadingPromise = loadingOptions.waitFor();
|
||||
|
|
@ -66,7 +132,10 @@ async function getLoadingPromiseWithBarKeyframes(
|
|||
if (keyframe.text !== undefined) {
|
||||
PageLoading.updateText(keyframe.text);
|
||||
}
|
||||
await PageLoading.updateBar(keyframe.percentage, keyframe.durationMs);
|
||||
await PageLoading.updateBar(
|
||||
fillOffset + keyframe.percentage / fillDivider,
|
||||
keyframe.durationMs
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
||||
|
|
@ -145,58 +214,47 @@ export async function change(
|
|||
previousPage.element.addClass("hidden");
|
||||
await previousPage?.afterHide();
|
||||
|
||||
//show loading page if needed
|
||||
try {
|
||||
let loadingOptions: LoadingOptions[] = [];
|
||||
if (options.loadingOptions) {
|
||||
loadingOptions.push(options.loadingOptions);
|
||||
}
|
||||
if (nextPage.loadingOptions) {
|
||||
loadingOptions.push(nextPage.loadingOptions);
|
||||
}
|
||||
|
||||
if (loadingOptions.length > 0) {
|
||||
const shouldShowLoading =
|
||||
options.loadingOptions?.shouldLoad() ||
|
||||
nextPage.loadingOptions?.shouldLoad();
|
||||
|
||||
if (shouldShowLoading === true) {
|
||||
await showLoading({
|
||||
loadingOptions,
|
||||
totalDuration,
|
||||
easingMethod,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
pages.loading.element.addClass("active");
|
||||
ActivePage.set(pages.loading.id);
|
||||
Focus.set(false);
|
||||
PageLoading.showError();
|
||||
PageLoading.updateText(
|
||||
`Failed to load the ${nextPage.id} page: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`
|
||||
);
|
||||
PageTransition.set(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
//between
|
||||
updateTitle(nextPage);
|
||||
ActivePage.set(nextPage.id);
|
||||
updateOpenGraphUrl();
|
||||
|
||||
const loadingOptions =
|
||||
options.overrideLoadingOptions ?? nextPage.loadingOptions;
|
||||
|
||||
//show loading page if needed
|
||||
if (loadingOptions && loadingOptions.shouldLoad()) {
|
||||
pages.loading.element.removeClass("hidden").css("opacity", 0);
|
||||
await pages.loading.beforeShow({});
|
||||
|
||||
if (loadingOptions.style === "bar") {
|
||||
await PageLoading.showBar();
|
||||
await PageLoading.updateBar(0, 0);
|
||||
PageLoading.updateText("");
|
||||
} else {
|
||||
PageLoading.showSpinner();
|
||||
}
|
||||
|
||||
//void here to run the loading promise as soon as possible
|
||||
void Misc.promiseAnimation(
|
||||
pages.loading.element,
|
||||
{
|
||||
opacity: "1",
|
||||
},
|
||||
totalDuration / 2,
|
||||
easingMethod
|
||||
);
|
||||
|
||||
if (loadingOptions.style === "bar") {
|
||||
await getLoadingPromiseWithBarKeyframes(loadingOptions);
|
||||
void PageLoading.updateBar(100, 125);
|
||||
PageLoading.updateText("Done");
|
||||
} else {
|
||||
await loadingOptions.waitFor();
|
||||
}
|
||||
|
||||
await Misc.promiseAnimation(
|
||||
pages.loading.element,
|
||||
{
|
||||
opacity: "0",
|
||||
},
|
||||
totalDuration / 2,
|
||||
easingMethod
|
||||
);
|
||||
|
||||
await pages.loading.afterHide();
|
||||
pages.loading.element.addClass("hidden");
|
||||
}
|
||||
|
||||
Focus.set(false);
|
||||
|
||||
//next page
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ type NavigateOptions = {
|
|||
force?: boolean;
|
||||
empty?: boolean;
|
||||
data?: unknown;
|
||||
overrideLoadingOptions?: LoadingOptions;
|
||||
loadingOptions?: LoadingOptions;
|
||||
};
|
||||
|
||||
function pathToRegex(path: string): RegExp {
|
||||
|
|
|
|||
|
|
@ -987,26 +987,14 @@ export async function downloadResults(offset?: number): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
function showError(message: string): void {
|
||||
$(".pageAccount .error .text").html(message);
|
||||
$(".pageAccount .error").removeClass("hidden");
|
||||
$(".pageAccount .content").remove();
|
||||
}
|
||||
|
||||
async function update(): Promise<void> {
|
||||
if (DB.getSnapshot() === null) {
|
||||
showError(
|
||||
"Looks like your account data didn't download correctly. Please refresh the page.<br>If this error persists, please contact support."
|
||||
);
|
||||
} else {
|
||||
await downloadResults();
|
||||
try {
|
||||
await Misc.sleep(0);
|
||||
await fillContent();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
Notifications.add(`Something went wrong: ${e}`, -1);
|
||||
}
|
||||
await downloadResults();
|
||||
try {
|
||||
await Misc.sleep(0);
|
||||
await fillContent();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
Notifications.add(`Something went wrong: ${e}`, -1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1344,12 +1332,19 @@ export const page = new Page({
|
|||
shouldLoad: () => {
|
||||
return DB.getSnapshot()?.results === undefined;
|
||||
},
|
||||
waitFor: downloadResults,
|
||||
waitFor: async () => {
|
||||
if (DB.getSnapshot() === null) {
|
||||
throw new Error(
|
||||
"Looks like your account data didn't download correctly. Please refresh the page.<br>If this error persists, please contact support."
|
||||
);
|
||||
}
|
||||
return downloadResults();
|
||||
},
|
||||
style: "bar",
|
||||
keyframes: [
|
||||
{
|
||||
percentage: 100,
|
||||
durationMs: 3000,
|
||||
percentage: 90,
|
||||
durationMs: 2000,
|
||||
text: "Downloading results...",
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,12 +1,19 @@
|
|||
import Page from "./page";
|
||||
import * as Skeleton from "../utils/skeleton";
|
||||
|
||||
const pageEl = $(".page.pageLoading");
|
||||
const barEl = pageEl.find(".bar");
|
||||
const errorEl = pageEl.find(".error");
|
||||
const spinnerEl = pageEl.find(".spinner");
|
||||
const textEl = pageEl.find(".text");
|
||||
|
||||
export async function updateBar(
|
||||
percentage: number,
|
||||
duration: number
|
||||
): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
$(".pageLoading .fill")
|
||||
barEl
|
||||
.find(".fill")
|
||||
.stop(true, false)
|
||||
.animate(
|
||||
{
|
||||
|
|
@ -21,22 +28,33 @@ export async function updateBar(
|
|||
}
|
||||
|
||||
export function updateText(text: string): void {
|
||||
$(".pageLoading .text").text(text);
|
||||
textEl.removeClass("hidden").html(text);
|
||||
}
|
||||
|
||||
export function showSpinner(): void {
|
||||
$(".pageLoading .preloader .icon").removeClass("hidden");
|
||||
$(".pageLoading .preloader .barWrapper").addClass("hidden");
|
||||
barEl.addClass("hidden");
|
||||
errorEl.addClass("hidden");
|
||||
spinnerEl.removeClass("hidden");
|
||||
textEl.addClass("hidden");
|
||||
}
|
||||
|
||||
export function showError(): void {
|
||||
barEl.addClass("hidden");
|
||||
spinnerEl.addClass("hidden");
|
||||
errorEl.removeClass("hidden");
|
||||
textEl.addClass("hidden");
|
||||
}
|
||||
|
||||
export async function showBar(): Promise<void> {
|
||||
$(".pageLoading .preloader .icon").addClass("hidden");
|
||||
$(".pageLoading .preloader .barWrapper").removeClass("hidden");
|
||||
barEl.removeClass("hidden");
|
||||
errorEl.addClass("hidden");
|
||||
spinnerEl.addClass("hidden");
|
||||
textEl.addClass("hidden");
|
||||
}
|
||||
|
||||
export const page = new Page({
|
||||
id: "loading",
|
||||
element: $(".page.pageLoading"),
|
||||
element: pageEl,
|
||||
path: "/",
|
||||
afterHide: async (): Promise<void> => {
|
||||
Skeleton.remove("pageLoading");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue