Resume window position/size, watch cargo/dioxus tomls, fix css reverting during hotreloading, allow menubar events to be captured from within dioxus (#2116)
* Fix: css hotreloading being invalidated, watcher not watching cargo/dioxus tomls, add feature to restore window state * Make clappy hippier * remove console log * use simpler css invalidator * Less flash, remove log on web hotreload * Fix floating window managed behavior on mac * clippy...
This commit is contained in:
1 parent
44e997f7df
commit
e923c6462c
17 files changed
+303
-40
No files matched your search
Generated
+12
@@ -2153,6 +2153,7 @@ name = "dioxus-desktop"
|
|||||||
version = "0.5.0-alpha.2"
|
version = "0.5.0-alpha.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"cocoa",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
"dioxus-cli-config",
|
"dioxus-cli-config",
|
||||||
@@ -2175,6 +2176,7 @@ dependencies = [
|
|||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"signal-hook",
|
||||||
"slab",
|
"slab",
|
||||||
"tao",
|
"tao",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
@@ -7869,6 +7871,16 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()
|
|||||||
.exec()
|
.exec()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let target_dir = metadata.target_directory.as_std_path();
|
let target_dir = metadata.target_directory.as_std_path();
|
||||||
|
|
||||||
let _ = create_dir_all(target_dir); // `_all` is for good measure and future-proofness.
|
let _ = create_dir_all(target_dir); // `_all` is for good measure and future-proofness.
|
||||||
let path = target_dir.join("dioxusin");
|
let path = target_dir.join("dioxusin");
|
||||||
clear_paths(&path);
|
clear_paths(&path);
|
||||||
@@ -141,6 +142,7 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()
|
|||||||
.flat_map(|v| v.templates.values().copied())
|
.flat_map(|v| v.templates.values().copied())
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
for template in templates {
|
for template in templates {
|
||||||
if !send_msg(
|
if !send_msg(
|
||||||
HotReloadMsg::UpdateTemplate(template),
|
HotReloadMsg::UpdateTemplate(template),
|
||||||
@@ -282,7 +284,29 @@ impl DesktopPlatform {
|
|||||||
config: &CrateConfig,
|
config: &CrateConfig,
|
||||||
rust_flags: Option<String>,
|
rust_flags: Option<String>,
|
||||||
) -> Result<BuildResult> {
|
) -> Result<BuildResult> {
|
||||||
self.currently_running_child.0.kill()?;
|
// Gracefully shtudown the desktop app
|
||||||
|
// It might have a receiver to do some cleanup stuff
|
||||||
|
let pid = self.currently_running_child.0.id();
|
||||||
|
|
||||||
|
// on unix, we can send a signal to the process to shut down
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
_ = Command::new("kill")
|
||||||
|
.args(["-s", "TERM", &pid.to_string()])
|
||||||
|
.spawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// on windows, use the `taskkill` command
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
_ = Command::new("taskkill")
|
||||||
|
.args(["/F", "/PID", &pid.to_string()])
|
||||||
|
.spawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Todo: add a timeout here to kill the process if it doesn't shut down within a reasonable time
|
||||||
|
self.currently_running_child.0.wait()?;
|
||||||
|
|
||||||
let (child, result) = start_desktop(config, self.skip_assets, rust_flags)?;
|
let (child, result) = start_desktop(config, self.skip_assets, rust_flags)?;
|
||||||
self.currently_running_child = child;
|
self.currently_running_child = child;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
|||||||
@@ -56,9 +56,14 @@ async fn setup_file_watcher<F: Fn() -> Result<BuildResult> + Send + 'static>(
|
|||||||
// file watcher: check file change
|
// file watcher: check file change
|
||||||
let mut allow_watch_path = config.dioxus_config.web.watcher.watch_path.clone();
|
let mut allow_watch_path = config.dioxus_config.web.watcher.watch_path.clone();
|
||||||
|
|
||||||
// Extend the watch path to include the assets directory - this is so we can hotreload CSS and other assets
|
// Extend the watch path to include the assets directory - this is so we can hotreload CSS and other assets by default
|
||||||
allow_watch_path.push(config.dioxus_config.application.asset_dir.clone());
|
allow_watch_path.push(config.dioxus_config.application.asset_dir.clone());
|
||||||
|
|
||||||
|
// Extend the watch path to include Cargo.toml and Dioxus.toml
|
||||||
|
allow_watch_path.push("Cargo.toml".to_string().into());
|
||||||
|
allow_watch_path.push("Dioxus.toml".to_string().into());
|
||||||
|
allow_watch_path.dedup();
|
||||||
|
|
||||||
// Create the file watcher
|
// Create the file watcher
|
||||||
let mut watcher = notify::recommended_watcher({
|
let mut watcher = notify::recommended_watcher({
|
||||||
let watcher_config = config.clone();
|
let watcher_config = config.clone();
|
||||||
@@ -66,7 +71,6 @@ async fn setup_file_watcher<F: Fn() -> Result<BuildResult> + Send + 'static>(
|
|||||||
let Ok(e) = info else {
|
let Ok(e) = info else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
watch_event(
|
watch_event(
|
||||||
e,
|
e,
|
||||||
&mut last_update_time,
|
&mut last_update_time,
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ pub fn print_console_info(
|
|||||||
.watch_path
|
.watch_path
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.chain(Some(config.dioxus_config.application.asset_dir.clone()))
|
.chain(["Cargo.toml", "Dioxus.toml"].iter().map(PathBuf::from))
|
||||||
.map(|f| f.display().to_string())
|
.map(|f| f.display().to_string())
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ futures-util = { workspace = true }
|
|||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
async-trait = "0.1.68"
|
async-trait = "0.1.68"
|
||||||
tao = { version = "0.26.1", features = ["rwh_05"] }
|
tao = { version = "0.26.1", features = ["rwh_05"] }
|
||||||
|
signal-hook = "0.3.17"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
|
[target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
|
||||||
global-hotkey = "0.5.0"
|
global-hotkey = "0.5.0"
|
||||||
@@ -62,6 +63,7 @@ objc = "0.2.7"
|
|||||||
objc_id = "0.1.1"
|
objc_id = "0.1.1"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
cocoa = "0.25"
|
||||||
core-foundation = "0.9.3"
|
core-foundation = "0.9.3"
|
||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
|
|
||||||
|
|||||||
+145
-1
@@ -18,6 +18,7 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use tao::{
|
use tao::{
|
||||||
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
event::Event,
|
event::Event,
|
||||||
event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget},
|
event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget},
|
||||||
window::WindowId,
|
window::WindowId,
|
||||||
@@ -35,6 +36,7 @@ pub(crate) struct App {
|
|||||||
pub(crate) is_visible_before_start: bool,
|
pub(crate) is_visible_before_start: bool,
|
||||||
pub(crate) window_behavior: WindowCloseBehaviour,
|
pub(crate) window_behavior: WindowCloseBehaviour,
|
||||||
pub(crate) webviews: HashMap<WindowId, WebviewInstance>,
|
pub(crate) webviews: HashMap<WindowId, WebviewInstance>,
|
||||||
|
pub(crate) float_all: bool,
|
||||||
|
|
||||||
/// This single blob of state is shared between all the windows so they have access to the runtime state
|
/// This single blob of state is shared between all the windows so they have access to the runtime state
|
||||||
///
|
///
|
||||||
@@ -61,6 +63,7 @@ impl App {
|
|||||||
webviews: HashMap::new(),
|
webviews: HashMap::new(),
|
||||||
control_flow: ControlFlow::Wait,
|
control_flow: ControlFlow::Wait,
|
||||||
unmounted_dom: Cell::new(Some(virtual_dom)),
|
unmounted_dom: Cell::new(Some(virtual_dom)),
|
||||||
|
float_all: cfg!(debug_assertions),
|
||||||
cfg: Cell::new(Some(cfg)),
|
cfg: Cell::new(Some(cfg)),
|
||||||
shared: Rc::new(SharedContext {
|
shared: Rc::new(SharedContext {
|
||||||
event_handlers: WindowEventHandlers::default(),
|
event_handlers: WindowEventHandlers::default(),
|
||||||
@@ -78,6 +81,10 @@ impl App {
|
|||||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||||
app.set_global_hotkey_handler();
|
app.set_global_hotkey_handler();
|
||||||
|
|
||||||
|
// Wire up the menubar receiver - this way any component can key into the menubar actions
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||||
|
app.set_menubar_receiver();
|
||||||
|
|
||||||
// Allow hotreloading to work - but only in debug mode
|
// Allow hotreloading to work - but only in debug mode
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "hot-reload",
|
feature = "hot-reload",
|
||||||
@@ -87,6 +94,10 @@ impl App {
|
|||||||
))]
|
))]
|
||||||
app.connect_hotreload();
|
app.connect_hotreload();
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||||
|
app.connect_preserve_window_state_handler();
|
||||||
|
|
||||||
(event_loop, app)
|
(event_loop, app)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +113,20 @@ impl App {
|
|||||||
self.shared.shortcut_manager.call_handlers(event);
|
self.shared.shortcut_manager.call_handlers(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||||
|
pub fn handle_menu_event(&mut self, event: muda::MenuEvent) {
|
||||||
|
if event.id() == "dioxus-float-top" {
|
||||||
|
for webview in self.webviews.values() {
|
||||||
|
webview
|
||||||
|
.desktop_context
|
||||||
|
.window
|
||||||
|
.set_always_on_top(self.float_all);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.float_all = !self.float_all;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "hot-reload",
|
feature = "hot-reload",
|
||||||
debug_assertions,
|
debug_assertions,
|
||||||
@@ -109,7 +134,11 @@ impl App {
|
|||||||
not(target_os = "ios")
|
not(target_os = "ios")
|
||||||
))]
|
))]
|
||||||
pub fn connect_hotreload(&self) {
|
pub fn connect_hotreload(&self) {
|
||||||
dioxus_hot_reload::connect({
|
let Ok(cfg) = dioxus_cli_config::CURRENT_CONFIG.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
dioxus_hot_reload::connect_at(cfg.target_dir.join("dioxusin"), {
|
||||||
let proxy = self.shared.proxy.clone();
|
let proxy = self.shared.proxy.clone();
|
||||||
move |template| {
|
move |template| {
|
||||||
let _ = proxy.send_event(UserWindowEvent::HotReloadEvent(template));
|
let _ = proxy.send_event(UserWindowEvent::HotReloadEvent(template));
|
||||||
@@ -169,6 +198,10 @@ impl App {
|
|||||||
|
|
||||||
let webview = WebviewInstance::new(cfg, virtual_dom, self.shared.clone());
|
let webview = WebviewInstance::new(cfg, virtual_dom, self.shared.clone());
|
||||||
|
|
||||||
|
// And then attempt to resume from state
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
self.resume_from_state(&webview);
|
||||||
|
|
||||||
let id = webview.desktop_context.window.id();
|
let id = webview.desktop_context.window.id();
|
||||||
self.webviews.insert(id, webview);
|
self.webviews.insert(id, webview);
|
||||||
}
|
}
|
||||||
@@ -356,6 +389,117 @@ impl App {
|
|||||||
_ = receiver.send_event(UserWindowEvent::GlobalHotKeyEvent(t));
|
_ = receiver.send_event(UserWindowEvent::GlobalHotKeyEvent(t));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||||
|
fn set_menubar_receiver(&self) {
|
||||||
|
let receiver = self.shared.proxy.clone();
|
||||||
|
|
||||||
|
// The event loop becomes the menu receiver
|
||||||
|
// This means we don't need to poll the receiver on every tick - we just get the events as they come in
|
||||||
|
// This is a bit more efficient than the previous implementation, but if someone else sets a handler, the
|
||||||
|
// receiver will become inert.
|
||||||
|
muda::MenuEvent::set_event_handler(Some(move |t| {
|
||||||
|
// todo: should we unset the event handler when the app shuts down?
|
||||||
|
_ = receiver.send_event(UserWindowEvent::MudaMenuEvent(t));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do our best to preserve state about the window when the event loop is destroyed
|
||||||
|
///
|
||||||
|
/// This will attempt to save the window position, size, and monitor into the environment before
|
||||||
|
/// closing. This way, when the app is restarted, it can attempt to restore the window to the same
|
||||||
|
/// position and size it was in before, making a better DX.
|
||||||
|
pub(crate) fn handle_loop_destroyed(&self) {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
self.persist_window_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
fn persist_window_state(&self) {
|
||||||
|
if let Some(webview) = self.webviews.values().next() {
|
||||||
|
let window = &webview.desktop_context.window;
|
||||||
|
|
||||||
|
let monitor = window.current_monitor().unwrap();
|
||||||
|
let position = window.outer_position().unwrap();
|
||||||
|
let size = window.outer_size();
|
||||||
|
|
||||||
|
let x = position.x;
|
||||||
|
let y = position.y;
|
||||||
|
|
||||||
|
// This is to work around a bug in how tao handles inner_size on macOS
|
||||||
|
// We *want* to use inner_size, but that's currently broken, so we use outer_size instead and then an adjustment
|
||||||
|
//
|
||||||
|
// https://github.com/tauri-apps/tao/issues/889
|
||||||
|
let adjustment = match window.is_decorated() {
|
||||||
|
true if cfg!(target_os = "macos") => 56,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = PreservedWindowState {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width: size.width.max(200),
|
||||||
|
height: size.height.saturating_sub(adjustment).max(200),
|
||||||
|
monitor: monitor.name().unwrap().to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(state) = serde_json::to_string(&state) {
|
||||||
|
// Write this to the target dir so we can pick back up in resume_from_state
|
||||||
|
if let Ok(cfg) = dioxus_cli_config::CURRENT_CONFIG.as_ref() {
|
||||||
|
let path = cfg.target_dir.join("window_state.json");
|
||||||
|
_ = std::fs::write(path, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write this to the target dir so we can pick back up
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
fn resume_from_state(&mut self, webview: &WebviewInstance) {
|
||||||
|
if let Ok(cfg) = dioxus_cli_config::CURRENT_CONFIG.as_ref() {
|
||||||
|
let path = cfg.target_dir.join("window_state.json");
|
||||||
|
if let Ok(state) = std::fs::read_to_string(path) {
|
||||||
|
if let Ok(state) = serde_json::from_str::<PreservedWindowState>(&state) {
|
||||||
|
let window = &webview.desktop_context.window;
|
||||||
|
let position = (state.x, state.y);
|
||||||
|
let size = (state.width, state.height);
|
||||||
|
window.set_outer_position(PhysicalPosition::new(position.0, position.1));
|
||||||
|
window.set_inner_size(PhysicalSize::new(size.0, size.1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wire up a receiver to sigkill that lets us preserve the window state
|
||||||
|
/// Whenever sigkill is sent, we shut down the app and save the window state
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
fn connect_preserve_window_state_handler(&self) {
|
||||||
|
// Wire up the trap
|
||||||
|
let target = self.shared.proxy.clone();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
use signal_hook::consts::{SIGINT, SIGTERM};
|
||||||
|
let sigkill = signal_hook::iterator::Signals::new([SIGTERM, SIGINT]);
|
||||||
|
if let Ok(mut sigkill) = sigkill {
|
||||||
|
for _ in sigkill.forever() {
|
||||||
|
if target.send_event(UserWindowEvent::Shutdown).is_err() {
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// give it a moment for the event to be processed
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
struct PreservedWindowState {
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
monitor: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Different hide implementations per platform
|
/// Different hide implementations per platform
|
||||||
|
|||||||
@@ -41,12 +41,15 @@ impl Config {
|
|||||||
/// Initializes a new `WindowBuilder` with default values.
|
/// Initializes a new `WindowBuilder` with default values.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let window = WindowBuilder::new().with_title(
|
let window: WindowBuilder = WindowBuilder::new()
|
||||||
dioxus_cli_config::CURRENT_CONFIG
|
.with_title(
|
||||||
.as_ref()
|
dioxus_cli_config::CURRENT_CONFIG
|
||||||
.map(|c| c.dioxus_config.application.name.clone())
|
.as_ref()
|
||||||
.unwrap_or("Dioxus App".to_string()),
|
.map(|c| c.dioxus_config.application.name.clone())
|
||||||
);
|
.unwrap_or("Dioxus App".to_string()),
|
||||||
|
)
|
||||||
|
// During development we want the window to be on top so we can see it while we work
|
||||||
|
.with_always_on_top(cfg!(debug_assertions));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
window,
|
window,
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tao::window::WindowId;
|
use tao::window::WindowId;
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum UserWindowEvent {
|
pub enum UserWindowEvent {
|
||||||
/// A global hotkey event
|
/// A global hotkey event
|
||||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||||
GlobalHotKeyEvent(global_hotkey::GlobalHotKeyEvent),
|
GlobalHotKeyEvent(global_hotkey::GlobalHotKeyEvent),
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||||
|
MudaMenuEvent(muda::MenuEvent),
|
||||||
|
|
||||||
/// Poll the virtualdom
|
/// Poll the virtualdom
|
||||||
Poll(WindowId),
|
Poll(WindowId),
|
||||||
|
|
||||||
@@ -27,6 +31,9 @@ pub enum UserWindowEvent {
|
|||||||
|
|
||||||
/// Close a given window (could be any window!)
|
/// Close a given window (could be any window!)
|
||||||
CloseWindow(WindowId),
|
CloseWindow(WindowId),
|
||||||
|
|
||||||
|
/// Gracefully shutdown the entire app
|
||||||
|
Shutdown,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A message struct that manages the communication between the webview and the eventloop code
|
/// A message struct that manages the communication between the webview and the eventloop code
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Conf
|
|||||||
|
|
||||||
match window_event {
|
match window_event {
|
||||||
Event::NewEvents(StartCause::Init) => app.handle_start_cause_init(),
|
Event::NewEvents(StartCause::Init) => app.handle_start_cause_init(),
|
||||||
|
Event::LoopDestroyed => app.handle_loop_destroyed(),
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
event, window_id, ..
|
event, window_id, ..
|
||||||
} => match event {
|
} => match event {
|
||||||
@@ -32,10 +33,14 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Conf
|
|||||||
UserWindowEvent::Poll(id) => app.poll_vdom(id),
|
UserWindowEvent::Poll(id) => app.poll_vdom(id),
|
||||||
UserWindowEvent::NewWindow => app.handle_new_window(),
|
UserWindowEvent::NewWindow => app.handle_new_window(),
|
||||||
UserWindowEvent::CloseWindow(id) => app.handle_close_msg(id),
|
UserWindowEvent::CloseWindow(id) => app.handle_close_msg(id),
|
||||||
|
UserWindowEvent::Shutdown => app.control_flow = tao::event_loop::ControlFlow::Exit,
|
||||||
|
|
||||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||||
UserWindowEvent::GlobalHotKeyEvent(evnt) => app.handle_global_hotkey(evnt),
|
UserWindowEvent::GlobalHotKeyEvent(evnt) => app.handle_global_hotkey(evnt),
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||||
|
UserWindowEvent::MudaMenuEvent(evnt) => app.handle_menu_event(evnt),
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "hot-reload",
|
feature = "hot-reload",
|
||||||
debug_assertions,
|
debug_assertions,
|
||||||
|
|||||||
@@ -93,6 +93,16 @@ mod desktop_platforms {
|
|||||||
.append_items(&[&MenuItem::new("Toggle Developer Tools", true, None)])
|
.append_items(&[&MenuItem::new("Toggle Developer Tools", true, None)])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
// By default we float the window on top in dev mode, but let the user disable it
|
||||||
|
help_menu
|
||||||
|
.append_items(&[&MenuItem::with_id(
|
||||||
|
"dioxus-float-top",
|
||||||
|
"Float on Top (dev mode only)",
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
)])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
menu.append_items(&[&window_menu, &edit_menu, &help_menu])
|
menu.append_items(&[&window_menu, &edit_menu, &help_menu])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,20 @@ impl WebviewInstance {
|
|||||||
|
|
||||||
let window = window.build(&shared.target).unwrap();
|
let window = window.build(&shared.target).unwrap();
|
||||||
|
|
||||||
|
// https://developer.apple.com/documentation/appkit/nswindowcollectionbehavior/nswindowcollectionbehaviormanaged
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
use cocoa::appkit::NSWindowCollectionBehavior;
|
||||||
|
use cocoa::base::id;
|
||||||
|
use objc::{msg_send, sel, sel_impl};
|
||||||
|
use tao::platform::macos::WindowExtMacOS;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let window: id = window.ns_window() as id;
|
||||||
|
let _: () = msg_send![window, setCollectionBehavior: NSWindowCollectionBehavior::NSWindowCollectionBehaviorManaged];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut web_context = WebContext::new(cfg.data_dir.clone());
|
let mut web_context = WebContext::new(cfg.data_dir.clone());
|
||||||
let edit_queue = EditQueue::default();
|
let edit_queue = EditQueue::default();
|
||||||
let file_hover = NativeFileHover::default();
|
let file_hover = NativeFileHover::default();
|
||||||
@@ -131,7 +145,18 @@ impl WebviewInstance {
|
|||||||
.with_transparent(cfg.window.window.transparent)
|
.with_transparent(cfg.window.window.transparent)
|
||||||
.with_url("dioxus://index.html/")
|
.with_url("dioxus://index.html/")
|
||||||
.with_ipc_handler(ipc_handler)
|
.with_ipc_handler(ipc_handler)
|
||||||
.with_navigation_handler(|var| var.contains("dioxus")) // prevent all navigations
|
.with_navigation_handler(|var| {
|
||||||
|
// We don't want to allow any navigation
|
||||||
|
// We only want to serve the index file and assets
|
||||||
|
if var.starts_with("dioxus://") || var.starts_with("http://dioxus.") {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
if var.starts_with("http://") || var.starts_with("https://") {
|
||||||
|
_ = webbrowser::open(&var);
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}) // prevent all navigations
|
||||||
.with_asynchronous_custom_protocol(String::from("dioxus"), request_handler)
|
.with_asynchronous_custom_protocol(String::from("dioxus"), request_handler)
|
||||||
.with_web_context(&mut web_context)
|
.with_web_context(&mut web_context)
|
||||||
.with_file_drop_handler(file_drop_handler);
|
.with_file_drop_handler(file_drop_handler);
|
||||||
@@ -228,8 +253,9 @@ impl WebviewInstance {
|
|||||||
pub fn kick_stylsheets(&self) {
|
pub fn kick_stylsheets(&self) {
|
||||||
// run eval in the webview to kick the stylesheets by appending a query string
|
// run eval in the webview to kick the stylesheets by appending a query string
|
||||||
// we should do something less clunky than this
|
// we should do something less clunky than this
|
||||||
_ = self.desktop_context
|
_ = self
|
||||||
|
.desktop_context
|
||||||
.webview
|
.webview
|
||||||
.evaluate_script("document.querySelectorAll('link[rel=\"stylesheet\"]').forEach((el) => el.href = el.href + \"?\" + Math.random());");
|
.evaluate_script("window.interpreter.kickAllStylesheetsOnPage()");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,8 @@ pub use dioxus_core;
|
|||||||
#[cfg_attr(docsrs, doc(cfg(feature = "launch")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "launch")))]
|
||||||
mod launch;
|
mod launch;
|
||||||
|
|
||||||
|
pub use launch::launch;
|
||||||
|
|
||||||
#[cfg(feature = "hooks")]
|
#[cfg(feature = "hooks")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "hooks")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "hooks")))]
|
||||||
pub use dioxus_hooks as hooks;
|
pub use dioxus_hooks as hooks;
|
||||||
|
|||||||
@@ -29,32 +29,40 @@ pub enum HotReloadMsg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected
|
/// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected
|
||||||
pub fn connect(mut callback: impl FnMut(HotReloadMsg) + Send + 'static) {
|
pub fn connect(callback: impl FnMut(HotReloadMsg) + Send + 'static) {
|
||||||
std::thread::spawn(move || {
|
let Ok(_manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") else {
|
||||||
// get the cargo manifest directory, where the target dir lives
|
return;
|
||||||
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
};
|
||||||
|
|
||||||
// walk the path until we a find a socket named `dioxusin` inside that folder's target directory
|
// get the cargo manifest directory, where the target dir lives
|
||||||
loop {
|
let mut path = PathBuf::from(_manifest_dir);
|
||||||
let maybe = path.join("target").join("dioxusin");
|
|
||||||
|
|
||||||
if maybe.exists() {
|
// walk the path until we a find a socket named `dioxusin` inside that folder's target directory
|
||||||
path = maybe;
|
loop {
|
||||||
break;
|
let maybe = path.join("target").join("dioxusin");
|
||||||
}
|
|
||||||
|
|
||||||
// It's likely we're running under just cargo and not dx
|
if maybe.exists() {
|
||||||
path = match path.parent() {
|
path = maybe;
|
||||||
Some(parent) => parent.to_path_buf(),
|
break;
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It's likely we're running under just cargo and not dx
|
||||||
|
path = match path.parent() {
|
||||||
|
Some(parent) => parent.to_path_buf(),
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
connect_at(path, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect_at(socket: PathBuf, mut callback: impl FnMut(HotReloadMsg) + Send + 'static) {
|
||||||
|
std::thread::spawn(move || {
|
||||||
// There might be a socket since the we're not running under the hot reloading server
|
// There might be a socket since the we're not running under the hot reloading server
|
||||||
let Ok(socket) = LocalSocketStream::connect(path.clone()) else {
|
let Ok(socket) = LocalSocketStream::connect(socket.clone()) else {
|
||||||
println!(
|
println!(
|
||||||
"could not find hot reloading server at {:?}, make sure it's running",
|
"could not find hot reloading server at {:?}, make sure it's running",
|
||||||
path
|
socket
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
12655652627
|
13799725074
|
||||||
@@ -1 +1 @@
|
|||||||
function retriveValues(event,target){let contents={values:{}},form=target.closest("form");if(form){if(event.type==="input"||event.type==="change"||event.type==="submit"||event.type==="reset"||event.type==="click")contents=retrieveFormValues(form)}return contents}function retrieveFormValues(form){const formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(contents[key])contents[key].push(value);else contents[key]=[value]}),{valid:form.checkValidity(),values:contents}}function retriveSelectValue(target){let options=target.selectedOptions,values=[];for(let i=0;i<options.length;i++)values.push(options[i].value);return values}function serializeEvent(event,target){let contents={},extend=(obj)=>contents={...contents,...obj};if(event instanceof WheelEvent)extend(serializeWheelEvent(event));if(event instanceof MouseEvent)extend(serializeMouseEvent(event));if(event instanceof KeyboardEvent)extend(serializeKeyboardEvent(event));if(event instanceof InputEvent)extend(serializeInputEvent(event,target));if(event instanceof PointerEvent)extend(serializePointerEvent(event));if(event instanceof AnimationEvent)extend(serializeAnimationEvent(event));if(event instanceof TransitionEvent)extend({property_name:event.propertyName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement});if(event instanceof CompositionEvent)extend({data:event.data});if(event instanceof DragEvent)extend(serializeDragEvent(event));if(event instanceof FocusEvent)extend({});if(event instanceof ClipboardEvent)extend({});if(typeof TouchEvent!=="undefined"&&event instanceof TouchEvent)extend(serializeTouchEvent(event));if(event.type==="submit"||event.type==="reset"||event.type==="click"||event.type==="change"||event.type==="input")extend(serializeInputEvent(event,target));if(event instanceof DragEvent);return contents}var serializeInputEvent=function(event,target){let contents={};if(target instanceof HTMLElement){let values=retriveValues(event,target);contents.values=values.values,contents.valid=values.valid}if(event.target instanceof HTMLInputElement){let target2=event.target,value=target2.value??target2.textContent??"";if(target2.type==="checkbox")value=target2.checked?"true":"false";else if(target2.type==="radio")value=target2.value;contents.value=value}if(event.target instanceof HTMLTextAreaElement)contents.value=event.target.value;if(event.target instanceof HTMLSelectElement)contents.value=retriveSelectValue(event.target).join(",");if(contents.value===void 0)contents.value="";return contents},serializeWheelEvent=function(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}},serializeTouchEvent=function(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:event.changedTouches,target_touches:event.targetTouches,touches:event.touches}},serializePointerEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey,pointer_id:event.pointerId,width:event.width,height:event.height,pressure:event.pressure,tangential_pressure:event.tangentialPressure,tilt_x:event.tiltX,tilt_y:event.tiltY,twist:event.twist,pointer_type:event.pointerType,is_primary:event.isPrimary}},serializeMouseEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,offset_x:event.offsetX,offset_y:event.offsetY,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey}},serializeKeyboardEvent=function(event){return{char_code:event.charCode,is_composing:event.isComposing,key:event.key,alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,key_code:event.keyCode,shift_key:event.shiftKey,location:event.location,repeat:event.repeat,which:event.which,code:event.code}},serializeAnimationEvent=function(event){return{animation_name:event.animationName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement}},serializeDragEvent=function(event){return{mouse:{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,...serializeMouseEvent(event)},files:{files:{a:[1,2,3]}}}};var getTargetId=function(target){if(!(target instanceof Node))return null;let ourTarget=target,realId=null;while(realId==null){if(ourTarget===null)return null;if(ourTarget instanceof Element)realId=ourTarget.getAttribute("data-dioxus-id");ourTarget=ourTarget.parentNode}return parseInt(realId)},JSChannel_;if(RawInterpreter!==void 0&&RawInterpreter!==null)JSChannel_=RawInterpreter;class NativeInterpreter extends JSChannel_{intercept_link_redirects;ipc;editsPath;liveview;constructor(editsPath){super();this.editsPath=editsPathLine truncated
|
function retriveValues(event,target){let contents={values:{}},form=target.closest("form");if(form){if(event.type==="input"||event.type==="change"||event.type==="submit"||event.type==="reset"||event.type==="click")contents=retrieveFormValues(form)}return contents}function retrieveFormValues(form){const formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(contents[key])contents[key].push(value);else contents[key]=[value]}),{valid:form.checkValidity(),values:contents}}function retriveSelectValue(target){let options=target.selectedOptions,values=[];for(let i=0;i<options.length;i++)values.push(options[i].value);return values}function serializeEvent(event,target){let contents={},extend=(obj)=>contents={...contents,...obj};if(event instanceof WheelEvent)extend(serializeWheelEvent(event));if(event instanceof MouseEvent)extend(serializeMouseEvent(event));if(event instanceof KeyboardEvent)extend(serializeKeyboardEvent(event));if(event instanceof InputEvent)extend(serializeInputEvent(event,target));if(event instanceof PointerEvent)extend(serializePointerEvent(event));if(event instanceof AnimationEvent)extend(serializeAnimationEvent(event));if(event instanceof TransitionEvent)extend({property_name:event.propertyName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement});if(event instanceof CompositionEvent)extend({data:event.data});if(event instanceof DragEvent)extend(serializeDragEvent(event));if(event instanceof FocusEvent)extend({});if(event instanceof ClipboardEvent)extend({});if(typeof TouchEvent!=="undefined"&&event instanceof TouchEvent)extend(serializeTouchEvent(event));if(event.type==="submit"||event.type==="reset"||event.type==="click"||event.type==="change"||event.type==="input")extend(serializeInputEvent(event,target));if(event instanceof DragEvent);return contents}var serializeInputEvent=function(event,target){let contents={};if(target instanceof HTMLElement){let values=retriveValues(event,target);contents.values=values.values,contents.valid=values.valid}if(event.target instanceof HTMLInputElement){let target2=event.target,value=target2.value??target2.textContent??"";if(target2.type==="checkbox")value=target2.checked?"true":"false";else if(target2.type==="radio")value=target2.value;contents.value=value}if(event.target instanceof HTMLTextAreaElement)contents.value=event.target.value;if(event.target instanceof HTMLSelectElement)contents.value=retriveSelectValue(event.target).join(",");if(contents.value===void 0)contents.value="";return contents},serializeWheelEvent=function(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}},serializeTouchEvent=function(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:event.changedTouches,target_touches:event.targetTouches,touches:event.touches}},serializePointerEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey,pointer_id:event.pointerId,width:event.width,height:event.height,pressure:event.pressure,tangential_pressure:event.tangentialPressure,tilt_x:event.tiltX,tilt_y:event.tiltY,twist:event.twist,pointer_type:event.pointerType,is_primary:event.isPrimary}},serializeMouseEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,offset_x:event.offsetX,offset_y:event.offsetY,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey}},serializeKeyboardEvent=function(event){return{char_code:event.charCode,is_composing:event.isComposing,key:event.key,alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,key_code:event.keyCode,shift_key:event.shiftKey,location:event.location,repeat:event.repeat,which:event.which,code:event.code}},serializeAnimationEvent=function(event){return{animation_name:event.animationName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement}},serializeDragEvent=function(event){return{mouse:{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,...serializeMouseEvent(event)},files:{files:{a:[1,2,3]}}}};var getTargetId=function(target){if(!(target instanceof Node))return null;let ourTarget=target,realId=null;while(realId==null){if(ourTarget===null)return null;if(ourTarget instanceof Element)realId=ourTarget.getAttribute("data-dioxus-id");ourTarget=ourTarget.parentNode}return parseInt(realId)},JSChannel_;if(RawInterpreter!==void 0&&RawInterpreter!==null)JSChannel_=RawInterpreter;class NativeInterpreter extends JSChannel_{intercept_link_redirects;ipc;editsPath;kickStylesheets;liveview;constructor(editsPath){super();this.ediLine truncated
|
||||||
@@ -21,6 +21,7 @@ export class NativeInterpreter extends JSChannel_ {
|
|||||||
intercept_link_redirects: boolean;
|
intercept_link_redirects: boolean;
|
||||||
ipc: any;
|
ipc: any;
|
||||||
editsPath: string;
|
editsPath: string;
|
||||||
|
kickStylesheets: boolean;
|
||||||
|
|
||||||
// eventually we want to remove liveview and build it into the server-side-events of fullstack
|
// eventually we want to remove liveview and build it into the server-side-events of fullstack
|
||||||
// however, for now we need to support it since SSE in fullstack doesn't exist yet
|
// however, for now we need to support it since SSE in fullstack doesn't exist yet
|
||||||
@@ -29,6 +30,7 @@ export class NativeInterpreter extends JSChannel_ {
|
|||||||
constructor(editsPath: string) {
|
constructor(editsPath: string) {
|
||||||
super();
|
super();
|
||||||
this.editsPath = editsPath;
|
this.editsPath = editsPath;
|
||||||
|
this.kickStylesheets = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize(root: HTMLElement): void {
|
initialize(root: HTMLElement): void {
|
||||||
@@ -272,14 +274,30 @@ export class NativeInterpreter extends JSChannel_ {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.run_from_bytes(bytes);
|
this.run_from_bytes(bytes);
|
||||||
} else {
|
} else {
|
||||||
// @ts-ignore
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => this.run_from_bytes(bytes));
|
// @ts-ignore
|
||||||
|
this.run_from_bytes(bytes)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.waitForRequest(headless);
|
this.waitForRequest(headless);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
kickAllStylesheetsOnPage() {
|
||||||
|
// If this function is being called and we have not explicitly set kickStylesheets to true, then we should
|
||||||
|
// force kick the stylesheets, regardless if they have a dioxus attribute or not
|
||||||
|
// This happens when any hotreload happens.
|
||||||
|
let stylesheets = document.querySelectorAll("link[rel=stylesheet]");
|
||||||
|
for (let i = 0; i < stylesheets.length; i++) {
|
||||||
|
let sheet = stylesheets[i] as HTMLLinkElement;
|
||||||
|
// Using `cache: reload` will force the browser to re-fetch the stylesheet and bust the cache
|
||||||
|
fetch(sheet.href, { cache: "reload" }).then(() => {
|
||||||
|
sheet.href = sheet.href + "?" + Math.random();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A liveview only function
|
// A liveview only function
|
||||||
// Desktop will intercept the event before it hits this
|
// Desktop will intercept the event before it hits this
|
||||||
async readFiles(target: HTMLInputElement, contents: SerializedEvent, bubbles: boolean, realId: NodeId, name: string) {
|
async readFiles(target: HTMLInputElement, contents: SerializedEvent, bubbles: boolean, realId: NodeId, name: string) {
|
||||||
|
|||||||
@@ -50,14 +50,12 @@ pub(crate) fn init() -> UnboundedReceiver<Template> {
|
|||||||
.query_selector_all("link[rel=stylesheet]")
|
.query_selector_all("link[rel=stylesheet]")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
console::log_1(&links.clone().into());
|
let noise = js_sys::Math::random();
|
||||||
|
|
||||||
for x in 0..links.length() {
|
for x in 0..links.length() {
|
||||||
console::log_1(&x.into());
|
|
||||||
|
|
||||||
let link: Element = links.get(x).unwrap().unchecked_into();
|
let link: Element = links.get(x).unwrap().unchecked_into();
|
||||||
let href = link.get_attribute("href").unwrap();
|
let href = link.get_attribute("href").unwrap();
|
||||||
_ = link.set_attribute("href", &format!("{}?{}", href, js_sys::Math::random()));
|
_ = link.set_attribute("href", &format!("{}?{}", href, noise));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user