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 let key = wasm_bindgen::JsValue::from_str("webkitRelativePath");
139
140 if let Ok(value) = js_sys::Reflect::get(&self.file, &key) {
141 if let Some(path_str) = value.as_string() {
142 if !path_str.is_empty() {
143 return std::path::PathBuf::from(path_str);
144 }
145 }
146 }
147
148 std::path::PathBuf::from(self.file.name())
149 }
150
151 fn content_type(&self) -> Option<String> {
152 let type_ = self.file.type_();
153 if type_.is_empty() {
154 None
155 } else {
156 Some(type_)
157 }
158 }
159}
160
161#[derive(Clone)]
163pub(crate) struct WebFileEngine {
164 file_list: FileList,
165}
166
167impl WebFileEngine {
168 pub fn new(file_list: FileList) -> Self {
170 Self { file_list }
171 }
172
173 fn len(&self) -> usize {
174 self.file_list.length() as usize
175 }
176
177 fn get(&self, index: usize) -> Option<File> {
178 self.file_list.item(index as u32)
179 }
180
181 pub fn to_files(&self) -> Vec<FileData> {
182 (0..self.len())
183 .filter_map(|i| self.get(i))
184 .map(|file| {
185 FileData::new(WebFileData {
186 file,
187 reader: FileReader::new().unwrap(),
188 })
189 })
190 .collect()
191 }
192}
193
194pub trait WebFileExt {
196 fn get_web_file(&self) -> Option<web_sys::File>;
198}
199
200impl WebFileExt for FileData {
201 fn get_web_file(&self) -> Option<web_sys::File> {
202 self.inner().downcast_ref::<web_sys::File>().cloned()
203 }
204}