fix desktop hot reloading

This commit is contained in:
Evan Almloff 2023-07-18 11:55:56 -07:00
parent 0c5025ffa0
commit 2c1c147828
15 changed files with 130 additions and 115 deletions

View file

@ -48,7 +48,7 @@ fn BlogList(cx: Scope) -> Element {
fn BlogPost(cx: Scope) -> Element { fn BlogPost(cx: Scope) -> Element {
let Some(id) = use_route(cx).segment("id") else { let Some(id) = use_route(cx).segment("id") else {
return cx.render(rsx! { div { "No blog post id" } }) return cx.render(rsx! { div { "No blog post id" } });
}; };
log::debug!("rendering blog post {}", id); log::debug!("rendering blog post {}", id);

View file

@ -364,6 +364,7 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
.display() .display()
); );
} }
println!("build desktop done");
Ok(BuildResult { Ok(BuildResult {
warnings: vec![], warnings: vec![],

View file

@ -57,21 +57,7 @@ impl Serve {
.await?; .await?;
} }
cfg::Platform::Desktop => { cfg::Platform::Desktop => {
crate::builder::build_desktop(&crate_config, true)?; server::desktop::startup(crate_config.clone()).await?;
match &crate_config.executable {
crate::ExecutableType::Binary(name)
| crate::ExecutableType::Lib(name)
| crate::ExecutableType::Example(name) => {
let mut file = crate_config.out_dir.join(name);
if cfg!(windows) {
file.set_extension("exe");
}
Command::new(file.to_str().unwrap())
.stdout(Stdio::inherit())
.output()?;
}
}
} }
} }
Ok(()) Ok(())

View file

@ -3,7 +3,7 @@ use crate::{
serve::Serve, serve::Serve,
server::{ server::{
output::{print_console_info, PrettierOptions, WebServerInfo}, output::{print_console_info, PrettierOptions, WebServerInfo},
setup_file_watcher, setup_file_watcher_hot_reload, BuildManager, setup_file_watcher, setup_file_watcher_hot_reload,
}, },
BuildResult, CrateConfig, Result, BuildResult, CrateConfig, Result,
}; };
@ -72,7 +72,15 @@ pub async fn serve_default(config: CrateConfig) -> Result<()> {
// We got to own watcher so that it exists for the duration of serve // We got to own watcher so that it exists for the duration of serve
// Otherwise full reload won't work. // Otherwise full reload won't work.
let _watcher = setup_file_watcher(crate::cfg::Platform::Desktop, &config, None, None).await?; let _watcher = setup_file_watcher(
{
let config = config.clone();
move || start_desktop(&config)
},
&config,
None,
)
.await?;
// Print serve info // Print serve info
print_console_info( print_console_info(
@ -107,15 +115,13 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> {
} }
let file_map = Arc::new(Mutex::new(map)); let file_map = Arc::new(Mutex::new(map));
let build_manager = Arc::new(BuildManager {
platform: crate::cfg::Platform::Web,
config: config.clone(),
reload_tx: None,
});
let (hot_reload_tx, mut hot_reload_rx) = broadcast::channel(100); let (hot_reload_tx, mut hot_reload_rx) = broadcast::channel(100);
// States // States
// The open interprocess sockets
let channels = Arc::new(Mutex::new(Vec::new()));
// Setup file watcher // Setup file watcher
// We got to own watcher so that it exists for the duration of serve // We got to own watcher so that it exists for the duration of serve
// Otherwise hot reload won't work. // Otherwise hot reload won't work.
@ -123,7 +129,17 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> {
&config, &config,
hot_reload_tx, hot_reload_tx,
file_map.clone(), file_map.clone(),
build_manager, {
let config = config.clone();
let channels = channels.clone();
move || {
for channel in &mut *channels.lock().unwrap() {
send_msg(HotReloadMsg::Shutdown, channel);
}
start_desktop(&config)
}
},
None, None,
) )
.await?; .await?;
@ -153,8 +169,6 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> {
} }
} }
let channels = Arc::new(Mutex::new(Vec::new()));
match LocalSocketListener::bind("@dioxusin") { match LocalSocketListener::bind("@dioxusin") {
Ok(local_socket_stream) => { Ok(local_socket_stream) => {
let aborted = Arc::new(Mutex::new(false)); let aborted = Arc::new(Mutex::new(false));
@ -196,10 +210,6 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> {
} }
}); });
// for channel in &mut *channels.lock().unwrap() {
// send_msg(HotReloadMsg::Shutdown, channel);
// }
while let Ok(template) = hot_reload_rx.recv().await { while let Ok(template) = hot_reload_rx.recv().await {
let channels = &mut *channels.lock().unwrap(); let channels = &mut *channels.lock().unwrap();
let mut i = 0; let mut i = 0;

View file

@ -39,18 +39,11 @@ pub mod desktop;
pub mod web; pub mod web;
/// Sets up a file watcher /// Sets up a file watcher
async fn setup_file_watcher( async fn setup_file_watcher<F: Fn() -> Result<BuildResult> + Send + 'static>(
platform: Platform, build_with: F,
config: &CrateConfig, config: &CrateConfig,
reload_tx: Option<Sender<()>>,
web_info: Option<WebServerInfo>, web_info: Option<WebServerInfo>,
) -> Result<RecommendedWatcher> { ) -> Result<RecommendedWatcher> {
let build_manager = BuildManager {
platform,
config: config.clone(),
reload_tx,
};
let mut last_update_time = chrono::Local::now().timestamp(); let mut last_update_time = chrono::Local::now().timestamp();
// file watcher: check file change // file watcher: check file change
@ -67,7 +60,7 @@ async fn setup_file_watcher(
let config = watcher_config.clone(); let config = watcher_config.clone();
if let Ok(e) = info { if let Ok(e) = info {
if chrono::Local::now().timestamp() > last_update_time { if chrono::Local::now().timestamp() > last_update_time {
match build_manager.rebuild() { match build_with() {
Ok(res) => { Ok(res) => {
last_update_time = chrono::Local::now().timestamp(); last_update_time = chrono::Local::now().timestamp();
@ -108,11 +101,11 @@ async fn setup_file_watcher(
// Todo: reduce duplication and merge with setup_file_watcher() // Todo: reduce duplication and merge with setup_file_watcher()
/// Sets up a file watcher with hot reload /// Sets up a file watcher with hot reload
async fn setup_file_watcher_hot_reload( async fn setup_file_watcher_hot_reload<F: Fn() -> Result<BuildResult> + Send + 'static>(
config: &CrateConfig, config: &CrateConfig,
hot_reload_tx: Sender<Template<'static>>, hot_reload_tx: Sender<Template<'static>>,
file_map: Arc<Mutex<FileMap<HtmlCtx>>>, file_map: Arc<Mutex<FileMap<HtmlCtx>>>,
build_manager: Arc<BuildManager>, build_with: F,
web_info: Option<WebServerInfo>, web_info: Option<WebServerInfo>,
) -> Result<RecommendedWatcher> { ) -> Result<RecommendedWatcher> {
// file watcher: check file change // file watcher: check file change
@ -138,7 +131,7 @@ async fn setup_file_watcher_hot_reload(
for path in evt.paths.clone() { for path in evt.paths.clone() {
// if this is not a rust file, rebuild the whole project // if this is not a rust file, rebuild the whole project
if path.extension().and_then(|p| p.to_str()) != Some("rs") { if path.extension().and_then(|p| p.to_str()) != Some("rs") {
match build_manager.rebuild() { match build_with() {
Ok(res) => { Ok(res) => {
print_console_info( print_console_info(
&config, &config,
@ -164,7 +157,7 @@ async fn setup_file_watcher_hot_reload(
messages.extend(msgs); messages.extend(msgs);
} }
Ok(UpdateResult::NeedsRebuild) => { Ok(UpdateResult::NeedsRebuild) => {
match build_manager.rebuild() { match build_with() {
Ok(res) => { Ok(res) => {
print_console_info( print_console_info(
&config, &config,
@ -209,35 +202,3 @@ async fn setup_file_watcher_hot_reload(
Ok(watcher) Ok(watcher)
} }
pub struct BuildManager {
platform: Platform,
config: CrateConfig,
reload_tx: Option<broadcast::Sender<()>>,
}
impl BuildManager {
fn rebuild(&self) -> Result<BuildResult> {
log::info!("🪁 Rebuild project");
match self.platform {
Platform::Web => {
let result = builder::build(&self.config, true)?;
// change the websocket reload state to true;
// the page will auto-reload.
if self
.config
.dioxus_config
.web
.watcher
.reload_html
.unwrap_or(false)
{
let _ = Serve::regen_dev_page(&self.config);
}
let _ = self.reload_tx.as_ref().map(|tx| tx.send(()));
Ok(result)
}
Platform::Desktop => start_desktop(&self.config),
}
}
}

View file

@ -10,12 +10,10 @@ use dioxus_html::HtmlCtx;
use dioxus_rsx::hot_reload::FileMap; use dioxus_rsx::hot_reload::FileMap;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use super::BuildManager;
use crate::CrateConfig; use crate::CrateConfig;
pub struct HotReloadState { pub struct HotReloadState {
pub messages: broadcast::Sender<Template<'static>>, pub messages: broadcast::Sender<Template<'static>>,
pub build_manager: Arc<BuildManager>,
pub file_map: Arc<Mutex<FileMap<HtmlCtx>>>, pub file_map: Arc<Mutex<FileMap<HtmlCtx>>>,
pub watcher_config: CrateConfig, pub watcher_config: CrateConfig,
} }

View file

@ -3,7 +3,7 @@ use crate::{
serve::Serve, serve::Serve,
server::{ server::{
output::{print_console_info, PrettierOptions, WebServerInfo}, output::{print_console_info, PrettierOptions, WebServerInfo},
setup_file_watcher, setup_file_watcher_hot_reload, BuildManager, setup_file_watcher, setup_file_watcher_hot_reload,
}, },
BuildResult, CrateConfig, Result, BuildResult, CrateConfig, Result,
}; };
@ -86,9 +86,12 @@ pub async fn serve_default(
// We got to own watcher so that it exists for the duration of serve // We got to own watcher so that it exists for the duration of serve
// Otherwise full reload won't work. // Otherwise full reload won't work.
let _watcher = setup_file_watcher( let _watcher = setup_file_watcher(
crate::cfg::Platform::Web, {
let config = config.clone();
let reload_tx = reload_tx.clone();
move || build(&config, &reload_tx)
},
&config, &config,
Some(reload_tx.clone()),
Some(WebServerInfo { Some(WebServerInfo {
ip: ip.clone(), ip: ip.clone(),
port, port,
@ -148,18 +151,12 @@ pub async fn serve_hot_reload(
} }
let file_map = Arc::new(Mutex::new(map)); let file_map = Arc::new(Mutex::new(map));
let build_manager = Arc::new(BuildManager {
platform: crate::cfg::Platform::Web,
config: config.clone(),
reload_tx: Some(reload_tx.clone()),
});
let hot_reload_tx = broadcast::channel(100).0; let hot_reload_tx = broadcast::channel(100).0;
// States // States
let hot_reload_state = Arc::new(HotReloadState { let hot_reload_state = Arc::new(HotReloadState {
messages: hot_reload_tx.clone(), messages: hot_reload_tx.clone(),
build_manager: build_manager.clone(),
file_map: file_map.clone(), file_map: file_map.clone(),
watcher_config: config.clone(), watcher_config: config.clone(),
}); });
@ -175,7 +172,11 @@ pub async fn serve_hot_reload(
&config, &config,
hot_reload_tx, hot_reload_tx,
file_map, file_map,
build_manager, {
let config = config.clone();
let reload_tx = reload_tx.clone();
move || build(&config, &reload_tx)
},
Some(WebServerInfo { Some(WebServerInfo {
ip: ip.clone(), ip: ip.clone(),
port, port,
@ -473,3 +474,20 @@ async fn ws_handler(
reload_watcher.await.unwrap(); reload_watcher.await.unwrap();
}) })
} }
fn build(config: &CrateConfig, reload_tx: &Sender<()>) -> Result<BuildResult> {
let result = builder::build(&config, true)?;
// change the websocket reload state to true;
// the page will auto-reload.
if config
.dioxus_config
.web
.watcher
.reload_html
.unwrap_or(false)
{
let _ = Serve::regen_dev_page(&config);
}
let _ = reload_tx.send(());
Ok(result)
}

View file

@ -378,7 +378,7 @@ impl ServerFnHandler {
.to_vec() .to_vec()
.into(); .into();
let body = hyper::body::to_bytes(req.body_mut().unwrap()).await; let body = hyper::body::to_bytes(req.body_mut().unwrap()).await;
let Ok(body)=body else { let Ok(body) = body else {
handle_error(body.err().unwrap(), res); handle_error(body.err().unwrap(), res);
return; return;
}; };

View file

@ -56,10 +56,9 @@ where
{ {
use serde::Deserialize; use serde::Deserialize;
let Ok(file_engine) = let Ok(file_engine) = SerializedFileEngine::deserialize(deserializer) else {
SerializedFileEngine::deserialize(deserializer) else{ return Ok(None);
return Ok(None); };
};
let file_engine = std::sync::Arc::new(file_engine); let file_engine = std::sync::Arc::new(file_engine);
Ok(Some(file_engine)) Ok(Some(file_engine))

View file

@ -82,7 +82,9 @@ impl Button {
fn write_value(&self, rdom: &mut RealDom) { fn write_value(&self, rdom: &mut RealDom) {
if let Some(mut text) = rdom.get_mut(self.text_id) { if let Some(mut text) = rdom.get_mut(self.text_id) {
let node_type = text.node_type_mut(); let node_type = text.node_type_mut();
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Text(mut text) = node_type else {
panic!("input must be an element")
};
*text.text_mut() = self.value.clone(); *text.text_mut() = self.value.clone();
} }
} }
@ -111,7 +113,9 @@ impl CustomElement for Button {
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
let node_type = root.node_type(); let node_type = root.node_type();
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; let NodeType::Element(el) = &*node_type else {
panic!("input must be an element")
};
let value = el let value = el
.attributes .attributes
@ -146,7 +150,9 @@ impl CustomElement for Button {
AttributeMask::All => { AttributeMask::All => {
{ {
let node_type = root.node_type_mut(); let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
self.update_value_attr(&el); self.update_value_attr(&el);
self.update_size_attr(&mut el); self.update_size_attr(&mut el);
} }
@ -155,7 +161,9 @@ impl CustomElement for Button {
AttributeMask::Some(attrs) => { AttributeMask::Some(attrs) => {
{ {
let node_type = root.node_type_mut(); let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
if attrs.contains("width") || attrs.contains("height") { if attrs.contains("width") || attrs.contains("height") {
self.update_size_attr(&mut el); self.update_size_attr(&mut el);
} }

View file

@ -94,14 +94,18 @@ impl CheckBox {
fn write_value(&self, mut root: NodeMut) { fn write_value(&self, mut root: NodeMut) {
let single_char = { let single_char = {
let node_type = root.node_type_mut(); let node_type = root.node_type_mut();
let NodeTypeMut::Element( el) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Element(el) = node_type else {
panic!("input must be an element")
};
Self::width(&el) == "1px" || Self::height(&el) == "1px" Self::width(&el) == "1px" || Self::height(&el) == "1px"
}; };
let rdom = root.real_dom_mut(); let rdom = root.real_dom_mut();
if let Some(mut text) = rdom.get_mut(self.text_id) { if let Some(mut text) = rdom.get_mut(self.text_id) {
let node_type = text.node_type_mut(); let node_type = text.node_type_mut();
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Text(mut text) = node_type else {
panic!("input must be an element")
};
let value = if single_char { let value = if single_char {
if self.checked { if self.checked {
"" ""
@ -156,7 +160,9 @@ impl CustomElement for CheckBox {
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
let node_type = root.node_type(); let node_type = root.node_type();
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; let NodeType::Element(el) = &*node_type else {
panic!("input must be an element")
};
let value = el let value = el
.attributes .attributes
@ -197,7 +203,9 @@ impl CustomElement for CheckBox {
AttributeMask::All => { AttributeMask::All => {
{ {
let node_type = root.node_type_mut(); let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
self.update_value_attr(&el); self.update_value_attr(&el);
self.update_size_attr(&mut el); self.update_size_attr(&mut el);
self.update_checked_attr(&el); self.update_checked_attr(&el);
@ -207,7 +215,9 @@ impl CustomElement for CheckBox {
AttributeMask::Some(attrs) => { AttributeMask::Some(attrs) => {
{ {
let node_type = root.node_type_mut(); let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
if attrs.contains("width") || attrs.contains("height") { if attrs.contains("width") || attrs.contains("height") {
self.update_size_attr(&mut el); self.update_size_attr(&mut el);
} }

View file

@ -56,7 +56,9 @@ impl CustomElement for Input {
} }
let node_type = root.node_type(); let node_type = root.node_type();
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; let NodeType::Element(el) = &*node_type else {
panic!("input must be an element")
};
let input_type = el let input_type = el
.attributes .attributes
.get(&OwnedAttributeDiscription { .get(&OwnedAttributeDiscription {

View file

@ -163,7 +163,9 @@ impl Slider {
if let Some(mut div) = rdom.get_mut(self.pre_cursor_div) { if let Some(mut div) = rdom.get_mut(self.pre_cursor_div) {
let node_type = div.node_type_mut(); let node_type = div.node_type_mut();
let NodeTypeMut::Element(mut element) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Element(mut element) = node_type else {
panic!("input must be an element")
};
element.set_attribute( element.set_attribute(
OwnedAttributeDiscription { OwnedAttributeDiscription {
name: "width".to_string(), name: "width".to_string(),
@ -175,7 +177,9 @@ impl Slider {
if let Some(mut div) = rdom.get_mut(self.post_cursor_div) { if let Some(mut div) = rdom.get_mut(self.post_cursor_div) {
let node_type = div.node_type_mut(); let node_type = div.node_type_mut();
let NodeTypeMut::Element(mut element) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Element(mut element) = node_type else {
panic!("input must be an element")
};
element.set_attribute( element.set_attribute(
OwnedAttributeDiscription { OwnedAttributeDiscription {
name: "width".to_string(), name: "width".to_string(),
@ -259,7 +263,9 @@ impl CustomElement for Slider {
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
let node_type = root.node_type(); let node_type = root.node_type();
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; let NodeType::Element(el) = &*node_type else {
panic!("input must be an element")
};
let value = el.attributes.get(&OwnedAttributeDiscription { let value = el.attributes.get(&OwnedAttributeDiscription {
name: "value".to_string(), name: "value".to_string(),
@ -390,7 +396,9 @@ impl CustomElement for Slider {
AttributeMask::All => { AttributeMask::All => {
{ {
let node_type = root.node_type_mut(); let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
self.update_value_attr(&el); self.update_value_attr(&el);
self.update_size_attr(&mut el); self.update_size_attr(&mut el);
self.update_max_attr(&el); self.update_max_attr(&el);
@ -403,7 +411,9 @@ impl CustomElement for Slider {
AttributeMask::Some(attrs) => { AttributeMask::Some(attrs) => {
{ {
let node_type = root.node_type_mut(); let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
if attrs.contains("width") || attrs.contains("height") { if attrs.contains("width") || attrs.contains("height") {
self.update_size_attr(&mut el); self.update_size_attr(&mut el);
} }

View file

@ -143,19 +143,25 @@ impl<C: TextLikeController> TextLike<C> {
if let Some(mut text) = rdom.get_mut(self.pre_cursor_text) { if let Some(mut text) = rdom.get_mut(self.pre_cursor_text) {
let node_type = text.node_type_mut(); let node_type = text.node_type_mut();
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Text(mut text) = node_type else {
panic!("input must be an element")
};
*text.text_mut() = self.controller.display_text(text_before_first_cursor); *text.text_mut() = self.controller.display_text(text_before_first_cursor);
} }
if let Some(mut text) = rdom.get_mut(self.highlighted_text) { if let Some(mut text) = rdom.get_mut(self.highlighted_text) {
let node_type = text.node_type_mut(); let node_type = text.node_type_mut();
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Text(mut text) = node_type else {
panic!("input must be an element")
};
*text.text_mut() = self.controller.display_text(text_highlighted); *text.text_mut() = self.controller.display_text(text_highlighted);
} }
if let Some(mut text) = rdom.get_mut(self.post_cursor_text) { if let Some(mut text) = rdom.get_mut(self.post_cursor_text) {
let node_type = text.node_type_mut(); let node_type = text.node_type_mut();
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Text(mut text) = node_type else {
panic!("input must be an element")
};
*text.text_mut() = self.controller.display_text(text_after_second_cursor); *text.text_mut() = self.controller.display_text(text_after_second_cursor);
} }
@ -288,7 +294,9 @@ impl<C: TextLikeController + Send + Sync + Default + 'static> CustomElement for
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
let node_type = root.node_type(); let node_type = root.node_type();
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; let NodeType::Element(el) = &*node_type else {
panic!("input must be an element")
};
let value = el let value = el
.attributes .attributes
@ -370,7 +378,9 @@ impl<C: TextLikeController + Send + Sync + Default + 'static> CustomElement for
AttributeMask::All => { AttributeMask::All => {
{ {
let node_type = root.node_type_mut(); let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
self.update_value_attr(&el); self.update_value_attr(&el);
self.update_size_attr(&mut el); self.update_size_attr(&mut el);
self.update_max_width_attr(&el); self.update_max_width_attr(&el);
@ -381,7 +391,9 @@ impl<C: TextLikeController + Send + Sync + Default + 'static> CustomElement for
AttributeMask::Some(attrs) => { AttributeMask::Some(attrs) => {
{ {
let node_type = root.node_type_mut(); let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
if attrs.contains("width") || attrs.contains("height") { if attrs.contains("width") || attrs.contains("height") {
self.update_size_attr(&mut el); self.update_size_attr(&mut el);
} }

View file

@ -38,7 +38,7 @@ fn BlogList(cx: Scope) -> Element {
fn BlogPost(cx: Scope) -> Element { fn BlogPost(cx: Scope) -> Element {
let Some(id) = use_route(cx).segment("id") else { let Some(id) = use_route(cx).segment("id") else {
return cx.render(rsx! { div { "No blog post id" } }) return cx.render(rsx! { div { "No blog post id" } });
}; };
log::trace!("rendering blog post {}", id); log::trace!("rendering blog post {}", id);