mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-09 00:09:55 +00:00
Progress bar for scene loading (#71)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
<!-- TODO: Animated spinner or something -- talk to our designers -->
|
||||
<div class="loading">
|
||||
<h1>Loading</h1>
|
||||
</div>
|
||||
<sts-loading></sts-loading>
|
||||
` : nothing}
|
||||
</sts-canvas-provider>
|
||||
`
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
<!-- TODO: Animated spinner or something -- talk to our designers -->
|
||||
<div class="loading">
|
||||
<h1>Loading</h1>
|
||||
</div>
|
||||
<sts-loading></sts-loading>
|
||||
` : nothing}
|
||||
</sts-canvas-provider>
|
||||
`;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
:host {
|
||||
display: block;
|
||||
height: 0.5rem;
|
||||
background: #713837;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fill {
|
||||
height: 100%;
|
||||
background: #E66462;
|
||||
}
|
||||
|
||||
.animated {
|
||||
// TODO
|
||||
}
|
||||
@@ -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<this>): 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`
|
||||
<div class="animated" part="animated"></div>
|
||||
` : html`
|
||||
<div class="fill"
|
||||
part="fill"
|
||||
style=${styleMap({
|
||||
width: `${(this.value / (this.max - this.min)) * 100}%`
|
||||
})}
|
||||
></div>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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`
|
||||
<h1 id="loading-label">Loading</h1>
|
||||
|
||||
<sts-progress class="progress"
|
||||
aria-labelledby="loading-label"
|
||||
?indeterminate=${this.queueMax === 0}
|
||||
.max=${this.queueMax}
|
||||
.value=${this.queueLoaded}
|
||||
></sts-progress>
|
||||
`;
|
||||
}
|
||||
+38
-1
@@ -96,6 +96,7 @@ impl Plugin for ScenePlugin {
|
||||
wasm::notify_state_changes
|
||||
.map(bevy::utils::error)
|
||||
.run_if(resource_changed::<State<SceneState>>()),
|
||||
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<String> = 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<Option<usize>>,
|
||||
r_loading: Res<LoadingQueue>,
|
||||
q_window: Query<&Window, With<PrimaryWindow>>,
|
||||
) -> 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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +147,12 @@ impl AsJsValue for u32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsJsValue for usize {
|
||||
fn as_js(&self) -> Result<JsValue, String> {
|
||||
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 {
|
||||
|
||||
@@ -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"],
|
||||
|
||||
Reference in New Issue
Block a user