Files
brittle/brittle-ui/src/pdf_viewer.rs

63 lines
2.2 KiB
Rust

//! PDF viewer tab: embeds the Tauri-served PDF viewer in an iframe.
//!
//! The custom `brittle://` URI scheme serves:
//! - `brittle://app/viewer?ref_id=<uuid>` — the viewer HTML page (PDF.js)
//! - `brittle://app/pdf?ref_id=<uuid>` — the raw PDF bytes
//!
//! Using an `<iframe>` keeps the viewer alive when the tab is hidden (via
//! `display:none`), so scrolling position and zoom are preserved across
//! tab switches.
use brittle_keymap::actions;
use leptos::prelude::*;
use wasm_bindgen::JsValue;
/// Renders the PDF viewer for a single reference.
///
/// `ref_id` must be the UUID string of a reference that has an attached PDF.
/// `is_active` indicates whether this is the currently visible PDF tab; when
/// `true`, keymap actions for PDF page navigation are forwarded into the iframe.
#[component]
pub fn PdfViewer(
ref_id: String,
is_active: Signal<bool>,
initial_zoom: Option<f64>,
initial_scroll_top: Option<f64>,
) -> impl IntoView {
let mut url = format!("brittle://app/viewer?ref_id={ref_id}");
if let Some(z) = initial_zoom { url.push_str(&format!("&zoom={z}")); }
if let Some(s) = initial_scroll_top { url.push_str(&format!("&scroll_top={s}")); }
let iframe_ref = NodeRef::<leptos::html::Iframe>::new();
let keymap_action = use_context::<crate::KeymapAction>()
.expect("KeymapAction context missing")
.0;
Effect::new(move |_| {
let Some(ev) = keymap_action.get() else { return };
if !is_active.get_untracked() { return }
let cmd = match ev.name.as_str() {
actions::PDF_PAGE_NEXT => actions::PDF_PAGE_NEXT,
actions::PDF_PAGE_PREV => actions::PDF_PAGE_PREV,
_ => return,
};
let Some(iframe) = iframe_ref.get() else { return };
if let Some(win) = iframe.content_window() {
let _ = win.post_message(&JsValue::from_str(cmd), "*");
}
});
view! {
<iframe
node_ref=iframe_ref
class="pdf-frame"
src=url
// Intentionally no `sandbox` attribute — the brittle:// protocol
// and PDF.js require unrestricted access within the webview.
/>
}
}