cranpose 0.1.158

Cranpose runtime and UI facade
Documentation
use std::{
    cell::RefCell,
    rc::{Rc, Weak},
};

use cranpose_app_shell::AppShell;
use cranpose_render_wgpu::WgpuRenderer;
use cranpose_ui::clipboard_session::PlatformClipboard;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::{JsFuture, spawn_local};

pub(crate) fn install(app: &Rc<RefCell<AppShell<WgpuRenderer>>>, request_frame: Rc<dyn Fn()>) {
    let clipboard = Rc::new(WebClipboard {
        app: Rc::downgrade(app),
        request_frame,
    });
    app.borrow().app_context().enter(move || {
        cranpose_ui::clipboard_session::set_platform_clipboard(clipboard);
    });
}

struct WebClipboard {
    app: Weak<RefCell<AppShell<WgpuRenderer>>>,
    request_frame: Rc<dyn Fn()>,
}

impl WebClipboard {
    fn dom_clipboard() -> Option<web_sys::Clipboard> {
        let clipboard = web_sys::window()?.navigator().clipboard();
        (!JsValue::from(clipboard.clone()).is_undefined()).then_some(clipboard)
    }
}

impl PlatformClipboard for WebClipboard {
    fn write_text(&self, text: &str) {
        let Some(clipboard) = Self::dom_clipboard() else {
            log::warn!("browser clipboard unavailable (insecure origin?); copy stayed in-page");
            return;
        };
        let promise = clipboard.write_text(text);
        spawn_local(async move {
            if let Err(error) = JsFuture::from(promise).await {
                log::warn!("browser clipboard write failed: {error:?}");
            }
        });
    }

    fn read_text(&self) -> Option<String> {
        None
    }

    fn can_request_paste(&self) -> bool {
        Self::dom_clipboard().is_some()
    }

    fn request_paste(&self) -> bool {
        let Some(clipboard) = Self::dom_clipboard() else {
            return false;
        };
        let promise = clipboard.read_text();
        let app = self.app.clone();
        let request_frame = Rc::clone(&self.request_frame);
        spawn_local(async move {
            let text = match JsFuture::from(promise).await {
                Ok(value) => value.as_string().unwrap_or_default(),
                Err(error) => {
                    log::info!("browser clipboard read declined: {error:?}");
                    return;
                }
            };
            if text.is_empty() {
                return;
            }
            let Some(shell) = app.upgrade() else {
                return;
            };
            let pasted = match shell.try_borrow_mut() {
                Ok(mut shell) => {
                    shell.on_paste(&text);
                    true
                }
                Err(_) => false,
            };
            if pasted {
                request_frame();
            }
        });
        true
    }
}