1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use std::any::Any;

use dioxus_html::FileEngine;
use futures_channel::oneshot;
use js_sys::Uint8Array;
use wasm_bindgen::{prelude::Closure, JsCast};
use web_sys::{File, FileList, FileReader};

pub(crate) struct WebFileEngine {
    file_reader: FileReader,
    file_list: FileList,
}

impl WebFileEngine {
    pub fn new(file_list: FileList) -> Option<Self> {
        Some(Self {
            file_list,
            file_reader: FileReader::new().ok()?,
        })
    }

    fn len(&self) -> usize {
        self.file_list.length() as usize
    }

    fn get(&self, index: usize) -> Option<File> {
        self.file_list.item(index as u32)
    }

    fn find(&self, name: &str) -> Option<File> {
        (0..self.len())
            .filter_map(|i| self.get(i))
            .find(|f| f.name() == name)
    }
}

#[async_trait::async_trait(?Send)]
impl FileEngine for WebFileEngine {
    fn files(&self) -> Vec<String> {
        (0..self.len())
            .filter_map(|i| self.get(i).map(|f| f.name()))
            .collect()
    }

    // read a file to bytes
    async fn read_file(&self, file: &str) -> Option<Vec<u8>> {
        let file = self.find(file)?;

        let file_reader = self.file_reader.clone();
        let (rx, tx) = oneshot::channel();
        let on_load: Closure<dyn FnMut()> = Closure::new({
            let mut rx = Some(rx);
            move || {
                let result = file_reader.result();
                let _ = rx
                    .take()
                    .expect("multiple files read without refreshing the channel")
                    .send(result);
            }
        });

        self.file_reader
            .set_onload(Some(on_load.as_ref().unchecked_ref()));
        on_load.forget();
        self.file_reader.read_as_array_buffer(&file).ok()?;

        if let Ok(Ok(js_val)) = tx.await {
            let as_u8_arr = Uint8Array::new(&js_val);
            let as_u8_vec = as_u8_arr.to_vec();

            Some(as_u8_vec)
        } else {
            None
        }
    }

    // read a file to string
    async fn read_file_to_string(&self, file: &str) -> Option<String> {
        let file = self.find(file)?;

        let file_reader = self.file_reader.clone();
        let (rx, tx) = oneshot::channel();
        let on_load: Closure<dyn FnMut()> = Closure::new({
            let mut rx = Some(rx);
            move || {
                let result = file_reader.result();
                let _ = rx
                    .take()
                    .expect("multiple files read without refreshing the channel")
                    .send(result);
            }
        });

        self.file_reader
            .set_onload(Some(on_load.as_ref().unchecked_ref()));
        on_load.forget();
        self.file_reader.read_as_text(&file).ok()?;

        if let Ok(Ok(js_val)) = tx.await {
            js_val.as_string()
        } else {
            None
        }
    }

    async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
        let file = self.find(file)?;
        Some(Box::new(file))
    }
}

/// Helper trait for WebFileEngine
#[async_trait::async_trait(?Send)]
pub trait WebFileEngineExt {
    /// returns web_sys::File
    async fn get_web_file(&self, file: &str) -> Option<web_sys::File>;
}

#[async_trait::async_trait(?Send)]
impl WebFileEngineExt for std::sync::Arc<dyn FileEngine> {
    async fn get_web_file(&self, file: &str) -> Option<web_sys::File> {
        let native_file = self.get_native_file(file).await?;
        let ret = native_file.downcast::<web_sys::File>().ok()?;
        Some(*ret)
    }
}