Clean up file uploads in desktop/liveview, remove minify
This commit is contained in:
14 files changed
+169
-156
No files matched your search
Vendored
+2
-1
@@ -3,7 +3,8 @@
|
|||||||
"[toml]": {
|
"[toml]": {
|
||||||
"editor.formatOnSave": false
|
"editor.formatOnSave": false
|
||||||
},
|
},
|
||||||
"rust-analyzer.check.workspace": true,
|
"rust-analyzer.check.workspace": false,
|
||||||
|
// "rust-analyzer.check.workspace": true,
|
||||||
"rust-analyzer.check.features": "all",
|
"rust-analyzer.check.features": "all",
|
||||||
"rust-analyzer.cargo.features": "all",
|
"rust-analyzer.cargo.features": "all",
|
||||||
"rust-analyzer.check.allTargets": true
|
"rust-analyzer.check.allTargets": true
|
||||||
|
|||||||
Generated
+3
-46
@@ -83,15 +83,6 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "0.7.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@@ -2601,7 +2592,6 @@ dependencies = [
|
|||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"generational-box",
|
"generational-box",
|
||||||
"minify-js",
|
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4084,7 +4074,7 @@ version = "0.4.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
|
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 1.1.2",
|
"aho-corasick",
|
||||||
"bstr 1.9.1",
|
"bstr 1.9.1",
|
||||||
"log",
|
"log",
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
@@ -4437,16 +4427,6 @@ dependencies = [
|
|||||||
"ahash 0.7.8",
|
"ahash 0.7.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.13.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
|
||||||
dependencies = [
|
|
||||||
"ahash 0.8.10",
|
|
||||||
"bumpalo",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.3"
|
version = "0.14.3"
|
||||||
@@ -5898,16 +5878,6 @@ dependencies = [
|
|||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "minify-js"
|
|
||||||
version = "0.5.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "22d6c512a82abddbbc13b70609cb2beff01be2c7afff534d6e5e1c85e438fc8b"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static",
|
|
||||||
"parse-js",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -6619,19 +6589,6 @@ dependencies = [
|
|||||||
"windows-targets 0.48.5",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parse-js"
|
|
||||||
version = "0.17.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9ec3b11d443640ec35165ee8f6f0559f1c6f41878d70330fe9187012b5935f02"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick 0.7.20",
|
|
||||||
"bumpalo",
|
|
||||||
"hashbrown 0.13.2",
|
|
||||||
"lazy_static",
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "password-hash"
|
name = "password-hash"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -7562,7 +7519,7 @@ version = "1.10.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 1.1.2",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
@@ -7574,7 +7531,7 @@ version = "0.4.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 1.1.2",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|||||||
+21
-14
@@ -3,9 +3,10 @@
|
|||||||
//! Dioxus intercepts these events and provides a Rusty interface to the file data. Since we want this interface to
|
//! Dioxus intercepts these events and provides a Rusty interface to the file data. Since we want this interface to
|
||||||
//! be crossplatform,
|
//! be crossplatform,
|
||||||
|
|
||||||
use dioxus::html::HasFileData;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use tokio::time::sleep;
|
use dioxus::{html::HasFileData, prelude::dioxus_elements::FileEngine};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
launch(app);
|
launch(app);
|
||||||
@@ -15,22 +16,24 @@ fn app() -> Element {
|
|||||||
let mut enable_directory_upload = use_signal(|| false);
|
let mut enable_directory_upload = use_signal(|| false);
|
||||||
let mut files_uploaded = use_signal(|| Vec::new() as Vec<String>);
|
let mut files_uploaded = use_signal(|| Vec::new() as Vec<String>);
|
||||||
|
|
||||||
|
let read_files = move |file_engine: Arc<dyn FileEngine>| async move {
|
||||||
|
let files = file_engine.files();
|
||||||
|
for file_name in &files {
|
||||||
|
if let Some(file) = file_engine.read_file_to_string(file_name).await {
|
||||||
|
files_uploaded.write().push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let upload_files = move |evt: FormEvent| async move {
|
let upload_files = move |evt: FormEvent| async move {
|
||||||
for file_name in evt.files().unwrap().files() {
|
if let Some(file_engine) = evt.files() {
|
||||||
// no files on form inputs?
|
read_files(file_engine).await;
|
||||||
sleep(std::time::Duration::from_secs(1)).await;
|
|
||||||
files_uploaded.write().push(file_name);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle_file_drop = move |evt: DragEvent| async move {
|
let handle_file_drop = move |evt: DragEvent| async move {
|
||||||
if let Some(file_engine) = &evt.files() {
|
if let Some(file_engine) = evt.files() {
|
||||||
let files = file_engine.files();
|
read_files(file_engine).await;
|
||||||
for file_name in &files {
|
|
||||||
if let Some(file) = file_engine.read_file_to_string(file_name).await {
|
|
||||||
files_uploaded.write().push(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,20 +53,24 @@ fn app() -> Element {
|
|||||||
r#type: "file",
|
r#type: "file",
|
||||||
accept: ".txt,.rs",
|
accept: ".txt,.rs",
|
||||||
multiple: true,
|
multiple: true,
|
||||||
|
name: "textreader",
|
||||||
directory: enable_directory_upload,
|
directory: enable_directory_upload,
|
||||||
onchange: upload_files,
|
onchange: upload_files,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label { r#for: "textreader", "Upload text/rust files and read them" }
|
||||||
|
|
||||||
div {
|
div {
|
||||||
// cheating with a little bit of JS...
|
// cheating with a little bit of JS...
|
||||||
"ondragover": "this.style.backgroundColor='#88FF88';",
|
"ondragover": "this.style.backgroundColor='#88FF88';",
|
||||||
"ondragleave": "this.style.backgroundColor='#FFFFFF';",
|
"ondragleave": "this.style.backgroundColor='#FFFFFF';",
|
||||||
id: "drop-zone",
|
id: "drop-zone",
|
||||||
prevent_default: "ondrop dragover dragenter",
|
// prevent_default: "ondrop dragover dragenter",
|
||||||
ondrop: handle_file_drop,
|
ondrop: handle_file_drop,
|
||||||
ondragover: move |event| event.stop_propagation(),
|
ondragover: move |event| event.stop_propagation(),
|
||||||
"Drop files here"
|
"Drop files here"
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
for file in files_uploaded.read().iter() {
|
for file in files_uploaded.read().iter() {
|
||||||
li { "{file}" }
|
li { "{file}" }
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
// Prevent file inputs from opening the file dialog on click
|
|
||||||
let inputs = document.querySelectorAll("input");
|
|
||||||
for (let input of inputs) {
|
|
||||||
if (!input.getAttribute("data-dioxus-file-listener")) {
|
|
||||||
// prevent file inputs from opening the file dialog on click
|
|
||||||
const type = input.getAttribute("type");
|
|
||||||
if (type === "file") {
|
|
||||||
input.setAttribute("data-dioxus-file-listener", true);
|
|
||||||
input.addEventListener("click", (event) => {
|
|
||||||
let target = event.target;
|
|
||||||
let target_id = find_real_id(target);
|
|
||||||
if (target_id !== null) {
|
|
||||||
const send = (event_name) => {
|
|
||||||
const message = window.interpreter.serializeIpcMessage("file_dialog", { accept: target.getAttribute("accept"), directory: target.getAttribute("webkitdirectory") === "true", multiple: target.hasAttribute("multiple"), target: parseInt(target_id), bubbles: event_bubbles(event_name), event: event_name });
|
|
||||||
window.ipc.postMessage(message);
|
|
||||||
};
|
|
||||||
send("change&input");
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,8 @@ use crate::{
|
|||||||
config::{Config, WindowCloseBehaviour},
|
config::{Config, WindowCloseBehaviour},
|
||||||
element::DesktopElement,
|
element::DesktopElement,
|
||||||
event_handlers::WindowEventHandlers,
|
event_handlers::WindowEventHandlers,
|
||||||
file_upload::FileDialogRequest,
|
file_upload::{DesktopFileUploadForm, FileDialogRequest},
|
||||||
ipc::IpcMessage,
|
ipc::{EventData, IpcMessage, UserWindowEvent},
|
||||||
ipc::{EventData, UserWindowEvent},
|
|
||||||
query::QueryResult,
|
query::QueryResult,
|
||||||
shortcut::{GlobalHotKeyEvent, ShortcutRegistry},
|
shortcut::{GlobalHotKeyEvent, ShortcutRegistry},
|
||||||
webview::WebviewInstance,
|
webview::WebviewInstance,
|
||||||
@@ -12,10 +11,7 @@ use crate::{
|
|||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use dioxus_core::ElementId;
|
use dioxus_core::ElementId;
|
||||||
use dioxus_core::VirtualDom;
|
use dioxus_core::VirtualDom;
|
||||||
use dioxus_html::{
|
use dioxus_html::{native_bind::NativeFileEngine, HtmlEvent, PlatformEventData};
|
||||||
native_bind::NativeFileEngine, FileEngine, HasFileData, HasFormData, HtmlEvent,
|
|
||||||
PlatformEventData,
|
|
||||||
};
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
@@ -270,30 +266,17 @@ impl App {
|
|||||||
let Ok(file_dialog) = serde_json::from_value::<FileDialogRequest>(msg.params()) else {
|
let Ok(file_dialog) = serde_json::from_value::<FileDialogRequest>(msg.params()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
struct DesktopFileUploadForm {
|
|
||||||
files: Arc<NativeFileEngine>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasFileData for DesktopFileUploadForm {
|
|
||||||
fn files(&self) -> Option<Arc<dyn FileEngine>> {
|
|
||||||
Some(self.files.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasFormData for DesktopFileUploadForm {
|
|
||||||
fn as_any(&self) -> &dyn std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = ElementId(file_dialog.target);
|
let id = ElementId(file_dialog.target);
|
||||||
let event_name = &file_dialog.event;
|
let event_name = &file_dialog.event;
|
||||||
let event_bubbles = file_dialog.bubbles;
|
let event_bubbles = file_dialog.bubbles;
|
||||||
let files = file_dialog.get_file_event();
|
let files = file_dialog.get_file_event();
|
||||||
|
|
||||||
let data = Rc::new(PlatformEventData::new(Box::new(DesktopFileUploadForm {
|
let as_any = Box::new(DesktopFileUploadForm {
|
||||||
files: Arc::new(NativeFileEngine::new(files)),
|
files: Arc::new(NativeFileEngine::new(files)),
|
||||||
})));
|
});
|
||||||
|
|
||||||
|
let data = Rc::new(PlatformEventData::new(as_any));
|
||||||
|
|
||||||
let view = self.webviews.get_mut(&window).unwrap();
|
let view = self.webviews.get_mut(&window).unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Convert a serialized event to an event trigger
|
//! Convert a serialized event to an event trigger
|
||||||
|
|
||||||
use crate::element::DesktopElement;
|
use crate::{element::DesktopElement, file_upload::DesktopFileUploadForm};
|
||||||
use dioxus_html::*;
|
use dioxus_html::*;
|
||||||
|
|
||||||
pub(crate) struct SerializedHtmlEventConverter;
|
pub(crate) struct SerializedHtmlEventConverter;
|
||||||
@@ -47,8 +47,14 @@ impl HtmlEventConverter for SerializedHtmlEventConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn convert_form_data(&self, event: &PlatformEventData) -> FormData {
|
fn convert_form_data(&self, event: &PlatformEventData) -> FormData {
|
||||||
|
// Attempt a simple serialized form data conversion
|
||||||
|
if let Some(_data) = event.downcast::<SerializedFormData>() {
|
||||||
|
return _data.clone().into();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If that failed then it's a file upload form
|
||||||
event
|
event
|
||||||
.downcast::<SerializedFormData>()
|
.downcast::<DesktopFileUploadForm>()
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into()
|
.into()
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
|
|
||||||
|
use dioxus_html::{native_bind::NativeFileEngine, FileEngine, HasFileData, HasFormData};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{path::PathBuf, str::FromStr};
|
use std::{path::PathBuf, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub(crate) struct FileDialogRequest {
|
pub(crate) struct FileDialogRequest {
|
||||||
@@ -124,3 +125,20 @@ impl FromStr for Filters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct DesktopFileUploadForm {
|
||||||
|
pub files: Arc<NativeFileEngine>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasFileData for DesktopFileUploadForm {
|
||||||
|
fn files(&self) -> Option<Arc<dyn FileEngine>> {
|
||||||
|
Some(self.files.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasFormData for DesktopFileUploadForm {
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,18 +13,6 @@ const EDITS_PATH: &str = "http://dioxus.index.html/edits";
|
|||||||
#[cfg(not(any(target_os = "android", target_os = "windows")))]
|
#[cfg(not(any(target_os = "android", target_os = "windows")))]
|
||||||
const EDITS_PATH: &str = "dioxus://index.html/edits";
|
const EDITS_PATH: &str = "dioxus://index.html/edits";
|
||||||
|
|
||||||
// const PREVENT_FILE_UPLOAD: &str = include_str!("../js/prevent_file_upload.js");
|
|
||||||
|
|
||||||
fn handle_edits_code() -> String {
|
|
||||||
format!(
|
|
||||||
r#"// Poll for requests
|
|
||||||
{SLEDGEHAMMER_JS}
|
|
||||||
{NATIVE_JS}
|
|
||||||
window.interpreter = new NativeInterpreter("{EDITS_PATH}");
|
|
||||||
"#
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_INDEX: &str = include_str!("./index.html");
|
static DEFAULT_INDEX: &str = include_str!("./index.html");
|
||||||
|
|
||||||
/// Build the index.html file we use for bootstrapping a new app
|
/// Build the index.html file we use for bootstrapping a new app
|
||||||
@@ -184,20 +172,26 @@ fn serve_from_fs(path: PathBuf) -> Result<Response<Vec<u8>>> {
|
|||||||
/// - headless: is this page being loaded but invisible? Important because not all windows are visible and the
|
/// - headless: is this page being loaded but invisible? Important because not all windows are visible and the
|
||||||
/// interpreter can't connect until the window is ready.
|
/// interpreter can't connect until the window is ready.
|
||||||
fn module_loader(root_id: &str, headless: bool) -> String {
|
fn module_loader(root_id: &str, headless: bool) -> String {
|
||||||
let js = handle_edits_code();
|
|
||||||
format!(
|
format!(
|
||||||
r#"
|
r#"
|
||||||
<script type="module">
|
<script type="module">
|
||||||
{js}
|
// Bring the sledgehammer code
|
||||||
// Wait for the page to load
|
{SLEDGEHAMMER_JS}
|
||||||
|
|
||||||
|
// And then extend it with our native bindings
|
||||||
|
{NATIVE_JS}
|
||||||
|
|
||||||
|
// The nativeinterprerter extends the sledgehammer interpreter with a few extra methods that we use for IPC
|
||||||
|
window.interpreter = new NativeInterpreter("{EDITS_PATH}");
|
||||||
|
|
||||||
|
// Wait for the page to load before sending the initialize message
|
||||||
window.onload = function() {{
|
window.onload = function() {{
|
||||||
let rootname = "{root_id}";
|
let root_element = window.document.getElementById("{root_id}");
|
||||||
let root_element = window.document.getElementById(rootname);
|
|
||||||
if (root_element != null) {{
|
if (root_element != null) {{
|
||||||
window.interpreter.initialize(root_element);
|
window.interpreter.initialize(root_element);
|
||||||
window.ipc.postMessage(window.interpreter.serializeIpcMessage("initialize"));
|
window.ipc.postMessage(window.interpreter.serializeIpcMessage("initialize"));
|
||||||
}}
|
}}
|
||||||
window.interpreter.wait_for_request({headless});
|
window.interpreter.waitForRequest({headless});
|
||||||
}}
|
}}
|
||||||
</script>
|
</script>
|
||||||
"#
|
"#
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ impl PlatformEventData {
|
|||||||
Self { event }
|
Self { event }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &Box<dyn Any> {
|
||||||
|
&self.event
|
||||||
|
}
|
||||||
|
|
||||||
pub fn downcast<T: 'static>(&self) -> Option<&T> {
|
pub fn downcast<T: 'static>(&self) -> Option<&T> {
|
||||||
self.event.downcast_ref::<T>()
|
self.event.downcast_ref::<T>()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
5236313184333634830
|
12023679989252671232
|
||||||
@@ -214,7 +214,7 @@ var serializeDragEvent = function(event) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// src/ts/native.ts
|
// src/ts/native.ts
|
||||||
var targetId = function(target) {
|
var getTargetId = function(target) {
|
||||||
if (!(target instanceof Node)) {
|
if (!(target instanceof Node)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -254,10 +254,30 @@ class NativeInterpreter extends JSChannel_ {
|
|||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
window.addEventListener("drop", function(e) {
|
window.addEventListener("drop", function(e) {
|
||||||
if (e.target instanceof Element && e.target.tagName != "INPUT") {
|
let target = e.target;
|
||||||
e.preventDefault();
|
if (!(target instanceof Element)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
e.preventDefault();
|
||||||
}, false);
|
}, false);
|
||||||
|
window.addEventListener("click", (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
if (target instanceof HTMLInputElement && target.getAttribute("type") === "file") {
|
||||||
|
let target_id = getTargetId(target);
|
||||||
|
if (target_id !== null) {
|
||||||
|
const message = this.serializeIpcMessage("file_dialog", {
|
||||||
|
event: "change&input",
|
||||||
|
accept: target.getAttribute("accept"),
|
||||||
|
directory: target.getAttribute("webkitdirectory") === "true",
|
||||||
|
multiple: target.hasAttribute("multiple"),
|
||||||
|
target: target_id,
|
||||||
|
bubbles: event.bubbles
|
||||||
|
});
|
||||||
|
this.ipc.postMessage(message);
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
this.ipc = window.ipc;
|
this.ipc = window.ipc;
|
||||||
const handler = (event) => this.handleEvent(event, event.type, true);
|
const handler = (event) => this.handleEvent(event, event.type, true);
|
||||||
super.initialize(root, handler);
|
super.initialize(root, handler);
|
||||||
@@ -310,9 +330,8 @@ class NativeInterpreter extends JSChannel_ {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleEvent(event, name, bubbles) {
|
handleEvent(event, name, bubbles) {
|
||||||
console.log("handling event: ", event);
|
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
const realId = targetId(target);
|
const realId = getTargetId(target);
|
||||||
const contents = serializeEvent(event, target);
|
const contents = serializeEvent(event, target);
|
||||||
let body = {
|
let body = {
|
||||||
name,
|
name,
|
||||||
@@ -386,14 +405,14 @@ class NativeInterpreter extends JSChannel_ {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wait_for_request(headless) {
|
waitForRequest(headless) {
|
||||||
fetch(new Request(this.editsPath)).then((response) => response.arrayBuffer()).then((bytes) => {
|
fetch(new Request(this.editsPath)).then((response) => response.arrayBuffer()).then((bytes) => {
|
||||||
if (headless) {
|
if (headless) {
|
||||||
this.run_from_bytes(bytes);
|
this.run_from_bytes(bytes);
|
||||||
} else {
|
} else {
|
||||||
requestAnimationFrame(() => this.run_from_bytes(bytes));
|
requestAnimationFrame(() => this.run_from_bytes(bytes));
|
||||||
}
|
}
|
||||||
this.wait_for_request(headless);
|
this.waitForRequest(headless);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,19 +36,49 @@ export class NativeInterpreter extends JSChannel_ {
|
|||||||
// attach an event listener on the body that prevents file drops from navigating
|
// attach an event listener on the body that prevents file drops from navigating
|
||||||
// this is because the browser will try to navigate to the file if it's dropped on the window
|
// this is because the browser will try to navigate to the file if it's dropped on the window
|
||||||
window.addEventListener("dragover", function (e) {
|
window.addEventListener("dragover", function (e) {
|
||||||
// check which element is our target
|
// // check which element is our target
|
||||||
if (e.target instanceof Element && e.target.tagName != "INPUT") {
|
if (e.target instanceof Element && e.target.tagName != "INPUT") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
window.addEventListener("drop", function (e) {
|
window.addEventListener("drop", function (e) {
|
||||||
// check which element is our target
|
let target = e.target;
|
||||||
if (e.target instanceof Element && e.target.tagName != "INPUT") {
|
|
||||||
e.preventDefault();
|
if (!(target instanceof Element)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dropping a file on the window will navigate to the file, which we don't want
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// if the element has a drop listener on it, we should send a message to the host with the contents of the drop instead
|
||||||
|
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
// attach a listener to the route that listens for clicks and prevents the default file dialog
|
||||||
|
window.addEventListener("click", (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
if (target instanceof HTMLInputElement && target.getAttribute("type") === "file") {
|
||||||
|
// Send a message to the host to open the file dialog if the target is a file input and has a dioxus id attached to it
|
||||||
|
let target_id = getTargetId(target);
|
||||||
|
if (target_id !== null) {
|
||||||
|
const message = this.serializeIpcMessage("file_dialog", {
|
||||||
|
event: "change&input",
|
||||||
|
accept: target.getAttribute("accept"),
|
||||||
|
directory: target.getAttribute("webkitdirectory") === "true",
|
||||||
|
multiple: target.hasAttribute("multiple"),
|
||||||
|
target: target_id,
|
||||||
|
bubbles: event.bubbles,
|
||||||
|
});
|
||||||
|
this.ipc.postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent default regardless - we don't want file dialogs and we don't want the browser to navigate
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// @ts-ignore - wry gives us this
|
// @ts-ignore - wry gives us this
|
||||||
this.ipc = window.ipc;
|
this.ipc = window.ipc;
|
||||||
@@ -119,10 +149,8 @@ export class NativeInterpreter extends JSChannel_ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleEvent(event: Event, name: string, bubbles: boolean) {
|
handleEvent(event: Event, name: string, bubbles: boolean) {
|
||||||
console.log("handling event: ", event);
|
|
||||||
|
|
||||||
const target = event.target!;
|
const target = event.target!;
|
||||||
const realId = targetId(target)!;
|
const realId = getTargetId(target)!;
|
||||||
const contents = serializeEvent(event, target);
|
const contents = serializeEvent(event, target);
|
||||||
|
|
||||||
// Handle the event on the virtualdom and then preventDefault if it also preventsDefault
|
// Handle the event on the virtualdom and then preventDefault if it also preventsDefault
|
||||||
@@ -141,6 +169,8 @@ export class NativeInterpreter extends JSChannel_ {
|
|||||||
this.preventDefaults(event, target);
|
this.preventDefaults(event, target);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// liveview does not have syncronous event handling, so we need to send the event to the host
|
// liveview does not have syncronous event handling, so we need to send the event to the host
|
||||||
if (this.liveview) {
|
if (this.liveview) {
|
||||||
// Okay, so the user might've requested some files to be read
|
// Okay, so the user might've requested some files to be read
|
||||||
@@ -263,7 +293,7 @@ export class NativeInterpreter extends JSChannel_ {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_for_request(headless: boolean) {
|
waitForRequest(headless: boolean) {
|
||||||
fetch(new Request(this.editsPath))
|
fetch(new Request(this.editsPath))
|
||||||
.then(response => response.arrayBuffer())
|
.then(response => response.arrayBuffer())
|
||||||
.then(bytes => {
|
.then(bytes => {
|
||||||
@@ -275,7 +305,7 @@ export class NativeInterpreter extends JSChannel_ {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
requestAnimationFrame(() => this.run_from_bytes(bytes));
|
requestAnimationFrame(() => this.run_from_bytes(bytes));
|
||||||
}
|
}
|
||||||
this.wait_for_request(headless);
|
this.waitForRequest(headless);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,7 +336,7 @@ function handleVirtualdomEventSync(contents: string): EventSyncResult {
|
|||||||
return JSON.parse(xhr.responseText);
|
return JSON.parse(xhr.responseText);
|
||||||
}
|
}
|
||||||
|
|
||||||
function targetId(target: EventTarget): NodeId | null {
|
function getTargetId(target: EventTarget): NodeId | null {
|
||||||
// Ensure that the target is a node, sometimes it's nota
|
// Ensure that the target is a node, sometimes it's nota
|
||||||
if (!(target instanceof Node)) {
|
if (!(target instanceof Node)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -329,3 +359,28 @@ function targetId(target: EventTarget): NodeId | null {
|
|||||||
|
|
||||||
return parseInt(realId);
|
return parseInt(realId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// function applyFileUpload() {
|
||||||
|
// let inputs = document.querySelectorAll("input");
|
||||||
|
// for (let input of inputs) {
|
||||||
|
// if (!input.getAttribute("data-dioxus-file-listener")) {
|
||||||
|
// // prevent file inputs from opening the file dialog on click
|
||||||
|
// const type = input.getAttribute("type");
|
||||||
|
// if (type === "file") {
|
||||||
|
// input.setAttribute("data-dioxus-file-listener", true);
|
||||||
|
// input.addEventListener("click", (event) => {
|
||||||
|
// let target = event.target;
|
||||||
|
// let target_id = find_real_id(target);
|
||||||
|
// if (target_id !== null) {
|
||||||
|
// const send = (event_name) => {
|
||||||
|
// const message = window.interpreter.serializeIpcMessage("file_diolog", { accept: target.getAttribute("accept"), directory: target.getAttribute("webkitdirectory") === "true", multiple: target.hasAttribute("multiple"), target: parseInt(target_id), bubbles: event_bubbles(event_name), event: event_name });
|
||||||
|
// window.ipc.postMessage(message);
|
||||||
|
// };
|
||||||
|
// send("change&input");
|
||||||
|
// }
|
||||||
|
// event.preventDefault();
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -24,7 +24,6 @@ serde = { version = "1.0.151", features = ["derive"] }
|
|||||||
serde_json = "1.0.91"
|
serde_json = "1.0.91"
|
||||||
dioxus-html = { workspace = true, features = ["serialize", "eval", "mounted"] }
|
dioxus-html = { workspace = true, features = ["serialize", "eval", "mounted"] }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
minify-js = "0.5.6"
|
|
||||||
dioxus-core = { workspace = true, features = ["serialize"] }
|
dioxus-core = { workspace = true, features = ["serialize"] }
|
||||||
dioxus-interpreter-js = { workspace = true, features = ["binary-protocol"] }
|
dioxus-interpreter-js = { workspace = true, features = ["binary-protocol"] }
|
||||||
dioxus-hot-reload = { workspace = true, optional = true }
|
dioxus-hot-reload = { workspace = true, optional = true }
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ pub enum LiveViewError {
|
|||||||
|
|
||||||
fn handle_edits_code() -> String {
|
fn handle_edits_code() -> String {
|
||||||
use dioxus_interpreter_js::unified_bindings::SLEDGEHAMMER_JS;
|
use dioxus_interpreter_js::unified_bindings::SLEDGEHAMMER_JS;
|
||||||
use minify_js::{minify, Session, TopLevelMode};
|
|
||||||
|
|
||||||
let serialize_file_uploads = r#"if (
|
let serialize_file_uploads = r#"if (
|
||||||
target.tagName === "INPUT" &&
|
target.tagName === "INPUT" &&
|
||||||
@@ -81,15 +80,9 @@ fn handle_edits_code() -> String {
|
|||||||
.unwrap_or_else(|| interpreter.len());
|
.unwrap_or_else(|| interpreter.len());
|
||||||
interpreter.replace_range(import_start..import_end, "");
|
interpreter.replace_range(import_start..import_end, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
let main_js = include_str!("./main.js");
|
let main_js = include_str!("./main.js");
|
||||||
|
|
||||||
let js = format!("{interpreter}\n{main_js}");
|
let js = format!("{interpreter}\n{main_js}");
|
||||||
|
js
|
||||||
let session = Session::new();
|
|
||||||
let mut out = Vec::new();
|
|
||||||
minify(&session, TopLevelMode::Module, js.as_bytes(), &mut out).unwrap();
|
|
||||||
String::from_utf8(out).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This script that gets injected into your app connects this page to the websocket endpoint
|
/// This script that gets injected into your app connects this page to the websocket endpoint
|
||||||
|
|||||||
Reference in new issue
Block a user