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
|
@ -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);
|
|
@ -4,6 +4,8 @@
|
|||
--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) => {
|
||||
this.welcomeDialog.showModal();
|
||||
|
||||
let submitListener = async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
this.registerProgress.beginLoading();
|
||||
let response = await registerUser(this.nicknameField.value);
|
||||
if (response.status != "ok") {
|
||||
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);
|
||||
|
||||
this.dialog.close();
|
||||
});
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue