implement the file engine for liveview
This commit is contained in:
1 parent
6d3d927d5c
commit
9cfb655478
5 files changed
+113
-16
No files matched your search
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user