From 9875ea7ccafe9b6ff8fce3c840142d4b96a01e39 Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Mon, 4 Mar 2024 14:39:48 -0500 Subject: [PATCH] Progress bar for scene loading (#71) --- image-generator-demo/src/app/app.element.scss | 14 ----- image-generator-demo/src/app/app.element.ts | 6 +-- studio-frontend/src/app/app.element.scss | 14 ----- studio-frontend/src/app/app.element.ts | 6 +-- studio-ui/src/index.ts | 1 + studio-ui/src/lib/progress.element.scss | 15 ++++++ studio-ui/src/lib/progress.element.ts | 51 +++++++++++++++++++ studio-web/src/index.ts | 1 + studio-web/src/lib/loading.element.scss | 23 +++++++++ studio-web/src/lib/loading.element.ts | 39 ++++++++++++++ studio/src/scene.rs | 39 +++++++++++++- studio/src/wasm/conv.rs | 6 +++ tsconfig.base.json | 2 + 13 files changed, 180 insertions(+), 37 deletions(-) create mode 100644 studio-ui/src/lib/progress.element.scss create mode 100644 studio-ui/src/lib/progress.element.ts create mode 100644 studio-web/src/lib/loading.element.scss create mode 100644 studio-web/src/lib/loading.element.ts diff --git a/image-generator-demo/src/app/app.element.scss b/image-generator-demo/src/app/app.element.scss index 2687776..4473358 100644 --- a/image-generator-demo/src/app/app.element.scss +++ b/image-generator-demo/src/app/app.element.scss @@ -166,17 +166,3 @@ background: transparent; color: #FFFC; } - -.loading { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - background: #222; - color: #FFF; - font: { - family: "Fira Sans", sans-serif; - weight: 700; - } -} diff --git a/image-generator-demo/src/app/app.element.ts b/image-generator-demo/src/app/app.element.ts index 6d18acd..853f6d6 100644 --- a/image-generator-demo/src/app/app.element.ts +++ b/image-generator-demo/src/app/app.element.ts @@ -16,6 +16,7 @@ import { styleMap } from "lit/directives/style-map.js"; import { ImageInfo, LcmClient } from "./lcm-client"; import "@storyteller/studio-web/canvas-provider"; +import "@storyteller/studio-web/loading"; import "@storyteller/studio-web/scene-hierarchy"; import "@storyteller/studio-web/studio"; import "@storyteller/studio-web/transform-toolbar"; @@ -293,10 +294,7 @@ export class AppElement extends LitElement { nothing} ${this.loading ? html` - -
-

Loading

-
+ ` : nothing} ` diff --git a/studio-frontend/src/app/app.element.scss b/studio-frontend/src/app/app.element.scss index f580b98..c292849 100644 --- a/studio-frontend/src/app/app.element.scss +++ b/studio-frontend/src/app/app.element.scss @@ -39,17 +39,3 @@ sts-button.active { max-height: calc(100vh - 32px); pointer-events: auto; } - -.loading { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - background: #222; - color: #FFF; - font: { - family: "Fira Sans", sans-serif; - weight: 700; - } -} diff --git a/studio-frontend/src/app/app.element.ts b/studio-frontend/src/app/app.element.ts index da2ace6..d50e912 100644 --- a/studio-frontend/src/app/app.element.ts +++ b/studio-frontend/src/app/app.element.ts @@ -25,6 +25,7 @@ import "@storyteller/studio-ui/icon"; import "@storyteller/studio-ui/toolbar"; import "@storyteller/studio-ui/tree"; import "@storyteller/studio-web/canvas-provider"; +import "@storyteller/studio-web/loading"; import "@storyteller/studio-web/scene-hierarchy"; import "@storyteller/studio-web/studio"; import "@storyteller/studio-web/transform-toolbar"; @@ -161,10 +162,7 @@ export class AppElement extends LitElement { ` : nothing} ${this.loading ? html` - -
-

Loading

-
+ ` : nothing} `; diff --git a/studio-ui/src/index.ts b/studio-ui/src/index.ts index 6cbf9ae..fe3b7a5 100644 --- a/studio-ui/src/index.ts +++ b/studio-ui/src/index.ts @@ -1,4 +1,5 @@ export * from "./lib/button.element"; export * from "./lib/icon.element"; +export * from "./lib/progress.element"; export * from "./lib/toolbar.element"; export * from "./lib/tree.element"; diff --git a/studio-ui/src/lib/progress.element.scss b/studio-ui/src/lib/progress.element.scss new file mode 100644 index 0000000..c388254 --- /dev/null +++ b/studio-ui/src/lib/progress.element.scss @@ -0,0 +1,15 @@ +:host { + display: block; + height: 0.5rem; + background: #713837; + overflow: hidden; +} + +.fill { + height: 100%; + background: #E66462; +} + +.animated { + // TODO +} diff --git a/studio-ui/src/lib/progress.element.ts b/studio-ui/src/lib/progress.element.ts new file mode 100644 index 0000000..cd188da --- /dev/null +++ b/studio-ui/src/lib/progress.element.ts @@ -0,0 +1,51 @@ +import { LitElement, type PropertyValues, html, unsafeCSS } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { styleMap } from "lit/directives/style-map.js"; + +import styles from "./progress.element.scss?inline"; + +@customElement("sts-progress") +export class ProgressElement extends LitElement { + static override styles = unsafeCSS(styles); + + @property() override role = "progressbar"; + + @property({ type: Number }) min = 0; + @property({ type: Number }) max = 100; + @property({ type: Number }) value = 0; + @property({ type: Boolean }) indeterminate = false; + + protected override willUpdate(changes: PropertyValues): void { + if (this.min === this.max) + this.indeterminate = true; + + if (this.indeterminate) { + this.removeAttribute("aria-valuenow"); + this.removeAttribute("aria-valuemin"); + this.removeAttribute("aria-valuemax"); + } else { + this.setAttribute("aria-valuenow", this.value.toString()); + + if (this.min !== 0) + this.setAttribute("aria-valuemin", this.min.toString()); + + if (this.max !== 100) + this.setAttribute("aria-valuemax", this.max.toString()); + } + + super.willUpdate(changes); + } + + protected override render = () => html` + ${this.indeterminate ? html` +
+ ` : html` +
+ `} + `; +} diff --git a/studio-web/src/index.ts b/studio-web/src/index.ts index e97dae9..0420917 100644 --- a/studio-web/src/index.ts +++ b/studio-web/src/index.ts @@ -1,4 +1,5 @@ export * from "./lib/canvas-provider.element"; +export * from "./lib/loading.element"; export * from "./lib/scene-hierarchy.element"; export * from "./lib/studio.element"; export * from "./lib/transform-toolbar.element"; diff --git a/studio-web/src/lib/loading.element.scss b/studio-web/src/lib/loading.element.scss new file mode 100644 index 0000000..b54eca1 --- /dev/null +++ b/studio-web/src/lib/loading.element.scss @@ -0,0 +1,23 @@ +:host { + position: fixed; + inset: 0; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1rem; + + background: #1A1A27; + color: #FFF; + + font: { + family: "Fira Sans", sans-serif; + weight: 700; + } +} + +.progress { + width: 512px; + border-radius: 0.25rem; +} diff --git a/studio-web/src/lib/loading.element.ts b/studio-web/src/lib/loading.element.ts new file mode 100644 index 0000000..8e6f78e --- /dev/null +++ b/studio-web/src/lib/loading.element.ts @@ -0,0 +1,39 @@ +import { on } from "@storyteller/framework"; +import { type LoadingQueueEvent } from "@storyteller/studio"; +import { LitElement, html, unsafeCSS } from "lit"; +import { customElement, state } from "lit/decorators.js"; + +import "@storyteller/studio-ui/progress"; + +import styles from "./loading.element.scss?inline"; + +@customElement("sts-loading") +export class LoadingElement extends LitElement { + static override styles = unsafeCSS(styles); + + @state() queueMax = 0; + @state() queueLoaded = 0; + + override connectedCallback(): void { + this.queueMax = 0; + this.queueLoaded = 0; + super.connectedCallback(); + } + + @on("window:loading-queue") + onLoadingQueue({ detail: queueLen }: LoadingQueueEvent): void { + this.queueMax = Math.max(queueLen, this.queueMax); + this.queueLoaded = this.queueMax - queueLen; + } + + protected override render = () => html` +

Loading

+ + + `; +} diff --git a/studio/src/scene.rs b/studio/src/scene.rs index 3eca8f2..ebd3c9c 100644 --- a/studio/src/scene.rs +++ b/studio/src/scene.rs @@ -96,6 +96,7 @@ impl Plugin for ScenePlugin { wasm::notify_state_changes .map(bevy::utils::error) .run_if(resource_changed::>()), + wasm::notify_loading_queue_changes.map(bevy::utils::error), ), ); } @@ -516,7 +517,7 @@ mod wasm { use bevy::{prelude::*, window::PrimaryWindow}; use wasm_bindgen::prelude::*; - use super::{LoadLibraryObjectEvent, SceneState}; + use super::{LoadLibraryObjectEvent, LoadingQueue, SceneState}; use crate::wasm::{self, StaticBuffer, WasmMessageBuffer}; static OBJECT_LOAD_REQUEST: WasmMessageBuffer = WasmMessageBuffer::new(); @@ -559,4 +560,40 @@ declare global { } Ok(()) } + + #[wasm_bindgen(typescript_custom_section)] + const LOADING_QUEUE_EVENT: &str = r#" +export interface LoadingQueueEvent extends CustomEvent { + type: "loading-queue"; + detail: number; +} + +declare global { + export interface GlobalEventHandlersEventMap { + "loading-queue": LoadingQueueEvent; + } +} + "#; + + pub(super) fn notify_loading_queue_changes( + mut l_last_sent: Local>, + r_loading: Res, + q_window: Query<&Window, With>, + ) -> Result<(), String> { + let queue_len = r_loading.queue.len(); + if let Some(last_sent) = *l_last_sent { + if last_sent == queue_len { + return Ok(()); + } + } + + let Ok(window) = q_window.get_single() else { + return Ok(()); + }; + + wasm::dispatch_to_js(window, "loading-queue", &queue_len)?; + *l_last_sent = Some(queue_len); + + Ok(()) + } } diff --git a/studio/src/wasm/conv.rs b/studio/src/wasm/conv.rs index 679ed17..e2a4018 100644 --- a/studio/src/wasm/conv.rs +++ b/studio/src/wasm/conv.rs @@ -147,6 +147,12 @@ impl AsJsValue for u32 { } } +impl AsJsValue for usize { + fn as_js(&self) -> Result { + Ok(JsValue::from_f64(*self as f64)) + } +} + /// A crate-local version of `std::str::FromStr` that can be implemented for /// foreign types pub trait FromStr: Sized { diff --git a/tsconfig.base.json b/tsconfig.base.json index 1157706..707a282 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -20,10 +20,12 @@ "@storyteller/studio-ui": ["studio-ui/src/index.ts"], "@storyteller/studio-ui/button": ["studio-ui/src/lib/button.element.ts"], "@storyteller/studio-ui/icon": ["studio-ui/src/lib/icon.element.ts"], + "@storyteller/studio-ui/progress": ["studio-ui/src/lib/progress.element.ts"], "@storyteller/studio-ui/toolbar": ["studio-ui/src/lib/toolbar.element.ts"], "@storyteller/studio-ui/tree": ["studio-ui/src/lib/tree.element.ts"], "@storyteller/studio-web": ["studio-web/src/index.ts"], "@storyteller/studio-web/canvas-provider": ["studio-web/src/lib/canvas-provider.element.ts"], + "@storyteller/studio-web/loading": ["studio-web/src/lib/loading.element.ts"], "@storyteller/studio-web/scene-hierarchy": ["studio-web/src/lib/scene-hierarchy.element.ts"], "@storyteller/studio-web/studio": ["studio-web/src/lib/studio.element.ts"], "@storyteller/studio-web/transform-toolbar": ["studio-web/src/lib/transform-toolbar.element.ts"],