💻

YewでバイナリをDnDしてパースして表示する

2023/04/24に公開

はじめに

こう言う感じのやつをYewで作りたいとします。

先行事例

(先にこちらの2つのリンク先を読んでいただくと分かりやすいです)
こちらのissueではファイルのDnDの方法が記載されています。
https://github.com/rustwasm/wasm-bindgen/issues/1292
こちらの記事では(DnDではなく)入力されたバイナリをパースしています。
https://alpaca.tc/posts/2020-12-21-rust-advent-calendar

2番目の記事がやりたいことにかなり近いのですが、結局do_somethingに何を書けば良いのか、と言う問題がありました。
と言うのは、ファイルをパースしてもそれをどうにかして表示しなければならないわけです。
しかしパースしたものをどうやってonload関数の「外」に持っていくかが問題です。単純にonloadの外にlet mutしておくと所有権絡みでエラーになりますし、そもそもパースした構造体がcopyできないと言う制約もありました(この構造体はライブラリのものです)。

そこで、パースする前のbytes: Vec<u8>を(Reactで言う)setStateして、表示する前にパースしました。

#[function_component(App)]
pub fn app() -> Html {
    let node = use_node_ref();
    let state = use_drop(node.clone());
    let bin = use_state(|| None);

として、

            // ここまでかなり省略
            {if let Some(files) = &*state.files {
                html! {for files.iter().map(|file| {
                    let file_reader = web_sys::FileReader::new().unwrap();
                    file_reader.read_as_array_buffer(&file).unwrap();
                    let bin_cloned = bin.clone(); // moveされるためclone
                    let onload = Closure::wrap(Box::new(move |event: Event| {
                        let file_reader: FileReader = event.target().unwrap().dyn_into().unwrap();
                        let file = file_reader.result().unwrap();
                        let file = js_sys::Uint8Array::new(&file);

                        let mut bytes = vec![0; file.length() as usize];
                        file.copy_to(&mut bytes);
                        bin_cloned.set(Some(bytes));
                    }) as Box<dyn FnMut(_)>);

                    file_reader.set_onload(Some(onload.as_ref().unchecked_ref()));
                    onload.forget();

とします。
後は使う側で、

                    let op_pe = parse(&*bin); // ここで*binをパース
                    if let Some(pe) = op_pe {
                        html! {
                            <>
                                <DosTable signature={pe.header.dos_header.signature} pe_pointer={pe.header.dos_header.pe_pointer}/> // この辺は適当に自分で作ったコンポーネント
                                <CoffTable machine={pe.header.coff_header.machine} number_of_sections={pe.header.coff_header.number_of_sections}/>
                            </>
                        }
                    } else {
                        html! {
                            // 省略
                        }
                    }

として、DnDされたバイナリファイルをパースできました。ここはCustom Hookを使うともっと良くなると思います(が、まだ作りかけなのでご勘弁を。。。)。

おわりに

やはりRust製のライブラリ(ここではバイナリのパーサー)を使うとき、UIまで一気通貫に作れるYewは良いです。

Discussion