implement the file engine for liveview

This commit is contained in:
Evan Almloff committed 2023-04-25 14:26:12 -05:00
1 parent 6d3d927d5c
commit 9cfb655478
5 files changed
+113 -16

No files matched your search

+6 -5
View File
@@ -23,11 +23,12 @@ fn module_loader(root_name: &str) -> String {
if (type === "file") {
let target_id = find_real_id(target);
if (target_id !== null) {
const event_name = "change";
const message = serializeIpcMessage("file_diolog", { accept: target.getAttribute("accept"), multiple: target.hasAttribute("multiple"), target: parseInt(target_id), bubbles: event_bubbles(event_name), event: event_name });
console.log(message);
console.log(event);
window.ipc.postMessage(message);
const send = (event_name) => {
const message = serializeIpcMessage("file_diolog", { accept: target.getAttribute("accept"), multiple: target.hasAttribute("multiple"), target: parseInt(target_id), bubbles: event_bubbles(event_name), event: event_name });
window.ipc.postMessage(message);
};
send("change");
send("input");
}
event.preventDefault();
}
+46 -1
View File
@@ -12,10 +12,55 @@ pub struct FormData {
pub values: HashMap<String, Vec<String>>,
#[cfg_attr(feature = "serialize", serde(skip))]
#[cfg_attr(
feature = "serialize",
serde(skip_serializing, deserialize_with = "deserialize_file_engine")
)]
pub files: Option<std::sync::Arc<dyn FileEngine>>,
}
#[cfg(feature = "serialize")]
#[derive(serde::Serialize, serde::Deserialize)]
struct SerializedFileEngine {
files: HashMap<String, Vec<u8>>,
}
#[cfg(feature = "serialize")]
#[async_trait::async_trait(?Send)]
impl FileEngine for SerializedFileEngine {
fn files(&self) -> Vec<String> {
self.files.keys().cloned().collect()
}
async fn read_file(&self, file: &str) -> Option<Vec<u8>> {
self.files.get(file).cloned()
}
async fn read_file_to_string(&self, file: &str) -> Option<String> {
self.read_file(file)
.await
.map(|bytes| String::from_utf8_lossy(&bytes).to_string())
}
}
#[cfg(feature = "serialize")]
fn deserialize_file_engine<'de, D>(
deserializer: D,
) -> Result<Option<std::sync::Arc<dyn FileEngine>>, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let Ok(file_engine) =
SerializedFileEngine::deserialize(deserializer) else{
return Ok(None);
};
let file_engine = std::sync::Arc::new(file_engine);
Ok(Some(file_engine))
}
impl PartialEq for FormData {
fn eq(&self, other: &Self) -> bool {
self.value == other.value && self.values == other.values
+13 -8
View File
@@ -344,17 +344,19 @@ class Interpreter {
this.RemoveEventListener(edit.id, edit.name);
break;
case "NewEventListener":
let bubbles = event_bubbles(edit.name);
const bubbles = event_bubbles(edit.name);
this.NewEventListener(edit.name, edit.id, bubbles, handler);
this.NewEventListener(edit.name, edit.id, bubbles, (event) =>
handler(event, edit.name, bubbles)
);
break;
}
}
}
// this handler is only provided on desktop implementations since this
// this handler is only provided on the desktop and liveview implementations since this
// method is not used by the web implementation
function handler(event) {
function handler(event, name, bubbles) {
let target = event.target;
if (target != null) {
let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
@@ -387,8 +389,6 @@ function handler(event) {
shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
let contents = serialize_event(event);
if (shouldPreventDefault === `on${event.type}`) {
event.preventDefault();
}
@@ -397,6 +397,10 @@ function handler(event) {
event.preventDefault();
}
let contents = serialize_event(event);
/*POST_EVENT_SERIALIZATION*/
if (
target.tagName === "FORM" &&
(event.type === "submit" || event.type === "input")
@@ -408,7 +412,8 @@ function handler(event) {
const formData = new FormData(target);
for (let name of formData.keys()) {
contents.values[name] = formData.getAll(name);
let value = formData.getAll(name);
contents.values[name] = value;
}
}
}
@@ -418,7 +423,7 @@ function handler(event) {
}
window.ipc.postMessage(
serializeIpcMessage("user_event", {
name: edit.name,
name: name,
element: parseInt(realId),
data: contents,
bubbles,
+1
View File
@@ -36,6 +36,7 @@ axum = { version = "0.6.1", optional = true, features = ["ws"] }
# salvo
salvo = { version = "0.37.7", optional = true, features = ["ws"] }
once_cell = "1.17.1"
# actix is ... complicated?
# actix-files = { version = "0.6.2", optional = true }
+47 -2
View File
@@ -34,7 +34,51 @@ pub enum LiveViewError {
SendingFailed,
}
use dioxus_interpreter_js::INTERPRETER_JS;
use once_cell::sync::Lazy;
static INTERPRETER_JS: Lazy<String> = Lazy::new(|| {
let interpreter = dioxus_interpreter_js::INTERPRETER_JS;
let serialize_file_uploads = r#"if (
target.tagName === "INPUT" &&
(event.type === "change" || event.type === "input")
) {
const type = target.getAttribute("type");
if (type === "file") {
async function read_files() {
const files = target.files;
const file_contents = {};
for (let i = 0; i < files.length; i++) {
const file = files[i];
file_contents[file.name] = Array.from(
new Uint8Array(await file.arrayBuffer())
);
}
let file_engine = {
files: file_contents,
};
contents.files = file_engine;
if (realId === null) {
return;
}
const message = serializeIpcMessage("user_event", {
name: name,
element: parseInt(realId),
data: contents,
bubbles,
});
window.ipc.postMessage(message);
}
read_files();
return;
}
}"#;
interpreter.replace("/*POST_EVENT_SERIALIZATION*/", serialize_file_uploads)
});
static MAIN_JS: &str = include_str!("./main.js");
/// This script that gets injected into your app connects this page to the websocket endpoint
@@ -42,11 +86,12 @@ static MAIN_JS: &str = include_str!("./main.js");
/// Once the endpoint is connected, it will send the initial state of the app, and then start
/// processing user events and returning edits to the liveview instance
pub fn interpreter_glue(url: &str) -> String {
let js = &*INTERPRETER_JS;
format!(
r#"
<script>
var WS_ADDR = "{url}";
{INTERPRETER_JS}
{js}
{MAIN_JS}
main();
</script>