CLI: Toasts & Tweaks (#2702)
* progress: cli toasts * forgot the html * progress: toasts * revision: don't open splash on desktop * fix: fmt, spellcheck
This commit is contained in:
1 parent
beeee0dda1
commit
bd58a92441
10 files changed
+363
-31
No files matched your search
@@ -325,7 +325,6 @@
|
|||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
// Parse the message as json
|
// Parse the message as json
|
||||||
let data = JSON.parse(event.data);
|
let data = JSON.parse(event.data);
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
// If the message is "Ready", reload the page
|
// If the message is "Ready", reload the page
|
||||||
if (data.type === "Ready") {
|
if (data.type === "Ready") {
|
||||||
@@ -364,7 +363,6 @@
|
|||||||
let errorBlock = document.getElementById("error-block");
|
let errorBlock = document.getElementById("error-block");
|
||||||
errorBlock.innerHTML = formatting2;
|
errorBlock.innerHTML = formatting2;
|
||||||
|
|
||||||
console.log(data.data.error);
|
|
||||||
} else if (data.type === "Building") {
|
} else if (data.type === "Building") {
|
||||||
// Show correct view for message.
|
// Show correct view for message.
|
||||||
let errorContainer = document.getElementById("error");
|
let errorContainer = document.getElementById("error");
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
<style>
|
||||||
|
/* Inter Font */
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');
|
||||||
|
|
||||||
|
#dx-toast-template {
|
||||||
|
display: none;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 0;
|
||||||
|
padding-right: 10px;
|
||||||
|
user-select: none;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast .dx-toast-inner {
|
||||||
|
transition: right 0.2s ease-out;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
background-color: #181B20;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
min-width: 280px;
|
||||||
|
min-height: 92px;
|
||||||
|
width: min-content;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
transform: translateX(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast .dx-toast-level-bar-container {
|
||||||
|
height: 100%;
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast .dx-toast-level-bar-container .dx-toast-level-bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 5px 0px 0px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast .dx-toast-content {
|
||||||
|
padding: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast .dx-toast-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: end;
|
||||||
|
margin-bottom: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast .dx-toast-header>svg {
|
||||||
|
height: 22px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast .dx-toast-header .dx-toast-header-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast .dx-toast-msg {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast-level-bar.info {
|
||||||
|
background-color: #428EFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast-level-bar.success {
|
||||||
|
background-color: #42FF65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-toast-level-bar.error {
|
||||||
|
background-color: #FF4242;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="dx-toast-template" class="dx-toast">
|
||||||
|
<div class="dx-toast-inner" style="right:-300px;">
|
||||||
|
<!-- Level/Color decor -->
|
||||||
|
<div class="dx-toast-level-bar-container">
|
||||||
|
<div class="dx-toast-level-bar info"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="dx-toast-content">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="dx-toast-header">
|
||||||
|
<!-- Dioxus Logo -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" preserveAspectRatio="none">
|
||||||
|
<path
|
||||||
|
d="M22.158 1.783c0 3.077-.851 5.482-2.215 7.377s-3.32 3.557-5.447 5.33-4.425 3.657-6.252 6.195-3.102 5.515-3.102 9.532h4.699c0-3.077.853-5.377 2.217-7.272s3.32-3.557 5.447-5.33 4.425-3.657 6.252-6.195 3.102-5.62 3.102-9.637z"
|
||||||
|
fill="#e96020" />
|
||||||
|
<path
|
||||||
|
d="M9.531 25.927c-.635 0-1.021.515-1.02 1.15s.385 1.151 1.02 1.15H22.47a1.151 1.151 0 1 0 0-2.301zm1.361-4.076c-.608 0-.954.558-.953 1.166s.346 1.035.953 1.035h10.217a1.101 1.101 0 1 0 0-2.201zm0-13.594a1.101 1.101 0 1 0 0 2.201h10.217c.607 0 .953-.598.953-1.205s-.345-.996-.953-.996zM9.531 4.021A1.15 1.15 0 0 0 8.38 5.17a1.15 1.15 0 0 0 1.15 1.15h12.94c.635 0 1.021-.498 1.02-1.133s-.386-1.166-1.02-1.166z"
|
||||||
|
fill="#2d323b" />
|
||||||
|
<path
|
||||||
|
d="M5.142 1.783c0 4.016 1.275 7.099 3.102 9.637s4.125 4.422 6.252 6.195 4.083 3.656 5.447 5.551 2.215 3.974 2.215 7.051h4.701c0-4.016-1.275-7.038-3.102-9.576s-4.125-4.422-6.252-6.195-4.083-3.435-5.447-5.33S9.841 4.86 9.841 1.783z"
|
||||||
|
fill="#00a8d6" />
|
||||||
|
</svg>
|
||||||
|
<!-- Toast Title Text -->
|
||||||
|
<h3 class="dx-toast-header-text">Your app is being rebuilt.</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Message -->
|
||||||
|
<p class="dx-toast-msg">A non-hot-reloadable change occurred and we must rebuild.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const STORAGE_KEY = "SCHEDULED-DX-TOAST";
|
||||||
|
let currentToast = null;
|
||||||
|
let currentTimeout = null;
|
||||||
|
|
||||||
|
// Show a toast, removing the previous one.
|
||||||
|
function showDXToast(headerText, message, progressLevel, durationMs) {
|
||||||
|
// Close current toast if exists.
|
||||||
|
closeDXToast();
|
||||||
|
|
||||||
|
// Clone template and add unique id.
|
||||||
|
let toastTemplate = document.getElementById("dx-toast-template");
|
||||||
|
let cloned = toastTemplate.cloneNode(true);
|
||||||
|
let toastId = `dx-toast`;
|
||||||
|
cloned.id = toastId;
|
||||||
|
currentToast = cloned;
|
||||||
|
|
||||||
|
let innerElem = currentToast.querySelector(`#${toastId} .dx-toast-inner`);
|
||||||
|
|
||||||
|
// Set the progress level
|
||||||
|
let progressBarElem = innerElem.querySelector(".dx-toast-inner .dx-toast-level-bar-container .dx-toast-level-bar");
|
||||||
|
progressBarElem.className = `dx-toast-level-bar ${progressLevel}`;
|
||||||
|
|
||||||
|
// Set header text
|
||||||
|
let headerTextElem = innerElem.querySelector(".dx-toast-inner .dx-toast-header .dx-toast-header-text");
|
||||||
|
headerTextElem.innerText = headerText;
|
||||||
|
|
||||||
|
// Set message
|
||||||
|
let messageElem = innerElem.querySelector(".dx-toast-inner .dx-toast-msg");
|
||||||
|
messageElem.innerText = message;
|
||||||
|
|
||||||
|
document.body.appendChild(currentToast);
|
||||||
|
|
||||||
|
// Add listener to close toasts when clicked.
|
||||||
|
// Safety: Calling `closeToast` removes the element and all event listeners with it.
|
||||||
|
currentToast.addEventListener("click", closeDXToast);
|
||||||
|
|
||||||
|
// Wait a bit of time so animation plays correctly.
|
||||||
|
setTimeout(() => {
|
||||||
|
innerElem.style.right = "0";
|
||||||
|
|
||||||
|
currentTimeout = setTimeout(() => {
|
||||||
|
closeDXToast();
|
||||||
|
}, durationMs);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule a toast to be displayed after reload.
|
||||||
|
function scheduleDXToast(headerText, message, level, durationMs) {
|
||||||
|
let data = {
|
||||||
|
headerText,
|
||||||
|
message,
|
||||||
|
level,
|
||||||
|
durationMs,
|
||||||
|
};
|
||||||
|
|
||||||
|
let jsonData = JSON.stringify(data);
|
||||||
|
sessionStorage.setItem(STORAGE_KEY, jsonData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the current toast.
|
||||||
|
function closeDXToast() {
|
||||||
|
if (currentToast) {
|
||||||
|
currentToast.remove();
|
||||||
|
}
|
||||||
|
clearTimeout(currentTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle any scheduled toasts after reload.
|
||||||
|
let potentialData = sessionStorage.getItem(STORAGE_KEY);
|
||||||
|
if (potentialData) {
|
||||||
|
sessionStorage.removeItem(STORAGE_KEY);
|
||||||
|
let data = JSON.parse(potentialData);
|
||||||
|
showDXToast(data.headerText, data.message, data.level, data.durationMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -10,6 +10,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
const DEFAULT_HTML: &str = include_str!("../../assets/index.html");
|
const DEFAULT_HTML: &str = include_str!("../../assets/index.html");
|
||||||
|
const TOAST_HTML: &str = include_str!("../../assets/toast.html");
|
||||||
|
|
||||||
impl BuildRequest {
|
impl BuildRequest {
|
||||||
pub(crate) fn prepare_html(
|
pub(crate) fn prepare_html(
|
||||||
@@ -110,9 +111,16 @@ impl BuildRequest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
</script></body"#,
|
</script>
|
||||||
|
{DX_TOAST_UTILITIES}
|
||||||
|
</body"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
*html = match self.serve && !self.build_arguments.release {
|
||||||
|
true => html.replace("{DX_TOAST_UTILITIES}", TOAST_HTML),
|
||||||
|
false => html.replace("{DX_TOAST_UTILITIES}", ""),
|
||||||
|
};
|
||||||
|
|
||||||
// And try to insert preload links for the wasm and js files
|
// And try to insert preload links for the wasm and js files
|
||||||
*html = html.replace(
|
*html = html.replace(
|
||||||
"</head",
|
"</head",
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ impl std::fmt::Display for Stage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct UpdateBuildProgress {
|
pub struct UpdateBuildProgress {
|
||||||
pub stage: Stage,
|
pub stage: Stage,
|
||||||
pub update: UpdateStage,
|
pub update: UpdateStage,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::builder::{Stage, UpdateBuildProgress, UpdateStage};
|
||||||
use crate::cli::serve::Serve;
|
use crate::cli::serve::Serve;
|
||||||
use crate::dioxus_crate::DioxusCrate;
|
use crate::dioxus_crate::DioxusCrate;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
@@ -106,9 +107,17 @@ pub async fn serve_all(serve: Serve, dioxus_crate: DioxusCrate) -> Result<()> {
|
|||||||
// We also can check the status of the builds here in case we have multiple ongoing builds
|
// We also can check the status of the builds here in case we have multiple ongoing builds
|
||||||
match application {
|
match application {
|
||||||
Ok(BuilderUpdate::Progress { platform, update }) => {
|
Ok(BuilderUpdate::Progress { platform, update }) => {
|
||||||
let update_stage = update.stage;
|
let update_clone = update.clone();
|
||||||
screen.new_build_logs(platform, update);
|
screen.new_build_logs(platform, update_clone);
|
||||||
server.update_build_status(screen.build_progress.progress(), update_stage.to_string()).await;
|
server.update_build_status(screen.build_progress.progress(), update.stage.to_string()).await;
|
||||||
|
|
||||||
|
match update {
|
||||||
|
// Send rebuild start message.
|
||||||
|
UpdateBuildProgress { stage: Stage::Compiling, update: UpdateStage::Start } => server.send_reload_start().await,
|
||||||
|
// Send rebuild failed message.
|
||||||
|
UpdateBuildProgress { stage: Stage::Finished, update: UpdateStage::Failed(_) } => server.send_reload_failed().await,
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(BuilderUpdate::Ready { results }) => {
|
Ok(BuilderUpdate::Ready { results }) => {
|
||||||
if !results.is_empty() {
|
if !results.is_empty() {
|
||||||
@@ -130,7 +139,7 @@ pub async fn serve_all(serve: Serve, dioxus_crate: DioxusCrate) -> Result<()> {
|
|||||||
screen.new_ready_app(&mut builder, results);
|
screen.new_ready_app(&mut builder, results);
|
||||||
|
|
||||||
// And then finally tell the server to reload
|
// And then finally tell the server to reload
|
||||||
server.send_reload().await;
|
server.send_reload_command().await;
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
server.send_build_error(err).await;
|
server.send_build_error(err).await;
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ impl Server {
|
|||||||
// Actually just start the server, cloning in a few bits of config
|
// Actually just start the server, cloning in a few bits of config
|
||||||
let web_config = cfg.dioxus_config.web.https.clone();
|
let web_config = cfg.dioxus_config.web.https.clone();
|
||||||
let base_path = cfg.dioxus_config.web.app.base_path.clone();
|
let base_path = cfg.dioxus_config.web.app.base_path.clone();
|
||||||
|
let platform = serve.platform();
|
||||||
let _server_task = tokio::spawn(async move {
|
let _server_task = tokio::spawn(async move {
|
||||||
let web_config = web_config.clone();
|
let web_config = web_config.clone();
|
||||||
// HTTPS
|
// HTTPS
|
||||||
@@ -139,7 +140,7 @@ impl Server {
|
|||||||
let rustls: Option<RustlsConfig> = get_rustls(&web_config).await.unwrap();
|
let rustls: Option<RustlsConfig> = get_rustls(&web_config).await.unwrap();
|
||||||
|
|
||||||
// Open the browser
|
// Open the browser
|
||||||
if start_browser {
|
if start_browser && platform != Platform::Desktop {
|
||||||
open_browser(base_path, addr, rustls.is_some());
|
open_browser(base_path, addr, rustls.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +176,7 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends the current build status to all clients.
|
||||||
async fn send_build_status(&mut self) {
|
async fn send_build_status(&mut self) {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < self.build_status_sockets.len() {
|
while i < self.build_status_sockets.len() {
|
||||||
@@ -190,6 +192,7 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends a start build message to all clients.
|
||||||
pub async fn start_build(&mut self) {
|
pub async fn start_build(&mut self) {
|
||||||
self.build_status.set(Status::Building {
|
self.build_status.set(Status::Building {
|
||||||
progress: 0.0,
|
progress: 0.0,
|
||||||
@@ -198,6 +201,7 @@ impl Server {
|
|||||||
self.send_build_status().await;
|
self.send_build_status().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends an updated build status to all clients.
|
||||||
pub async fn update_build_status(&mut self, progress: f64, build_message: String) {
|
pub async fn update_build_status(&mut self, progress: f64, build_message: String) {
|
||||||
if !matches!(self.build_status.get(), Status::Building { .. }) {
|
if !matches!(self.build_status.get(), Status::Building { .. }) {
|
||||||
return;
|
return;
|
||||||
@@ -209,6 +213,7 @@ impl Server {
|
|||||||
self.send_build_status().await;
|
self.send_build_status().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends hot reloadable changes to all clients.
|
||||||
pub async fn send_hotreload(&mut self, reload: HotReloadMsg) {
|
pub async fn send_hotreload(&mut self, reload: HotReloadMsg) {
|
||||||
let msg = DevserverMsg::HotReload(reload);
|
let msg = DevserverMsg::HotReload(reload);
|
||||||
let msg = serde_json::to_string(&msg).unwrap();
|
let msg = serde_json::to_string(&msg).unwrap();
|
||||||
@@ -275,6 +280,7 @@ impl Server {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a `cargo` error to HTML and sends it to clients.
|
||||||
pub async fn send_build_error(&mut self, error: Error) {
|
pub async fn send_build_error(&mut self, error: Error) {
|
||||||
let error = error.to_string();
|
let error = error.to_string();
|
||||||
self.build_status.set(Status::BuildError {
|
self.build_status.set(Status::BuildError {
|
||||||
@@ -283,25 +289,36 @@ impl Server {
|
|||||||
self.send_build_status().await;
|
self.send_build_status().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_reload(&mut self) {
|
/// Tells all clients that a full rebuild has started.
|
||||||
self.build_status.set(Status::Ready);
|
pub async fn send_reload_start(&mut self) {
|
||||||
self.send_build_status().await;
|
self.send_devserver_message(DevserverMsg::FullReloadStart)
|
||||||
for socket in self.hot_reload_sockets.iter_mut() {
|
.await;
|
||||||
_ = socket
|
|
||||||
.send(Message::Text(
|
|
||||||
serde_json::to_string(&DevserverMsg::FullReload).unwrap(),
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a shutdown message to all connected clients
|
/// Tells all clients that a full rebuild has failed.
|
||||||
|
pub async fn send_reload_failed(&mut self) {
|
||||||
|
self.send_devserver_message(DevserverMsg::FullReloadFailed)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tells all clients to reload if possible for new changes.
|
||||||
|
pub async fn send_reload_command(&mut self) {
|
||||||
|
self.build_status.set(Status::Ready);
|
||||||
|
self.send_build_status().await;
|
||||||
|
self.send_devserver_message(DevserverMsg::FullReloadCommand)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a shutdown message to all connected clients.
|
||||||
pub async fn send_shutdown(&mut self) {
|
pub async fn send_shutdown(&mut self) {
|
||||||
|
self.send_devserver_message(DevserverMsg::Shutdown).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a devserver message to all connected clients.
|
||||||
|
async fn send_devserver_message(&mut self, msg: DevserverMsg) {
|
||||||
for socket in self.hot_reload_sockets.iter_mut() {
|
for socket in self.hot_reload_sockets.iter_mut() {
|
||||||
_ = socket
|
_ = socket
|
||||||
.send(Message::Text(
|
.send(Message::Text(serde_json::to_string(&msg).unwrap()))
|
||||||
serde_json::to_string(&DevserverMsg::Shutdown).unwrap(),
|
|
||||||
))
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,8 +330,10 @@ impl App {
|
|||||||
not(target_os = "ios")
|
not(target_os = "ios")
|
||||||
))]
|
))]
|
||||||
pub fn handle_hot_reload_msg(&mut self, msg: dioxus_hot_reload::DevserverMsg) {
|
pub fn handle_hot_reload_msg(&mut self, msg: dioxus_hot_reload::DevserverMsg) {
|
||||||
|
use dioxus_hot_reload::DevserverMsg;
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
dioxus_hot_reload::DevserverMsg::HotReload(hr_msg) => {
|
DevserverMsg::HotReload(hr_msg) => {
|
||||||
for webview in self.webviews.values_mut() {
|
for webview in self.webviews.values_mut() {
|
||||||
dioxus_hot_reload::apply_changes(&mut webview.dom, &hr_msg);
|
dioxus_hot_reload::apply_changes(&mut webview.dom, &hr_msg);
|
||||||
webview.poll_vdom();
|
webview.poll_vdom();
|
||||||
@@ -343,11 +345,13 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dioxus_hot_reload::DevserverMsg::FullReload => {
|
DevserverMsg::FullReloadCommand
|
||||||
|
| DevserverMsg::FullReloadStart
|
||||||
|
| DevserverMsg::FullReloadFailed => {
|
||||||
// usually only web gets this message - what are we supposed to do?
|
// usually only web gets this message - what are we supposed to do?
|
||||||
// Maybe we could just binary patch ourselves in place without losing window state?
|
// Maybe we could just binary patch ourselves in place without losing window state?
|
||||||
}
|
}
|
||||||
dioxus_hot_reload::DevserverMsg::Shutdown => {
|
DevserverMsg::Shutdown => {
|
||||||
self.control_flow = ControlFlow::Exit;
|
self.control_flow = ControlFlow::Exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,14 @@ pub enum DevserverMsg {
|
|||||||
/// This includes all the templates/literals/assets/binary patches that have changed in one shot
|
/// This includes all the templates/literals/assets/binary patches that have changed in one shot
|
||||||
HotReload(HotReloadMsg),
|
HotReload(HotReloadMsg),
|
||||||
|
|
||||||
|
/// The devserver is starting a full rebuild.
|
||||||
|
FullReloadStart,
|
||||||
|
|
||||||
|
/// The full reload failed.
|
||||||
|
FullReloadFailed,
|
||||||
|
|
||||||
/// The app should reload completely if it can
|
/// The app should reload completely if it can
|
||||||
FullReload,
|
FullReloadCommand,
|
||||||
|
|
||||||
/// The program is shutting down completely - maybe toss up a splash screen or something?
|
/// The program is shutting down completely - maybe toss up a splash screen or something?
|
||||||
Shutdown,
|
Shutdown,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::{
|
|||||||
LiveViewError,
|
LiveViewError,
|
||||||
};
|
};
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
|
use dioxus_hot_reload::DevserverMsg;
|
||||||
use dioxus_html::{EventData, HtmlEvent, PlatformEventData};
|
use dioxus_html::{EventData, HtmlEvent, PlatformEventData};
|
||||||
use dioxus_interpreter_js::MutationState;
|
use dioxus_interpreter_js::MutationState;
|
||||||
use futures_util::{pin_mut, SinkExt, StreamExt};
|
use futures_util::{pin_mut, SinkExt, StreamExt};
|
||||||
@@ -213,16 +214,18 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
|
|||||||
Some(msg) = hot_reload_wait => {
|
Some(msg) = hot_reload_wait => {
|
||||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||||
match msg{
|
match msg{
|
||||||
dioxus_hot_reload::DevserverMsg::HotReload(msg)=> {
|
DevserverMsg::HotReload(msg)=> {
|
||||||
dioxus_hot_reload::apply_changes(&mut vdom, &msg);
|
dioxus_hot_reload::apply_changes(&mut vdom, &msg);
|
||||||
}
|
}
|
||||||
dioxus_hot_reload::DevserverMsg::Shutdown => {
|
DevserverMsg::Shutdown => {
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
},
|
},
|
||||||
dioxus_hot_reload::DevserverMsg::FullReload => {
|
DevserverMsg::FullReloadCommand
|
||||||
|
| DevserverMsg::FullReloadStart
|
||||||
|
| DevserverMsg::FullReloadFailed => {
|
||||||
// usually only web gets this message - what are we supposed to do?
|
// usually only web gets this message - what are we supposed to do?
|
||||||
// Maybe we could just binary patch ourselves in place without losing window state?
|
// Maybe we could just binary patch ourselves in place without losing window state?
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
#[cfg(not(all(feature = "hot-reload", debug_assertions)))]
|
#[cfg(not(all(feature = "hot-reload", debug_assertions)))]
|
||||||
let () = msg;
|
let () = msg;
|
||||||
|
|||||||
@@ -3,7 +3,12 @@
|
|||||||
//! This sets up a websocket connection to the devserver and handles messages from it.
|
//! This sets up a websocket connection to the devserver and handles messages from it.
|
||||||
//! We also set up a little recursive timer that will attempt to reconnect if the connection is lost.
|
//! We also set up a little recursive timer that will attempt to reconnect if the connection is lost.
|
||||||
|
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use dioxus_core::ScopeId;
|
||||||
use dioxus_hot_reload::{DevserverMsg, HotReloadMsg};
|
use dioxus_hot_reload::{DevserverMsg, HotReloadMsg};
|
||||||
|
use dioxus_html::prelude::eval;
|
||||||
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
|
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
|
||||||
use js_sys::JsString;
|
use js_sys::JsString;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
@@ -14,6 +19,9 @@ const POLL_INTERVAL_MIN: i32 = 250;
|
|||||||
const POLL_INTERVAL_MAX: i32 = 4000;
|
const POLL_INTERVAL_MAX: i32 = 4000;
|
||||||
const POLL_INTERVAL_SCALE_FACTOR: i32 = 2;
|
const POLL_INTERVAL_SCALE_FACTOR: i32 = 2;
|
||||||
|
|
||||||
|
/// Amount of time that toats should be displayed.
|
||||||
|
const TOAST_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
pub(crate) fn init() -> UnboundedReceiver<HotReloadMsg> {
|
pub(crate) fn init() -> UnboundedReceiver<HotReloadMsg> {
|
||||||
// Create the tx/rx pair that we'll use for the top-level future in the dioxus loop
|
// Create the tx/rx pair that we'll use for the top-level future in the dioxus loop
|
||||||
let (tx, rx) = unbounded();
|
let (tx, rx) = unbounded();
|
||||||
@@ -62,8 +70,34 @@ fn make_ws(tx: UnboundedSender<HotReloadMsg>, poll_interval: i32, reload: bool)
|
|||||||
web_sys::console::error_1(&"Connection to the devserver was closed".into())
|
web_sys::console::error_1(&"Connection to the devserver was closed".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The devserver is telling us that it started a full rebuild. This does not mean that it is ready.
|
||||||
|
Ok(DevserverMsg::FullReloadStart) => show_toast(
|
||||||
|
"Your app is being rebuilt.",
|
||||||
|
"A non-hot-reloadable change occurred and we must rebuild.",
|
||||||
|
ToastLevel::Info,
|
||||||
|
TOAST_TIMEOUT,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
// The devserver is telling us that the full rebuild failed.
|
||||||
|
Ok(DevserverMsg::FullReloadFailed) => show_toast(
|
||||||
|
"Oops! The build failed.",
|
||||||
|
"We tried to rebuild your app, but something went wrong.",
|
||||||
|
ToastLevel::Error,
|
||||||
|
TOAST_TIMEOUT,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
|
||||||
// The devserver is telling us to reload the whole page
|
// The devserver is telling us to reload the whole page
|
||||||
Ok(DevserverMsg::FullReload) => window().unwrap().location().reload().unwrap(),
|
Ok(DevserverMsg::FullReloadCommand) => {
|
||||||
|
show_toast(
|
||||||
|
"Successfully rebuilt.",
|
||||||
|
"Your app was rebuilt successfully and without error.",
|
||||||
|
ToastLevel::Success,
|
||||||
|
TOAST_TIMEOUT,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
window().unwrap().location().reload().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
Err(e) => web_sys::console::error_1(
|
Err(e) => web_sys::console::error_1(
|
||||||
&format!("Error parsing devserver message: {}", e).into(),
|
&format!("Error parsing devserver message: {}", e).into(),
|
||||||
@@ -133,6 +167,52 @@ fn make_ws(tx: UnboundedSender<HotReloadMsg>, poll_interval: i32, reload: bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents what color the toast should have.
|
||||||
|
enum ToastLevel {
|
||||||
|
/// Green
|
||||||
|
Success,
|
||||||
|
/// Blue
|
||||||
|
Info,
|
||||||
|
/// Red
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ToastLevel {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ToastLevel::Success => write!(f, "success"),
|
||||||
|
ToastLevel::Info => write!(f, "info"),
|
||||||
|
ToastLevel::Error => write!(f, "error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays a toast to the developer.
|
||||||
|
fn show_toast(
|
||||||
|
header_text: &str,
|
||||||
|
message: &str,
|
||||||
|
level: ToastLevel,
|
||||||
|
duration: Duration,
|
||||||
|
after_reload: bool,
|
||||||
|
) {
|
||||||
|
let as_ms = duration.as_millis();
|
||||||
|
|
||||||
|
let js_fn_name = match after_reload {
|
||||||
|
true => "scheduleDXToast",
|
||||||
|
false => "showDXToast",
|
||||||
|
};
|
||||||
|
|
||||||
|
ScopeId::ROOT.in_runtime(|| {
|
||||||
|
eval(&format!(
|
||||||
|
r#"
|
||||||
|
if (typeof {js_fn_name} !== "undefined") {{
|
||||||
|
{js_fn_name}("{header_text}", "{message}", "{level}", {as_ms});
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Force a hotreload of the assets on this page by walking them and changing their URLs to include
|
/// Force a hotreload of the assets on this page by walking them and changing their URLs to include
|
||||||
/// some extra entropy.
|
/// some extra entropy.
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in new issue
Block a user