Files
OneCAD-Draft/src/sys.rs
T
Hakan SevenandClaude Opus 4.8 e1c5917644 fix(web): paste clipboard text into the TEXT / MText editors
Ctrl+V was globally bound to the entity paste command, so it never pasted
into an open text editor, and even when routed there iced's clipboard read
is a no-op on the web. Route Ctrl+V to the focused editor and, on the web,
read the system clipboard via the browser's async Clipboard API (the
keypress is a user gesture, so the read is permitted):

- MText editor: insert at the caret (web + native).
- TEXT field: append on the web; native lets the text_input paste itself.
- No editor open: falls through to the entity paste command as before.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-20 17:05:02 +03:00

106 lines
3.9 KiB
Rust

// Small platform shims for things the desktop build does natively but the web
// (wasm) build must handle differently or skip.
/// Open a URL in the user's browser. The desktop launches the default handler;
/// the web opens a new tab (the button click is a user gesture, so it isn't
/// caught by the pop-up blocker). Focus of the opened page is left to the
/// OS / browser.
#[cfg(not(target_arch = "wasm32"))]
pub fn open_url(url: &str) {
let _ = open::that(url);
}
#[cfg(target_arch = "wasm32")]
pub fn open_url(url: &str) {
if let Some(window) = web_sys::window() {
let _ = window.open_with_url_and_target(url, "_blank");
}
}
/// Web: read text from the system clipboard via the async Clipboard API.
/// iced's own `clipboard::read` is a no-op on the web (the browser clipboard is
/// async + permission-gated), so the editor paste paths use this instead. The
/// Ctrl+V keypress that drives it is a user gesture, so the read is permitted.
/// Returns `None` when denied, empty, or unsupported.
#[cfg(target_arch = "wasm32")]
pub async fn read_clipboard_text() -> Option<String> {
let clipboard = web_sys::window()?.navigator().clipboard();
let value = wasm_bindgen_futures::JsFuture::from(clipboard.read_text())
.await
.ok()?;
value.as_string()
}
/// Turn an `rfd` file handle into a `PathBuf` the rest of the app keys on.
///
/// Desktop returns the real filesystem path. The browser has no path, so we
/// synthesize one from the file name — enough for the app to compile and track
/// the document name; actual byte I/O on the web reads the handle directly
/// (a follow-up).
#[cfg(not(target_arch = "wasm32"))]
pub fn handle_path(h: &rfd::FileHandle) -> std::path::PathBuf {
h.path().to_path_buf()
}
#[cfg(target_arch = "wasm32")]
pub fn handle_path(h: &rfd::FileHandle) -> std::path::PathBuf {
std::path::PathBuf::from(h.file_name())
}
/// Trigger a browser download of `bytes` as `name`. Builds a Blob, points a
/// hidden `<a download>` at it and clicks it programmatically — because this
/// runs inside the Save button's click (a user gesture), the file downloads
/// immediately with no extra "click to download" link. Web only.
#[cfg(target_arch = "wasm32")]
pub fn download_bytes(name: &str, bytes: &[u8]) {
use wasm_bindgen::JsCast;
let Some(window) = web_sys::window() else { return };
let Some(document) = window.document() else { return };
let array = js_sys::Uint8Array::from(bytes);
let parts = js_sys::Array::new();
parts.push(&array.buffer());
let Ok(blob) = web_sys::Blob::new_with_u8_array_sequence(&parts) else {
return;
};
let Ok(url) = web_sys::Url::create_object_url_with_blob(&blob) else {
return;
};
if let Ok(el) = document.create_element("a") {
let a: web_sys::HtmlAnchorElement = el.unchecked_into();
a.set_href(&url);
a.set_download(name);
a.click();
}
let _ = web_sys::Url::revoke_object_url(&url);
}
/// Short platform string for bug reports: OS + architecture on the desktop,
/// the browser user-agent on the web.
#[cfg(not(target_arch = "wasm32"))]
pub fn platform_info() -> String {
format!("{} {}", std::env::consts::OS, std::env::consts::ARCH)
}
#[cfg(target_arch = "wasm32")]
pub fn platform_info() -> String {
web_sys::window()
.and_then(|w| w.navigator().user_agent().ok())
.map(|ua| format!("Web — {ua}"))
.unwrap_or_else(|| "Web (wasm)".to_string())
}
/// Percent-encode `s` for use in a URL query value (e.g. a GitHub issue
/// `?body=`). Encodes everything outside the unreserved set.
pub fn percent_encode(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for &b in s.as_bytes() {
match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
out.push(b as char)
}
_ => out.push_str(&format!("%{b:02X}")),
}
}
out
}