mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-27 12:16:54 -06:00
Merge pull request #36 from harrisonoconnorhover/fix/rich-table-clipboard
Fix copying selected cells as rich HTML tables
This commit is contained in:
@@ -26,4 +26,4 @@ rfd = { workspace = true }
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
web-sys = { version = "0.3", features = ["Window", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "HtmlAnchorElement", "Blob", "BlobPropertyBag", "Url", "Location", "CssStyleDeclaration"] }
|
||||
web-sys = { version = "0.3", features = ["Window", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "HtmlAnchorElement", "Blob", "BlobPropertyBag", "Url", "Location", "CssStyleDeclaration", "Clipboard", "ClipboardItem", "Navigator"] }
|
||||
|
||||
@@ -93,6 +93,7 @@ impl eframe::App for WebShell {
|
||||
fn services(inbox: Inbox, ctx: egui::Context) -> Services {
|
||||
let open_inbox = inbox.clone();
|
||||
Services {
|
||||
copy_html: Some(Box::new(copy_html)),
|
||||
open_async: Some(Box::new(move || {
|
||||
let inbox = open_inbox.clone();
|
||||
let ctx = ctx.clone();
|
||||
@@ -124,6 +125,39 @@ fn services(inbox: Inbox, ctx: egui::Context) -> Services {
|
||||
}
|
||||
}
|
||||
|
||||
/// Start inside the copy gesture; deferring `write` can lose browser user activation.
|
||||
fn copy_html(html: &str, text: &str) -> Result<(), String> {
|
||||
let js = |e: wasm_bindgen::JsValue| format!("{e:?}");
|
||||
let window = web_sys::window().ok_or("no window")?;
|
||||
let clipboard = window.navigator().clipboard();
|
||||
if clipboard.is_undefined() || clipboard.is_null() {
|
||||
return Err("browser clipboard is unavailable (a secure context is required)".into());
|
||||
}
|
||||
if !js_sys::Reflect::get(&clipboard, &"write".into()).map_err(js)?.is_function() {
|
||||
return Err("browser does not support rich clipboard writes".into());
|
||||
}
|
||||
let data = js_sys::Object::new();
|
||||
for (mime, value) in [("text/html", html), ("text/plain", text)] {
|
||||
let opts = web_sys::BlobPropertyBag::new();
|
||||
opts.set_type(mime);
|
||||
let parts = js_sys::Array::of1(&wasm_bindgen::JsValue::from_str(value));
|
||||
let blob = web_sys::Blob::new_with_str_sequence_and_options(&parts, &opts).map_err(js)?;
|
||||
js_sys::Reflect::set(&data, &mime.into(), &blob).map_err(js)?;
|
||||
}
|
||||
let item = web_sys::ClipboardItem::new_with_record_from_str_to_blob_promise(&data).map_err(js)?;
|
||||
let promise = clipboard.write(&js_sys::Array::of1(&item));
|
||||
let text = text.to_owned();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
if let Err(e) = wasm_bindgen_futures::JsFuture::from(promise).await {
|
||||
log::warn!("Rich clipboard write failed; trying plain text: {e:?}");
|
||||
if let Err(e) = wasm_bindgen_futures::JsFuture::from(clipboard.write_text(&text)).await {
|
||||
log::error!("Plain-text clipboard fallback also failed: {e:?}");
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Triggers a browser download of `bytes`.
|
||||
fn download(name: &str, bytes: &[u8]) -> Result<(), String> {
|
||||
let js = |e: wasm_bindgen::JsValue| format!("{e:?}");
|
||||
|
||||
@@ -25,6 +25,10 @@ winresource = "0.1"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
rfd = { workspace = true }
|
||||
arboard = { workspace = true }
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||
arboard = { workspace = true, features = ["wayland-data-control"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
muda = { version = "0.21", default-features = false }
|
||||
|
||||
@@ -107,7 +107,15 @@ fn save_prefs(app: &SheetApp) {
|
||||
}
|
||||
|
||||
fn services() -> Services {
|
||||
// Keep the owner alive: X11/Wayland serve clipboard data from this handle.
|
||||
let mut clipboard: Option<arboard::Clipboard> = None;
|
||||
Services {
|
||||
copy_html: Some(Box::new(move |html, text| {
|
||||
if clipboard.is_none() {
|
||||
clipboard = Some(arboard::Clipboard::new().map_err(|e| e.to_string())?);
|
||||
}
|
||||
clipboard.as_mut().ok_or("clipboard unavailable")?.set_html(html, Some(text)).map_err(|e| e.to_string())
|
||||
})),
|
||||
pick_open: Some(Box::new(|| {
|
||||
rfd::FileDialog::new()
|
||||
.add_filter("Spreadsheets", &["xlsx", "xlsm", "xlsb", "ods", "csv", "tsv", "txt", "json"])
|
||||
|
||||
@@ -294,11 +294,7 @@ impl NativeMenu {
|
||||
let name = if name == "note" { "comment" } else { name };
|
||||
app.open_dialog(name, params);
|
||||
} else if id == "edit.copy" || id == "edit.cut" {
|
||||
if let Ok(r) = app.run(&id, json!({}))
|
||||
&& let Some(t) = r.get("text").and_then(Value::as_str)
|
||||
{
|
||||
ctx.copy_text(t.to_string());
|
||||
}
|
||||
app.copy_to_clipboard(ctx, &id);
|
||||
} else {
|
||||
app.run_or_alert(&id, json!({}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user