From c65bd0f731ea892ede4bdebcbf716c55d3b5061b Mon Sep 17 00:00:00 2001 From: Kamil Jarosz Date: Tue, 30 Apr 2024 22:18:15 +0200 Subject: [PATCH] web: Download the file on save dialog --- web/Cargo.toml | 3 +- web/src/ui.rs | 114 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 95 insertions(+), 22 deletions(-) diff --git a/web/Cargo.toml b/web/Cargo.toml index bdbb20a278924..c8de1fb97a14f 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -72,7 +72,8 @@ features = [ "ChannelMergerNode", "ChannelSplitterNode", "ClipboardEvent", "DataTransfer", "Element", "Event", "EventTarget", "GainNode", "Headers", "HtmlCanvasElement", "HtmlDocument", "HtmlElement", "HtmlFormElement", "HtmlInputElement", "HtmlTextAreaElement", "KeyboardEvent", "Location", "PointerEvent", - "Request", "RequestInit", "Response", "Storage", "WheelEvent", "Window", "ReadableStream", "RequestCredentials" + "Request", "RequestInit", "Response", "Storage", "WheelEvent", "Window", "ReadableStream", "RequestCredentials", + "Url", ] [package.metadata.cargo-machete] diff --git a/web/src/ui.rs b/web/src/ui.rs index 186ae31255fdd..28ce9bf26bd23 100644 --- a/web/src/ui.rs +++ b/web/src/ui.rs @@ -8,11 +8,15 @@ use ruffle_core::backend::ui::{ }; use ruffle_web_common::JsResult; use std::borrow::Cow; +use std::sync::{Mutex, MutexGuard}; use url::Url; use wasm_bindgen::JsCast; -use web_sys::{HtmlCanvasElement, HtmlDocument, HtmlTextAreaElement}; +use web_sys::{ + Blob, HtmlCanvasElement, HtmlDocument, HtmlElement, HtmlTextAreaElement, Url as JsUrl, +}; use chrono::{DateTime, Utc}; +use js_sys::Uint8Array; #[allow(dead_code)] #[derive(Debug)] @@ -116,6 +120,91 @@ impl FileDialogResult for WebFileDialogResult { fn refresh(&mut self) {} } +pub struct WebDownloadFileData { + file_name: String, + contents: Vec, + downloaded: bool, +} + +pub struct WebDownloadFileDialogResult { + data: Mutex, +} + +impl WebDownloadFileDialogResult { + fn new(file_name: String) -> WebDownloadFileDialogResult { + Self { + data: Mutex::new(WebDownloadFileData { + file_name, + contents: Vec::new(), + downloaded: false, + }), + } + } + + fn data(&self) -> MutexGuard<'_, WebDownloadFileData> { + self.data.lock().expect("non-poisoned data") + } + + fn download(mut data: MutexGuard) -> Option<()> { + let array = Uint8Array::from(&data.contents[..]); + let blob = Blob::new_with_u8_array_sequence(&array).ok()?; + let window = web_sys::window()?; + let document = window.document()?; + let a = document.create_element("a").ok()?; + + let url = JsUrl::create_object_url_with_blob(&blob).ok()?; + a.set_attribute("href", &url).ok()?; + a.set_attribute("download", &data.file_name).ok()?; + a.dyn_into::().ok()?.click(); + data.downloaded = true; + Some(()) + } +} + +impl FileDialogResult for WebDownloadFileDialogResult { + fn is_cancelled(&self) -> bool { + self.data().downloaded + } + + fn creation_time(&self) -> Option> { + None + } + + fn modification_time(&self) -> Option> { + None + } + + fn file_name(&self) -> Option { + Some(self.data().file_name.clone()) + } + + fn size(&self) -> Option { + Some(self.data().contents.len() as u64) + } + + fn file_type(&self) -> Option { + None + } + + fn creator(&self) -> Option { + None + } + + fn contents(&self) -> &[u8] { + tracing::warn!("Reading downloaded file not implemented"); + &[] + } + + fn write(&self, to_write: &[u8]) { + let mut data = self.data(); + data.contents = to_write.to_vec(); + + Self::download(data); + } + + fn refresh(&mut self) {} +} + /// An implementation of `UiBackend` utilizing `web_sys` bindings to input APIs. pub struct WebUiBackend { js_player: JavascriptPlayer, @@ -312,30 +401,13 @@ impl UiBackend for WebUiBackend { fn display_file_save_dialog( &mut self, - _file_name: String, + file_name: String, _title: String, ) -> Option { - None - /* Temporary disabled while #14949 is being fixed - - // Prevent opening multiple dialogs at the same time - if self.dialog_open { - return None; - } - self.dialog_open = true; - - // Create the dialog future Some(Box::pin(async move { - // Select the location to save the file to - let dialog = AsyncFileDialog::new() - .set_title(&title) - .set_file_name(&file_name); - - let result: Result, DialogLoaderError> = Ok(Box::new( - WebFileDialogResult::new(dialog.save_file().await).await, - )); + let result: Result, DialogLoaderError> = + Ok(Box::new(WebDownloadFileDialogResult::new(file_name))); result })) - */ } }