add loading screens instead of dropping the user into an app that's not ready to use
This commit is contained in:
		
							parent
							
								
									7f78d0ce1b
								
							
						
					
					
						commit
						84abba3e0b
					
				
					 6 changed files with 147 additions and 27 deletions
				
			
		| 
						 | 
				
			
			@ -30,7 +30,7 @@ export class BrushEditor extends HTMLElement {
 | 
			
		|||
                }),
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        this.#resizeTextArea();
 | 
			
		||||
        requestAnimationFrame(() => this.#resizeTextArea());
 | 
			
		||||
 | 
			
		||||
        this.errorHeader = this.appendChild(document.createElement("h1"));
 | 
			
		||||
        this.errorHeader.classList.add("error-header");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								static/connection-status.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								static/connection-status.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
export class ConnectionStatus extends HTMLElement {
 | 
			
		||||
    connectedCallback() {
 | 
			
		||||
        this.loggingInDialog = this.querySelector("dialog[name='logging-in-dialog']");
 | 
			
		||||
        this.loggingInThrobber = this.loggingInDialog.querySelector("rkgk-throbber");
 | 
			
		||||
 | 
			
		||||
        // This is a progress dialog and shouldn't be closed.
 | 
			
		||||
        this.loggingInDialog.addEventListener("cancel", (event) => event.preventDefault());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    showLoggingIn() {
 | 
			
		||||
        this.loggingInDialog.showModal();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hideLoggingIn() {
 | 
			
		||||
        this.loggingInDialog.close();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define("rkgk-connection-status", ConnectionStatus);
 | 
			
		||||
| 
						 | 
				
			
			@ -3,6 +3,8 @@
 | 
			
		|||
:root {
 | 
			
		||||
    --color-text: #111;
 | 
			
		||||
    --color-error: #db344b;
 | 
			
		||||
 | 
			
		||||
    --color-brand-blue: #40b1f4;
 | 
			
		||||
    
 | 
			
		||||
    --color-panel-border: rgba(0, 0, 0, 20%);
 | 
			
		||||
    --color-panel-background: #fff;
 | 
			
		||||
| 
						 | 
				
			
			@ -71,6 +73,14 @@ main {
 | 
			
		|||
    height: 100%;
 | 
			
		||||
    position: relative;
 | 
			
		||||
 | 
			
		||||
    &>.fullscreen {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        height: 100%;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        left: 0;
 | 
			
		||||
        top: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &>rkgk-canvas-renderer {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        height: 100%;
 | 
			
		||||
| 
						 | 
				
			
			@ -95,6 +105,14 @@ main {
 | 
			
		|||
        top: 0;
 | 
			
		||||
        margin: 16px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &>#js-loading {
 | 
			
		||||
        background-color: var(--color-panel-background);
 | 
			
		||||
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Buttons */
 | 
			
		||||
| 
						 | 
				
			
			@ -133,14 +151,33 @@ dialog::backdrop {
 | 
			
		|||
 | 
			
		||||
/* Throbbers */
 | 
			
		||||
 | 
			
		||||
@keyframes rkgk-throbber-loading {
 | 
			
		||||
    0% {
 | 
			
		||||
        clip-path: inset(0% 100% 0% 0%);
 | 
			
		||||
        animation-timing-function: cubic-bezier(0.12, 0, 0.39, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    50% {
 | 
			
		||||
        clip-path: inset(0% 0% 0% 0%);
 | 
			
		||||
        animation-timing-function: cubic-bezier(0.61, 1, 0.88, 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    100% {
 | 
			
		||||
        clip-path: inset(0% 0% 0% 100%);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
rkgk-throbber {
 | 
			
		||||
    display: inline;
 | 
			
		||||
 | 
			
		||||
    &.loading {
 | 
			
		||||
        &::before {
 | 
			
		||||
            /* This could use an entertaining animation. */
 | 
			
		||||
            content: "Please wait...";
 | 
			
		||||
        }
 | 
			
		||||
        display: block;
 | 
			
		||||
        width: 16px;
 | 
			
		||||
        height: 16px;
 | 
			
		||||
        background-color: var(--color-brand-blue);
 | 
			
		||||
        animation: infinite alternate rkgk-throbber-loading;
 | 
			
		||||
        /* I wonder how many people will get _that_ reference. */
 | 
			
		||||
        animation-duration: calc(60s / 141.98);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.error {
 | 
			
		||||
| 
						 | 
				
			
			@ -254,3 +291,17 @@ rkgk-welcome {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Connection status dialogs */
 | 
			
		||||
 | 
			
		||||
rkgk-connection-status {
 | 
			
		||||
    &>dialog[name='logging-in-dialog'][open] {
 | 
			
		||||
        border: none;
 | 
			
		||||
        outline: none;
 | 
			
		||||
        background: none;
 | 
			
		||||
 | 
			
		||||
        display: flex;
 | 
			
		||||
        gap: 8px;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,8 @@
 | 
			
		|||
 | 
			
		||||
<html>
 | 
			
		||||
    <head>
 | 
			
		||||
        <meta charset="UTF-8">
 | 
			
		||||
        
 | 
			
		||||
        <title>rakugaki</title>
 | 
			
		||||
 | 
			
		||||
        <link rel="stylesheet" href="static/index.css">
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +12,7 @@
 | 
			
		|||
 | 
			
		||||
        <script src="static/brush-editor.js" type="module"></script>
 | 
			
		||||
        <script src="static/canvas-renderer.js" type="module"></script>
 | 
			
		||||
        <script src="static/connection-status.js" type="module"></script>
 | 
			
		||||
        <script src="static/framework.js" type="module"></script>
 | 
			
		||||
        <script src="static/reticle-renderer.js" type="module"></script>
 | 
			
		||||
        <script src="static/session.js" type="module"></script>
 | 
			
		||||
| 
						 | 
				
			
			@ -17,13 +20,13 @@
 | 
			
		|||
        <script src="static/viewport.js" type="module"></script>
 | 
			
		||||
        <script src="static/welcome.js" type="module"></script>        
 | 
			
		||||
        
 | 
			
		||||
        <script src="static/index.js" type="module"></script>
 | 
			
		||||
        <script src="static/index.js" type="module" defer></script>
 | 
			
		||||
    </head>
 | 
			
		||||
 | 
			
		||||
    <body>
 | 
			
		||||
        <main>
 | 
			
		||||
            <rkgk-canvas-renderer></rkgk-canvas-renderer>
 | 
			
		||||
            <rkgk-reticle-renderer></rkgk-reticle-renderer>
 | 
			
		||||
            <rkgk-canvas-renderer class="fullscreen"></rkgk-canvas-renderer>
 | 
			
		||||
            <rkgk-reticle-renderer class="fullscreen"></rkgk-reticle-renderer>
 | 
			
		||||
            <rkgk-brush-editor></rkgk-brush-editor>
 | 
			
		||||
 | 
			
		||||
            <rkgk-welcome>
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +52,25 @@
 | 
			
		|||
                    </form>
 | 
			
		||||
                </dialog>
 | 
			
		||||
            </rkgk-welcome>
 | 
			
		||||
 | 
			
		||||
            <rkgk-connection-status>
 | 
			
		||||
                <dialog name="logging-in-dialog">
 | 
			
		||||
                    <rkgk-throbber class="loading"></rkgk-throbber><p>Logging in...</p>
 | 
			
		||||
                </dialog>
 | 
			
		||||
            </rkgk-connection-status>
 | 
			
		||||
 | 
			
		||||
            <div class="fullscreen" id="js-loading">
 | 
			
		||||
                <rkgk-throbber class="loading"></rkgk-throbber>
 | 
			
		||||
                <noscript>
 | 
			
		||||
                    <style>
 | 
			
		||||
                        #js-loading>rkgk-throbber { display: none; }
 | 
			
		||||
                    </style>
 | 
			
		||||
                    <p>
 | 
			
		||||
                        rakugaki is a web app and does not work without JavaScript :(<br>
 | 
			
		||||
                        but I swear it's a very lightweight and delightful web app!
 | 
			
		||||
                    </p>
 | 
			
		||||
                </noscript>
 | 
			
		||||
            </div>
 | 
			
		||||
        </main>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,12 @@
 | 
			
		|||
import { Wall } from "./wall.js";
 | 
			
		||||
import { getLoginSecret, getUserId, newSession, waitForLogin } from "./session.js";
 | 
			
		||||
import {
 | 
			
		||||
    getLoginSecret,
 | 
			
		||||
    getUserId,
 | 
			
		||||
    isUserLoggedIn,
 | 
			
		||||
    newSession,
 | 
			
		||||
    registerUser,
 | 
			
		||||
    waitForLogin,
 | 
			
		||||
} from "./session.js";
 | 
			
		||||
import { debounce } from "./framework.js";
 | 
			
		||||
import { ReticleCursor } from "./reticle-renderer.js";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +16,10 @@ let main = document.querySelector("main");
 | 
			
		|||
let canvasRenderer = main.querySelector("rkgk-canvas-renderer");
 | 
			
		||||
let reticleRenderer = main.querySelector("rkgk-reticle-renderer");
 | 
			
		||||
let brushEditor = main.querySelector("rkgk-brush-editor");
 | 
			
		||||
let welcome = main.querySelector("rkgk-welcome");
 | 
			
		||||
let connectionStatus = main.querySelector("rkgk-connection-status");
 | 
			
		||||
 | 
			
		||||
document.getElementById("js-loading").remove();
 | 
			
		||||
reticleRenderer.connectViewport(canvasRenderer.viewport);
 | 
			
		||||
 | 
			
		||||
function updateUrl(session, viewport) {
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +59,16 @@ function readUrl() {
 | 
			
		|||
 | 
			
		||||
// In the background, connect to the server.
 | 
			
		||||
(async () => {
 | 
			
		||||
    console.info("checking for user registration status");
 | 
			
		||||
    if (!isUserLoggedIn()) {
 | 
			
		||||
        await welcome.show({
 | 
			
		||||
            async onRegister(nickname) {
 | 
			
		||||
                return await registerUser(nickname);
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    connectionStatus.showLoggingIn();
 | 
			
		||||
    await waitForLogin();
 | 
			
		||||
    console.info("login ready! starting session");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -67,6 +87,8 @@ function readUrl() {
 | 
			
		|||
    );
 | 
			
		||||
    localStorage.setItem("rkgk.mostRecentWallId", session.wallId);
 | 
			
		||||
 | 
			
		||||
    connectionStatus.hideLoggingIn();
 | 
			
		||||
 | 
			
		||||
    updateUrl(session, canvasRenderer.viewport);
 | 
			
		||||
 | 
			
		||||
    let wall = new Wall(session.wallInfo);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,35 +1,41 @@
 | 
			
		|||
import { isUserLoggedIn, registerUser } from "./session.js";
 | 
			
		||||
 | 
			
		||||
export class Welcome extends HTMLElement {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    connectedCallback() {
 | 
			
		||||
        this.dialog = this.querySelector("dialog[name='welcome-dialog']");
 | 
			
		||||
        this.form = this.dialog.querySelector("form");
 | 
			
		||||
        this.welcomeDialog = this.querySelector("dialog[name='welcome-dialog']");
 | 
			
		||||
        this.welcomeForm = this.welcomeDialog.querySelector("form");
 | 
			
		||||
        this.nicknameField = this.querySelector("input[name='nickname']");
 | 
			
		||||
        this.registerButton = this.querySelector("button[name='register']");
 | 
			
		||||
        this.registerProgress = this.querySelector("rkgk-throbber[name='register-progress']");
 | 
			
		||||
 | 
			
		||||
        if (!isUserLoggedIn()) {
 | 
			
		||||
            this.dialog.showModal();
 | 
			
		||||
        // Once the dialog is open, you need an account to use the website.
 | 
			
		||||
        this.welcomeDialog.addEventListener("cancel", (event) => event.preventDefault());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
            // Require an account to use the website.
 | 
			
		||||
            this.dialog.addEventListener("close", (event) => event.preventDefault());
 | 
			
		||||
    show({ onRegister }) {
 | 
			
		||||
        let resolvePromise;
 | 
			
		||||
        let promise = new Promise((resolve) => (resolvePromise = resolve));
 | 
			
		||||
 | 
			
		||||
            this.form.addEventListener("submit", async (event) => {
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
        this.welcomeDialog.showModal();
 | 
			
		||||
 | 
			
		||||
                this.registerProgress.beginLoading();
 | 
			
		||||
                let response = await registerUser(this.nicknameField.value);
 | 
			
		||||
                if (response.status != "ok") {
 | 
			
		||||
                    this.registerProgress.showError(response.message);
 | 
			
		||||
                }
 | 
			
		||||
        let submitListener = async (event) => {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
 | 
			
		||||
                this.dialog.close();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
            this.registerProgress.beginLoading();
 | 
			
		||||
            let response = await onRegister(this.nicknameField.value);
 | 
			
		||||
            if (response.status == "ok") {
 | 
			
		||||
                this.welcomeDialog.close();
 | 
			
		||||
                resolvePromise();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.registerProgress.showError(response.message);
 | 
			
		||||
            }
 | 
			
		||||
            this.welcomeForm.removeEventListener("submit", submitListener);
 | 
			
		||||
        };
 | 
			
		||||
        this.welcomeForm.addEventListener("submit", submitListener);
 | 
			
		||||
 | 
			
		||||
        return promise;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue