lib.rs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. #![doc = include_str!("../README.md")]
  2. #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
  3. #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
  4. mod adapters;
  5. #[allow(unused_imports)]
  6. pub use adapters::*;
  7. mod element;
  8. pub mod pool;
  9. mod query;
  10. use dioxus_interpreter_js::NATIVE_JS;
  11. use futures_util::{SinkExt, StreamExt};
  12. pub use pool::*;
  13. mod config;
  14. mod eval;
  15. mod events;
  16. pub use config::*;
  17. #[cfg(feature = "axum")]
  18. pub mod launch;
  19. pub trait WebsocketTx: SinkExt<String, Error = LiveViewError> {}
  20. impl<T> WebsocketTx for T where T: SinkExt<String, Error = LiveViewError> {}
  21. pub trait WebsocketRx: StreamExt<Item = Result<String, LiveViewError>> {}
  22. impl<T> WebsocketRx for T where T: StreamExt<Item = Result<String, LiveViewError>> {}
  23. #[derive(Debug, thiserror::Error)]
  24. #[non_exhaustive]
  25. pub enum LiveViewError {
  26. #[error("Sending to client error")]
  27. SendingFailed,
  28. }
  29. fn handle_edits_code() -> String {
  30. use dioxus_interpreter_js::unified_bindings::SLEDGEHAMMER_JS;
  31. let serialize_file_uploads = r#"if (
  32. target.tagName === "INPUT" &&
  33. (event.type === "change" || event.type === "input")
  34. ) {
  35. const type = target.getAttribute("type");
  36. if (type === "file") {
  37. async function read_files() {
  38. const files = target.files;
  39. const file_contents = {};
  40. for (let i = 0; i < files.length; i++) {
  41. const file = files[i];
  42. file_contents[file.name] = Array.from(
  43. new Uint8Array(await file.arrayBuffer())
  44. );
  45. }
  46. let file_engine = {
  47. files: file_contents,
  48. };
  49. contents.files = file_engine;
  50. if (realId === null) {
  51. return;
  52. }
  53. const message = window.interpreter.serializeIpcMessage("user_event", {
  54. name: name,
  55. element: parseInt(realId),
  56. data: contents,
  57. bubbles,
  58. });
  59. window.ipc.postMessage(message);
  60. }
  61. read_files();
  62. return;
  63. }
  64. }"#;
  65. let mut interpreter = format!(
  66. r#"
  67. // Bring the sledgehammer code
  68. {SLEDGEHAMMER_JS}
  69. // And then extend it with our native bindings
  70. {NATIVE_JS}
  71. "#
  72. )
  73. .replace("/*POST_EVENT_SERIALIZATION*/", serialize_file_uploads)
  74. .replace("export", "");
  75. while let Some(import_start) = interpreter.find("import") {
  76. let import_end = interpreter[import_start..]
  77. .find(|c| c == ';' || c == '\n')
  78. .map(|i| i + import_start)
  79. .unwrap_or_else(|| interpreter.len());
  80. interpreter.replace_range(import_start..import_end, "");
  81. }
  82. let main_js = include_str!("./main.js");
  83. let js = format!("{interpreter}\n{main_js}");
  84. js
  85. }
  86. /// This script that gets injected into your app connects this page to the websocket endpoint
  87. ///
  88. /// Once the endpoint is connected, it will send the initial state of the app, and then start
  89. /// processing user events and returning edits to the liveview instance.
  90. ///
  91. /// You can pass a relative path prefixed with "/", or enter a full URL including the protocol
  92. /// (`ws:` or `wss:`) as an argument.
  93. ///
  94. /// If you enter a relative path, the web client automatically prefixes the host address in
  95. /// `window.location` when creating a web socket to LiveView.
  96. ///
  97. /// ```
  98. /// // Creates websocket connection to same host as current page
  99. /// interpreter_glue("/api/liveview");
  100. ///
  101. /// // Creates websocket connection to specified url
  102. /// interpreter_glue("ws://localhost:8080/api/liveview");
  103. /// ```
  104. pub fn interpreter_glue(url_or_path: &str) -> String {
  105. // If the url starts with a `/`, generate glue which reuses current host
  106. let get_ws_url = if url_or_path.starts_with('/') {
  107. r#"
  108. let loc = window.location;
  109. let new_url = "";
  110. if (loc.protocol === "https:") {{
  111. new_url = "wss:";
  112. }} else {{
  113. new_url = "ws:";
  114. }}
  115. new_url += "//" + loc.host + path;
  116. return new_url;
  117. "#
  118. } else {
  119. "return path;"
  120. };
  121. let handle_edits = handle_edits_code();
  122. format!(
  123. r#"
  124. <script>
  125. function __dioxusGetWsUrl(path) {{
  126. {get_ws_url}
  127. }}
  128. var WS_ADDR = __dioxusGetWsUrl("{url_or_path}");
  129. {handle_edits}
  130. </script>
  131. "#
  132. )
  133. }