如何在Rust WASM中选择文件为字节或文本?

w8biq8rn  于 2023-01-21  发布在  其他
关注(0)|答案(2)|浏览(122)

我试图通过单击按钮来触发上传文件的Vec<u8>String(或者更理想的是Blob ObjectURL)。
我猜这将需要在DOM中的某个地方有一个不可见的<input>,但我不知道如何利用web_sys和/或gloo来获取内容或Blob对象URL。

pgvzfuti

pgvzfuti1#

一个js触发的输入可能不起作用,因为许多浏览器不允许你从JS触发一个文件输入,这是有原因的。如果你觉得它很难看,你可以用use label s来隐藏输入。除此之外,你需要通过HtmlInputElementfiles API来操作自己。这是很痛苦的:

use js_sys::{Object, Reflect, Uint8Array};
use wasm_bindgen::{prelude::*, JsCast};
use wasm_bindgen_futures::JsFuture;
use web_sys::*;

#[wasm_bindgen(start)]
pub fn init() {
    // Just some setup for the example
    std::panic::set_hook(Box::new(console_error_panic_hook::hook));
    let window = window().unwrap();
    let document = window.document().unwrap();
    let body = document.body().unwrap();
    while let Some(child) = body.first_child() {
        body.remove_child(&child).unwrap();
    }
    // Create the actual input element
    let input = document
        .create_element("input")
        .expect_throw("Create input")
        .dyn_into::<HtmlInputElement>()
        .unwrap();
    input
        .set_attribute("type", "file")
        .expect_throw("Set input type file");

    let recv_file = {
        let input = input.clone();
        Closure::<dyn FnMut()>::wrap(Box::new(move || {
            let input = input.clone();
            wasm_bindgen_futures::spawn_local(async move {
                file_callback(input.files()).await;
            })
        }))
    };
    input
        .add_event_listener_with_callback("change", recv_file.as_ref().dyn_ref().unwrap())
        .expect_throw("Listen for file upload");
    recv_file.forget(); // TODO: this leaks. I forgot how to get around that.
    body.append_child(&input).unwrap();
}

async fn file_callback(files: Option<FileList>) {
    let files = match files {
        Some(files) => files,
        None => return,
    };
    for i in 0..files.length() {
        let file = match files.item(i) {
            Some(file) => file,
            None => continue,
        };
        console::log_2(&"File:".into(), &file.name().into());
        let reader = file
            .stream()
            .get_reader()
            .dyn_into::<ReadableStreamDefaultReader>()
            .expect_throw("Reader is reader");
        let mut data = Vec::new();
        loop {
            let chunk = JsFuture::from(reader.read())
                .await
                .expect_throw("Read")
                .dyn_into::<Object>()
                .unwrap();
            // ReadableStreamReadResult is somehow wrong. So go by hand. Might be a web-sys bug.
            let done = Reflect::get(&chunk, &"done".into()).expect_throw("Get done");
            if done.is_truthy() {
                break;
            }
            let chunk = Reflect::get(&chunk, &"value".into())
                .expect_throw("Get chunk")
                .dyn_into::<Uint8Array>()
                .expect_throw("bytes are bytes");
            let data_len = data.len();
            data.resize(data_len + chunk.length() as usize, 255);
            chunk.copy_to(&mut data[data_len..]);
        }
        console::log_2(
            &"Got data".into(),
            &String::from_utf8_lossy(&data).into_owned().into(),
        );
    }
}

(If如果你对代码有疑问,可以问,但是太多了,无法详细解释。)
另外,您需要在web-sys上实现以下功能:

[dependencies.web-sys]
version = "0.3.60"
features = ["Window", "Navigator", "console", "Document", "HtmlInputElement", "Event", "EventTarget", "FileList", "File", "Blob", "ReadableStream", "ReadableStreamDefaultReader", "ReadableStreamReadResult"]
rqdpfwrv

rqdpfwrv2#

多亏了凯撒,我最终得到了这个代码,可以与dominator一起用作Dom板条箱。

pub fn upload_file_input(mimes: &str, mutable: Mutable<Vec<u8>>) -> Dom {
    input(|i| {
        i.class("file-input")
            .prop("type", "file")
            .prop("accept", mimes)
            .apply(|el| {
                let element: HtmlInputElement = el.__internal_element();

                let recv_file = {
                    let input = element.clone();
                    Closure::<dyn FnMut()>::wrap(Box::new(move || {
                        let input = input.clone();
                        let mutable = mutable.clone();
                        wasm_bindgen_futures::spawn_local(async move {
                            file_callback(input.files(), mutable.clone()).await;
                        })
                    }))
                };

                element
                    .add_event_listener_with_callback(
                        "change",
                        recv_file.as_ref().dyn_ref().unwrap(),
                    )
                    .expect("Listen for file upload");
                recv_file.forget();
                el
            })
    })
}

async fn file_callback(files: Option<FileList>, mutable: Mutable<Vec<u8>>) {
    let files = match files {
        Some(files) => files,
        None => return,
    };
    for i in 0..files.length() {
        let file = match files.item(i) {
            Some(file) => file,
            None => continue,
        };
        // gloo::console::console_dbg!("File:", &file.name());
        let reader = file
            .stream()
            .get_reader()
            .dyn_into::<ReadableStreamDefaultReader>()
            .expect("Reader is reader");
        let mut data = Vec::new();
        loop {
            let chunk = JsFuture::from(reader.read())
                .await
                .expect("Read")
                .dyn_into::<Object>()
                .unwrap();
            // ReadableStreamReadResult is somehow wrong. So go by hand. Might be a web-sys bug.
            let done = Reflect::get(&chunk, &"done".into()).expect("Get done");
            if done.is_truthy() {
                break;
            }
            let chunk = Reflect::get(&chunk, &"value".into())
                .expect("Get chunk")
                .dyn_into::<Uint8Array>()
                .expect("bytes are bytes");
            let data_len = data.len();
            data.resize(data_len + chunk.length() as usize, 255);
            chunk.copy_to(&mut data[data_len..]);
        }
        mutable.set(data);
        // gloo::console::console_dbg!(
        //     "Got data",
        //     &String::from_utf8_lossy(&data).into_owned(),
        // );
    }
}

相关问题