1use dioxus_core::AnyhowContext;
2use dioxus_html::{bytes::Bytes, FileData, NativeFileData};
3use futures_channel::oneshot;
4use js_sys::Uint8Array;
5use send_wrapper::SendWrapper;
6use std::{pin::Pin, prelude::rust_2024::Future};
7use wasm_bindgen::{prelude::Closure, JsCast};
8use web_sys::{File, FileList, FileReader};
9
10#[derive(Clone)]
12pub struct WebFileData {
13 file: File,
14 reader: FileReader,
15}
16
17unsafe impl Send for WebFileData {}
18unsafe impl Sync for WebFileData {}
19
20impl WebFileData {
21 pub fn new(file: File, reader: FileReader) -> Self {
23 Self { file, reader }
24 }
25}
26
27impl NativeFileData for WebFileData {
28 fn name(&self) -> String {
29 self.file.name()
30 }
31
32 fn size(&self) -> u64 {
33 self.file.size() as u64
34 }
35
36 fn last_modified(&self) -> u64 {
37 self.file.last_modified() as u64
38 }
39
40 fn read_bytes(
41 &self,
42 ) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::CapturedError>> + 'static>> {
43 let file_reader = self.reader.clone();
44 let file_reader_ = self.reader.clone();
45 let file = self.file.clone();
46 Box::pin(async move {
47 let (rx, tx) = oneshot::channel();
48 let on_load: Closure<dyn FnMut()> = Closure::new({
49 let mut rx = Some(rx);
50 move || {
51 let result = file_reader.result();
52 let _ = rx
53 .take()
54 .expect("multiple files read without refreshing the channel")
55 .send(result);
56 }
57 });
58
59 file_reader_.set_onload(Some(on_load.as_ref().unchecked_ref()));
60 on_load.forget();
61 file_reader_
62 .read_as_array_buffer(&file)
63 .ok()
64 .context("Failed to read file")?;
65
66 let js_val = tx.await?.ok().context("Failed to read file")?;
67 let as_u8_arr = Uint8Array::new(&js_val);
68 let as_u8_vec = as_u8_arr.to_vec().into();
69 Ok(as_u8_vec)
70 })
71 }
72
73 fn read_string(
74 &self,
75 ) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::CapturedError>> + 'static>> {
76 let file_reader = self.reader.clone();
77 let file_reader_ = self.reader.clone();
78 let file = self.file.clone();
79 Box::pin(async move {
80 let (rx, tx) = oneshot::channel();
81 let on_load: Closure<dyn FnMut()> = Closure::new({
82 let mut rx = Some(rx);
83 move || {
84 let result = file_reader.result();
85 let _ = rx
86 .take()
87 .expect("multiple files read without refreshing the channel")
88 .send(result);
89 }
90 });
91
92 file_reader_.set_onload(Some(on_load.as_ref().unchecked_ref()));
93 on_load.forget();
94 file_reader_
95 .read_as_text(&file)
96 .ok()
97 .context("Failed to read file")?;
98
99 let js_val = tx.await?.ok().context("Failed to read file")?;
100 let as_string = js_val.as_string().context("Failed to read file")?;
101 Ok(as_string)
102 })
103 }
104
105 fn byte_stream(
115 &self,
116 ) -> Pin<
117 Box<
118 dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::CapturedError>>
119 + 'static
120 + Send,
121 >,
122 > {
123 let file = self.file.dyn_ref::<web_sys::Blob>().unwrap().clone();
124 Box::pin(SendWrapper::new(futures_util::stream::once(async move {
125 let array_buff = wasm_bindgen_futures::JsFuture::from(file.array_buffer())
126 .await
127 .unwrap();
128 let as_uint_array = array_buff.dyn_into::<Uint8Array>().unwrap();
129 Ok(as_uint_array.to_vec().into())
130 })))
131 }
132
133 fn inner(&self) -> &dyn std::any::Any {
134 &self.file
135 }
136
137 fn path(&self) -> std::path::PathBuf {
138 std::path::PathBuf::from(self.file.name())
139 }
140
141 fn content_type(&self) -> Option<String> {
142 let type_ = self.file.type_();
143 if type_.is_empty() {
144 None
145 } else {
146 Some(type_)
147 }
148 }
149}
150
151#[derive(Clone)]
153pub(crate) struct WebFileEngine {
154 file_list: FileList,
155}
156
157impl WebFileEngine {
158 pub fn new(file_list: FileList) -> Self {
160 Self { file_list }
161 }
162
163 fn len(&self) -> usize {
164 self.file_list.length() as usize
165 }
166
167 fn get(&self, index: usize) -> Option<File> {
168 self.file_list.item(index as u32)
169 }
170
171 pub fn to_files(&self) -> Vec<FileData> {
172 (0..self.len())
173 .filter_map(|i| self.get(i))
174 .map(|file| {
175 FileData::new(WebFileData {
176 file,
177 reader: FileReader::new().unwrap(),
178 })
179 })
180 .collect()
181 }
182}
183
184pub trait WebFileExt {
186 fn get_web_file(&self) -> Option<web_sys::File>;
188}
189
190impl WebFileExt for FileData {
191 fn get_web_file(&self) -> Option<web_sys::File> {
192 self.inner().downcast_ref::<web_sys::File>().cloned()
193 }
194}