T44d Web · Gallery builds for wasm32: one story per section by URL, themed by the host
The gallery's entry runs natively or, on wasm32, from a wasm_bindgen start through gpui_web, single-threaded with WebGL2 fallback. ?page, ?story and ?theme choose what shows; an unknown one fails in the page and the log. set_theme switches the mode later. Pictures load from ELY_GALLERY_ASSETS. scripts/stories.py writes examples/gallery/stories.json from the pages.
This commit is contained in:
83 files changed
+1000
-455
No files matched your search
@@ -0,0 +1,115 @@
|
||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use ely_gpui_component::Assets;
|
||||
use gpui::{Application, AsyncApp, SharedString, WindowHandle};
|
||||
use gpui_web::{CanvasFontFallback, WebBackendPreference, WebPlatform};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::UrlSearchParams;
|
||||
|
||||
use crate::{Start, choose, launch, pages, shell::Choice, shell::Gallery};
|
||||
|
||||
thread_local! {
|
||||
/// The running app and its window, for the host page's calls.
|
||||
static HOST: RefCell<Option<(AsyncApp, WindowHandle<Gallery>)>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
fn js(error: JsValue) -> anyhow::Error {
|
||||
anyhow!("{error:?}")
|
||||
}
|
||||
|
||||
fn choice(theme: &str) -> Result<Choice> {
|
||||
match theme {
|
||||
"light" => Ok(Choice::Light),
|
||||
"dark" => Ok(Choice::Dark),
|
||||
other => bail!("theme is light or dark, not {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// `?page=<slug>&story=<title or slug>&theme=light|dark`, each optional; a story needs its page.
|
||||
fn read(search: &str) -> Result<Start> {
|
||||
let assets = asset!("");
|
||||
if !(assets.starts_with("https://") || assets.starts_with("http://")) || !assets.ends_with('/')
|
||||
{
|
||||
bail!("ELY_GALLERY_ASSETS is an address ending in /, not {assets}");
|
||||
}
|
||||
let mut start = Start {
|
||||
page: 0,
|
||||
story: None,
|
||||
choice: Choice::Light,
|
||||
narrow: None,
|
||||
failure: None,
|
||||
};
|
||||
let params = UrlSearchParams::new_with_str(search).map_err(js)?;
|
||||
for key in js_sys::try_iter(¶ms.keys())
|
||||
.map_err(js)?
|
||||
.context("the address's parameters do not iterate")?
|
||||
{
|
||||
let key = key
|
||||
.map_err(js)?
|
||||
.as_string()
|
||||
.context("a parameter without a name")?;
|
||||
let value = params.get(&key).context("a parameter without a value")?;
|
||||
match key.as_str() {
|
||||
"page" => {
|
||||
start.page = pages::find(&value).with_context(|| format!("no page {value}"))?
|
||||
}
|
||||
"story" => start.story = Some(value.into()),
|
||||
"theme" => start.choice = choice(&value)?,
|
||||
other => bail!("unknown parameter {other}; the gallery reads page, story and theme"),
|
||||
}
|
||||
}
|
||||
if start.story.is_some() && params.get("page").is_none() {
|
||||
bail!("a story needs its page: ?page=<slug>&story=<title>");
|
||||
}
|
||||
Ok(start)
|
||||
}
|
||||
|
||||
/// The gallery in a browser: one window over the page's canvas.
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn start() {
|
||||
gpui_platform::web_init();
|
||||
let search = web_sys::window()
|
||||
.expect("a browser window")
|
||||
.location()
|
||||
.search()
|
||||
.expect("the page's address");
|
||||
let start = read(&search).unwrap_or_else(|error| {
|
||||
log::error!("gallery: {search} -> {error:#}");
|
||||
Start {
|
||||
page: 0,
|
||||
story: None,
|
||||
choice: Choice::Light,
|
||||
narrow: None,
|
||||
failure: Some(SharedString::from(format!("{search}: {error:#}"))),
|
||||
}
|
||||
});
|
||||
// One thread: a static site sends no cross-origin isolation, so there is no shared memory.
|
||||
let platform = Rc::new(WebPlatform::new_with_backend_and_font_fallback(
|
||||
false,
|
||||
WebBackendPreference::Auto,
|
||||
CanvasFontFallback::EmojiAndCjk,
|
||||
));
|
||||
let http = Arc::new(platform.fetch_http_client());
|
||||
Application::with_platform(platform)
|
||||
.with_http_client(http)
|
||||
.with_assets(Assets)
|
||||
.run(move |cx| {
|
||||
let window = launch(start, cx);
|
||||
HOST.with(|host| *host.borrow_mut() = Some((cx.to_async(), window)));
|
||||
});
|
||||
}
|
||||
|
||||
/// Switches Ely's mode from the host page: `light` or `dark`.
|
||||
#[wasm_bindgen]
|
||||
pub fn set_theme(theme: &str) -> Result<(), JsError> {
|
||||
let choice = choice(theme).map_err(|error| JsError::new(&format!("{error:#}")))?;
|
||||
HOST.with(|host| {
|
||||
let host = host.borrow();
|
||||
let (app, window) = host
|
||||
.as_ref()
|
||||
.ok_or_else(|| JsError::new("the gallery's window is not open yet"))?;
|
||||
app.update(|cx| choose(*window, choice, cx));
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user