Files
pdfcraft/apps/pdfcraft-web/src/main.rs
T
2026-10-09 18:52:13 -04:00

87 lines
3.9 KiB
Rust

//! PdfCraft in the browser. Build: `cd apps/pdfcraft-web && trunk build --release`
//! (trunk generates the JS loader; no handwritten JS — plan/execution-plan.md §1).
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#[cfg(target_arch = "wasm32")]
fn main() {
use eframe::wasm_bindgen::JsCast;
use pdfcraft_ui_egui::PdfCraftApp;
let options = eframe::WebOptions { renderer: eframe::Renderer::Glow, ..Default::default() };
wasm_bindgen_futures::spawn_local(async move {
let Some(canvas) = web_sys::window()
.and_then(|w| w.document())
.and_then(|d| d.get_element_by_id("pdfcraft"))
.and_then(|e| e.dyn_into::<web_sys::HtmlCanvasElement>().ok())
else {
return;
};
let started = eframe::WebRunner::new()
.start(
canvas,
options,
Box::new(|cc| {
let mut app = PdfCraftApp::new();
// "printcraft": settings saved under the app's former name, PrintCraft.
if let Some(json) = cc.storage.and_then(|s| s.get_string("pdfcraft").or_else(|| s.get_string("printcraft"))) {
app.restore(&json);
}
// `?file=<url>` opens a PDF from a URL (same-origin or CORS-enabled).
if let Some(url) = query_param("file") {
let inbox = app.startup_inbox.clone();
let failed = app.failed_inbox.clone();
let ctx = cc.egui_ctx.clone();
wasm_bindgen_futures::spawn_local(async move {
let name = url.rsplit('/').next().unwrap_or("document.pdf").split('?').next().unwrap_or("document.pdf").to_string();
match fetch_bytes(&url).await {
Ok(bytes) => {
if let Ok(mut q) = inbox.lock() {
q.push((name, bytes));
}
}
Err(e) => {
eframe::web_sys::console::error_1(&format!("PdfCraft: could not fetch {url}: {e}").into());
// Shown in the app too, not only in the console (#173).
if let Ok(mut q) = failed.lock() {
q.push((name, e));
}
}
}
ctx.request_repaint();
});
}
Ok(Box::new(app))
}),
)
.await;
if let Err(e) = started {
eframe::web_sys::console::error_1(&e);
}
});
}
#[cfg(target_arch = "wasm32")]
fn query_param(key: &str) -> Option<String> {
let search = web_sys::window()?.location().search().ok()?;
web_sys::UrlSearchParams::new_with_str(&search).ok()?.get(key)
}
#[cfg(target_arch = "wasm32")]
async fn fetch_bytes(url: &str) -> Result<Vec<u8>, String> {
use eframe::wasm_bindgen::JsCast;
let window = web_sys::window().ok_or("no window")?;
let resp = wasm_bindgen_futures::JsFuture::from(window.fetch_with_str(url)).await.map_err(|e| format!("{e:?}"))?;
let resp: web_sys::Response = resp.dyn_into().map_err(|_| "not a response")?;
if !resp.ok() {
return Err(format!("HTTP {}", resp.status()));
}
let buf = wasm_bindgen_futures::JsFuture::from(resp.array_buffer().map_err(|e| format!("{e:?}"))?).await.map_err(|e| format!("{e:?}"))?;
Ok(js_sys::Uint8Array::new(&buf).to_vec())
}
#[cfg(not(target_arch = "wasm32"))]
fn main() {
eprintln!("pdfcraft-web targets wasm32: run `trunk serve` or `trunk build --release` in apps/pdfcraft-web");
}