diff --git a/static/brush-editor.js b/static/brush-editor.js index 11abc2a..df8cffe 100644 --- a/static/brush-editor.js +++ b/static/brush-editor.js @@ -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"); diff --git a/static/connection-status.js b/static/connection-status.js new file mode 100644 index 0000000..2162d51 --- /dev/null +++ b/static/connection-status.js @@ -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); diff --git a/static/index.css b/static/index.css index c261380..4a656ff 100644 --- a/static/index.css +++ b/static/index.css @@ -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; + } +} diff --git a/static/index.html b/static/index.html index 5860f18..20f698e 100644 --- a/static/index.html +++ b/static/index.html @@ -2,6 +2,8 @@ + + rakugaki @@ -10,6 +12,7 @@ + @@ -17,13 +20,13 @@ - +
- - + + @@ -49,6 +52,25 @@ + + + +

Logging in...

+
+
+ +
+ + +
diff --git a/static/index.js b/static/index.js index 4bfbddd..27c1e6f 100644 --- a/static/index.js +++ b/static/index.js @@ -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); diff --git a/static/welcome.js b/static/welcome.js index 663d144..11ce1ad 100644 --- a/static/welcome.js +++ b/static/welcome.js @@ -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; } }