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 @@
+
+
+
+
+
+
+
+
+
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;
}
}