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 = this.appendChild(document.createElement("h1"));
|
||||||
this.errorHeader.classList.add("error-header");
|
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-text: #111;
|
||||||
--color-error: #db344b;
|
--color-error: #db344b;
|
||||||
|
|
||||||
|
--color-brand-blue: #40b1f4;
|
||||||
|
|
||||||
--color-panel-border: rgba(0, 0, 0, 20%);
|
--color-panel-border: rgba(0, 0, 0, 20%);
|
||||||
--color-panel-background: #fff;
|
--color-panel-background: #fff;
|
||||||
|
|
||||||
|
@ -71,6 +73,14 @@ main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
&>.fullscreen {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&>rkgk-canvas-renderer {
|
&>rkgk-canvas-renderer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -95,6 +105,14 @@ main {
|
||||||
top: 0;
|
top: 0;
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&>#js-loading {
|
||||||
|
background-color: var(--color-panel-background);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
|
@ -133,14 +151,33 @@ dialog::backdrop {
|
||||||
|
|
||||||
/* Throbbers */
|
/* 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 {
|
rkgk-throbber {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
|
||||||
&.loading {
|
&.loading {
|
||||||
&::before {
|
display: block;
|
||||||
/* This could use an entertaining animation. */
|
width: 16px;
|
||||||
content: "Please wait...";
|
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 {
|
&.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>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
<title>rakugaki</title>
|
<title>rakugaki</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="static/index.css">
|
<link rel="stylesheet" href="static/index.css">
|
||||||
|
@ -10,6 +12,7 @@
|
||||||
|
|
||||||
<script src="static/brush-editor.js" type="module"></script>
|
<script src="static/brush-editor.js" type="module"></script>
|
||||||
<script src="static/canvas-renderer.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/framework.js" type="module"></script>
|
||||||
<script src="static/reticle-renderer.js" type="module"></script>
|
<script src="static/reticle-renderer.js" type="module"></script>
|
||||||
<script src="static/session.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/viewport.js" type="module"></script>
|
||||||
<script src="static/welcome.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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<rkgk-canvas-renderer></rkgk-canvas-renderer>
|
<rkgk-canvas-renderer class="fullscreen"></rkgk-canvas-renderer>
|
||||||
<rkgk-reticle-renderer></rkgk-reticle-renderer>
|
<rkgk-reticle-renderer class="fullscreen"></rkgk-reticle-renderer>
|
||||||
<rkgk-brush-editor></rkgk-brush-editor>
|
<rkgk-brush-editor></rkgk-brush-editor>
|
||||||
|
|
||||||
<rkgk-welcome>
|
<rkgk-welcome>
|
||||||
|
@ -49,6 +52,25 @@
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
</rkgk-welcome>
|
</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>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
import { Wall } from "./wall.js";
|
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 { debounce } from "./framework.js";
|
||||||
import { ReticleCursor } from "./reticle-renderer.js";
|
import { ReticleCursor } from "./reticle-renderer.js";
|
||||||
|
|
||||||
|
@ -9,7 +16,10 @@ let main = document.querySelector("main");
|
||||||
let canvasRenderer = main.querySelector("rkgk-canvas-renderer");
|
let canvasRenderer = main.querySelector("rkgk-canvas-renderer");
|
||||||
let reticleRenderer = main.querySelector("rkgk-reticle-renderer");
|
let reticleRenderer = main.querySelector("rkgk-reticle-renderer");
|
||||||
let brushEditor = main.querySelector("rkgk-brush-editor");
|
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);
|
reticleRenderer.connectViewport(canvasRenderer.viewport);
|
||||||
|
|
||||||
function updateUrl(session, viewport) {
|
function updateUrl(session, viewport) {
|
||||||
|
@ -49,6 +59,16 @@ function readUrl() {
|
||||||
|
|
||||||
// In the background, connect to the server.
|
// In the background, connect to the server.
|
||||||
(async () => {
|
(async () => {
|
||||||
|
console.info("checking for user registration status");
|
||||||
|
if (!isUserLoggedIn()) {
|
||||||
|
await welcome.show({
|
||||||
|
async onRegister(nickname) {
|
||||||
|
return await registerUser(nickname);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionStatus.showLoggingIn();
|
||||||
await waitForLogin();
|
await waitForLogin();
|
||||||
console.info("login ready! starting session");
|
console.info("login ready! starting session");
|
||||||
|
|
||||||
|
@ -67,6 +87,8 @@ function readUrl() {
|
||||||
);
|
);
|
||||||
localStorage.setItem("rkgk.mostRecentWallId", session.wallId);
|
localStorage.setItem("rkgk.mostRecentWallId", session.wallId);
|
||||||
|
|
||||||
|
connectionStatus.hideLoggingIn();
|
||||||
|
|
||||||
updateUrl(session, canvasRenderer.viewport);
|
updateUrl(session, canvasRenderer.viewport);
|
||||||
|
|
||||||
let wall = new Wall(session.wallInfo);
|
let wall = new Wall(session.wallInfo);
|
||||||
|
|
|
@ -1,35 +1,41 @@
|
||||||
import { isUserLoggedIn, registerUser } from "./session.js";
|
|
||||||
|
|
||||||
export class Welcome extends HTMLElement {
|
export class Welcome extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.dialog = this.querySelector("dialog[name='welcome-dialog']");
|
this.welcomeDialog = this.querySelector("dialog[name='welcome-dialog']");
|
||||||
this.form = this.dialog.querySelector("form");
|
this.welcomeForm = this.welcomeDialog.querySelector("form");
|
||||||
this.nicknameField = this.querySelector("input[name='nickname']");
|
this.nicknameField = this.querySelector("input[name='nickname']");
|
||||||
this.registerButton = this.querySelector("button[name='register']");
|
this.registerButton = this.querySelector("button[name='register']");
|
||||||
this.registerProgress = this.querySelector("rkgk-throbber[name='register-progress']");
|
this.registerProgress = this.querySelector("rkgk-throbber[name='register-progress']");
|
||||||
|
|
||||||
if (!isUserLoggedIn()) {
|
// Once the dialog is open, you need an account to use the website.
|
||||||
this.dialog.showModal();
|
this.welcomeDialog.addEventListener("cancel", (event) => event.preventDefault());
|
||||||
|
}
|
||||||
|
|
||||||
// Require an account to use the website.
|
show({ onRegister }) {
|
||||||
this.dialog.addEventListener("close", (event) => event.preventDefault());
|
let resolvePromise;
|
||||||
|
let promise = new Promise((resolve) => (resolvePromise = resolve));
|
||||||
|
|
||||||
this.form.addEventListener("submit", async (event) => {
|
this.welcomeDialog.showModal();
|
||||||
|
|
||||||
|
let submitListener = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
this.registerProgress.beginLoading();
|
this.registerProgress.beginLoading();
|
||||||
let response = await registerUser(this.nicknameField.value);
|
let response = await onRegister(this.nicknameField.value);
|
||||||
if (response.status != "ok") {
|
if (response.status == "ok") {
|
||||||
|
this.welcomeDialog.close();
|
||||||
|
resolvePromise();
|
||||||
|
} else {
|
||||||
this.registerProgress.showError(response.message);
|
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