Merge branch 'master' into breaking

This commit is contained in:
Jonathan Kelley committed 2024-01-30 16:20:19 -08:00
commit eff1dd6c90
29 files changed
+684 -258

No files matched your search

+18 -11
View File
@@ -338,11 +338,9 @@ pub struct WebHttpsConfig {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrateConfig {
pub out_dir: PathBuf,
pub crate_dir: PathBuf,
pub workspace_dir: PathBuf,
pub target_dir: PathBuf,
pub asset_dir: PathBuf,
#[cfg(feature = "cli")]
pub manifest: cargo_toml::Manifest<cargo_toml::Value>,
pub executable: ExecutableType,
@@ -383,12 +381,8 @@ impl CrateConfig {
let workspace_dir = meta.workspace_root;
let target_dir = meta.target_directory;
let out_dir = crate_dir.join(&dioxus_config.application.out_dir);
let cargo_def = &crate_dir.join("Cargo.toml");
let asset_dir = crate_dir.join(&dioxus_config.application.asset_dir);
let manifest = cargo_toml::Manifest::from_path(cargo_def).unwrap();
let mut output_filename = String::from("dioxus_app");
@@ -414,6 +408,7 @@ impl CrateConfig {
let release = false;
let hot_reload = false;
let cross_origin_policy = false;
let verbose = false;
let custom_profile = None;
let features = None;
@@ -421,26 +416,38 @@ impl CrateConfig {
let cargo_args = vec![];
Ok(Self {
out_dir,
crate_dir,
workspace_dir,
target_dir,
asset_dir,
#[cfg(feature = "cli")]
manifest,
executable,
release,
dioxus_config,
release,
hot_reload,
cross_origin_policy: false,
cross_origin_policy,
verbose,
custom_profile,
features,
verbose,
target,
cargo_args,
})
}
/// Compose an asset directory. Represents the typical "public" directory
/// with publicly available resources (configurable in the `Dioxus.toml`).
pub fn asset_dir(&self) -> PathBuf {
self.crate_dir
.join(&self.dioxus_config.application.asset_dir)
}
/// Compose an out directory. Represents the typical "dist" directory that
/// is "distributed" after building an application (configurable in the
/// `Dioxus.toml`).
pub fn out_dir(&self) -> PathBuf {
self.crate_dir.join(&self.dioxus_config.application.out_dir)
}
pub fn as_example(&mut self, example_name: String) -> &mut Self {
self.executable = ExecutableType::Example(example_name);
self
+8 -9
View File
@@ -21,12 +21,13 @@ log = "0.4.14"
fern = { version = "0.6.0", features = ["colored"] }
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
toml = "0.8.8"
fs_extra = "1.2.0"
cargo_toml = "0.16.0"
futures = "0.3.21"
cargo_toml = "0.18.0"
futures-util = { workspace = true }
notify = { version = "5.0.0-pre.16", features = ["serde"] }
html_parser = { workspace = true }
cargo_metadata = "0.15.0"
cargo_metadata = "0.18.1"
tokio = { version = "1.16.1", features = ["fs", "sync", "rt", "macros"] }
atty = "0.2.14"
chrono = "0.4.19"
@@ -44,7 +45,7 @@ headers = "0.3.7"
walkdir = "2"
# tools download
dirs = "4.0.0"
dirs = "5.0.1"
reqwest = { version = "0.11", features = [
"rustls-tls",
"stream",
@@ -55,7 +56,6 @@ flate2 = "1.0.22"
tar = "0.4.38"
zip = "0.6.2"
tower = "0.4.12"
syn = { version = "2.0", features = ["full", "extra-traits"] }
lazy_static = "1.4.0"
# plugin packages
@@ -67,13 +67,12 @@ mlua = { version = "0.8.1", features = [
"macros",
], optional = true }
ctrlc = "3.2.3"
open = "4.1.0"
cargo-generate = "0.18"
toml_edit = "0.19.11"
open = "5.0.1"
cargo-generate = "0.19.0"
toml_edit = "0.21.0"
# bundling
tauri-bundler = { version = "=1.4.*", features = ["native-tls-vendored"] }
tauri-utils = "=1.5.*"
manganis-cli-support = { workspace = true, features = ["webp", "html"] }
+2 -2
View File
@@ -13,7 +13,7 @@ pub fn asset_manifest(crate_config: &CrateConfig) -> AssetManifest {
/// Create a head file that contains all of the imports for assets that the user project uses
pub fn create_assets_head(config: &CrateConfig, manifest: &AssetManifest) -> Result<()> {
let mut file = File::create(config.out_dir.join("__assets_head.html"))?;
let mut file = File::create(config.out_dir().join("__assets_head.html"))?;
file.write_all(manifest.head().as_bytes())?;
Ok(())
}
@@ -29,7 +29,7 @@ pub(crate) fn process_assets(config: &CrateConfig, manifest: &AssetManifest) ->
.clone()
.unwrap_or_default(),
);
let static_asset_output_dir = config.out_dir.join(static_asset_output_dir);
let static_asset_output_dir = config.out_dir().join(static_asset_output_dir);
manifest.copy_static_assets_to(static_asset_output_dir)?;
+21 -21
View File
@@ -39,14 +39,14 @@ pub fn build(config: &CrateConfig, _: bool, skip_assets: bool) -> Result<BuildRe
// [6] Link up the html page to the wasm module
let CrateConfig {
out_dir,
crate_dir,
target_dir,
asset_dir,
executable,
dioxus_config,
..
} = config;
let out_dir = config.out_dir();
let asset_dir = config.asset_dir();
let _guard = WebAssetConfigDropGuard::new();
let _manganis_support = ManganisSupportGuard::default();
@@ -251,15 +251,15 @@ pub fn build(config: &CrateConfig, _: bool, skip_assets: bool) -> Result<BuildRe
if path.is_file() {
std::fs::copy(&path, out_dir.join(path.file_name().unwrap()))?;
} else {
match fs_extra::dir::copy(&path, out_dir, &copy_options) {
match fs_extra::dir::copy(&path, &out_dir, &copy_options) {
Ok(_) => {}
Err(_e) => {
log::warn!("Error copying dir: {}", _e);
}
}
for ignore in &ignore_files {
let ignore = ignore.strip_prefix(&config.asset_dir).unwrap();
let ignore = config.out_dir.join(ignore);
let ignore = ignore.strip_prefix(&config.asset_dir()).unwrap();
let ignore = out_dir.join(ignore);
if ignore.is_file() {
std::fs::remove_file(ignore)?;
}
@@ -369,13 +369,13 @@ pub fn build_desktop(
file_name
};
if !config.out_dir.is_dir() {
create_dir_all(&config.out_dir)?;
if !config.out_dir().is_dir() {
create_dir_all(config.out_dir())?;
}
copy(res_path, config.out_dir.join(target_file))?;
copy(res_path, config.out_dir().join(target_file))?;
// this code will copy all public file to the output dir
if config.asset_dir.is_dir() {
if config.asset_dir().is_dir() {
let copy_options = fs_extra::dir::CopyOptions {
overwrite: true,
skip_exist: false,
@@ -385,20 +385,20 @@ pub fn build_desktop(
depth: 0,
};
for entry in std::fs::read_dir(&config.asset_dir)? {
for entry in std::fs::read_dir(config.asset_dir())? {
let path = entry?.path();
if path.is_file() {
std::fs::copy(&path, &config.out_dir.join(path.file_name().unwrap()))?;
std::fs::copy(&path, &config.out_dir().join(path.file_name().unwrap()))?;
} else {
match fs_extra::dir::copy(&path, &config.out_dir, &copy_options) {
match fs_extra::dir::copy(&path, &config.out_dir(), &copy_options) {
Ok(_) => {}
Err(e) => {
log::warn!("Error copying dir: {}", e);
}
}
for ignore in &ignore_files {
let ignore = ignore.strip_prefix(&config.asset_dir).unwrap();
let ignore = config.out_dir.join(ignore);
let ignore = ignore.strip_prefix(&config.asset_dir()).unwrap();
let ignore = config.out_dir().join(ignore);
if ignore.is_file() {
std::fs::remove_file(ignore)?;
}
@@ -443,7 +443,7 @@ fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result<Vec<Diagnostic>> {
.unwrap()
.tick_chars("/|\\- "),
);
pb.set_message("💼 Waiting to start build the project...");
pb.set_message("💼 Waiting to start building the project...");
let stdout = cmd.detached().stream_stdout()?;
let reader = std::io::BufReader::new(stdout);
@@ -635,7 +635,7 @@ fn build_assets(config: &CrateConfig) -> Result<Vec<PathBuf>> {
if file == "*" {
// if the sass open auto, we need auto-check the assets dir.
let asset_dir = config.asset_dir.clone();
let asset_dir = config.asset_dir().clone();
if asset_dir.is_dir() {
for entry in walkdir::WalkDir::new(&asset_dir)
.into_iter()
@@ -655,7 +655,7 @@ fn build_assets(config: &CrateConfig) -> Result<Vec<PathBuf>> {
temp.file_stem().unwrap().to_str().unwrap()
);
let target_path = config
.out_dir
.out_dir()
.join(
temp.strip_prefix(&asset_dir)
.unwrap()
@@ -685,11 +685,11 @@ fn build_assets(config: &CrateConfig) -> Result<Vec<PathBuf>> {
} else {
file
};
let path = config.asset_dir.join(relative_path);
let path = config.asset_dir().join(relative_path);
let out_file =
format!("{}.css", path.file_stem().unwrap().to_str().unwrap());
let target_path = config
.out_dir
.out_dir()
.join(PathBuf::from(relative_path).parent().unwrap())
.join(out_file);
if path.is_file() {
@@ -719,11 +719,11 @@ fn build_assets(config: &CrateConfig) -> Result<Vec<PathBuf>> {
} else {
path
};
let path = config.asset_dir.join(relative_path);
let path = config.asset_dir().join(relative_path);
let out_file =
format!("{}.css", path.file_stem().unwrap().to_str().unwrap());
let target_path = config
.out_dir
.out_dir()
.join(PathBuf::from(relative_path).parent().unwrap())
.join(out_file);
if path.is_file() {
+1 -1
View File
@@ -1,5 +1,5 @@
use dioxus_autofmt::{IndentOptions, IndentType};
use futures::{stream::FuturesUnordered, StreamExt};
use futures_util::{stream::FuturesUnordered, StreamExt};
use std::{fs, path::Path, process::exit};
use super::*;
+2 -2
View File
@@ -87,7 +87,7 @@ impl Bundle {
build_desktop(&crate_config, false, false)?;
// copy the binary to the out dir
let package = crate_config.manifest.package.unwrap();
let package = crate_config.manifest.package.as_ref().unwrap();
let mut name: PathBuf = match &crate_config.executable {
ExecutableType::Binary(name)
@@ -149,7 +149,7 @@ impl Bundle {
}
let mut settings = SettingsBuilder::new()
.project_out_directory(crate_config.out_dir)
.project_out_directory(crate_config.out_dir())
.package_settings(PackageSettings {
product_name: crate_config.dioxus_config.application.name.clone(),
version: package.version().to_string(),
+3 -1
View File
@@ -78,6 +78,7 @@ impl From<ConfigOptsServe> for ConfigOptsBuild {
}
#[derive(Clone, Debug, Default, Deserialize, Parser)]
#[command(group = clap::ArgGroup::new("release-incompatible").multiple(true).conflicts_with("release"))]
pub struct ConfigOptsServe {
/// Port of dev server
#[clap(long)]
@@ -116,8 +117,9 @@ pub struct ConfigOptsServe {
#[clap(long, value_enum)]
pub platform: Option<Platform>,
/// Build with hot reloading rsx [default: false]
/// Build with hot reloading rsx. Will not work with release builds. [default: false]
#[clap(long)]
#[clap(group = "release-incompatible")]
#[serde(default)]
pub hot_reload: bool,
+1 -1
View File
@@ -1,4 +1,4 @@
use futures::{stream::FuturesUnordered, StreamExt};
use futures_util::{stream::FuturesUnordered, StreamExt};
use std::{path::Path, process::exit};
use super::*;
+26 -24
View File
@@ -1,7 +1,7 @@
use dioxus_cli_config::DioxusConfig;
use std::path::PathBuf;
use anyhow::anyhow;
use anyhow::Context;
use clap::Parser;
use dioxus_cli::*;
@@ -19,24 +19,26 @@ fn get_bin(bin: Option<String>) -> Result<PathBuf> {
.workspace_packages()
.into_iter()
.find(|p| p.name == bin)
.ok_or(format!("no such package: {}", bin))
.map_err(Error::CargoError)?
.ok_or(Error::CargoError(format!("no such package: {}", bin)))?
} else {
metadata
.root_package()
.ok_or("no root package?".into())
.map_err(Error::CargoError)?
.ok_or(Error::CargoError("no root package?".to_string()))?
};
let crate_dir = package
.manifest_path
.parent()
.ok_or("couldn't take parent dir".into())
.map_err(Error::CargoError)?;
.ok_or(Error::CargoError("couldn't take parent dir".to_string()))?;
Ok(crate_dir.into())
}
/// Simplifies error messages that use the same pattern.
fn error_wrapper(message: &str) -> String {
format!("🚫 {message}:")
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Cli::parse();
@@ -46,35 +48,35 @@ async fn main() -> anyhow::Result<()> {
match args.action {
Translate(opts) => opts
.translate()
.map_err(|e| anyhow!("🚫 Translation of HTML into RSX failed: {}", e)),
.context(error_wrapper("Translation of HTML into RSX failed")),
Create(opts) => opts
.create()
.map_err(|e| anyhow!("🚫 Creating new project failed: {}", e)),
.context(error_wrapper("Creating new project failed")),
Init(opts) => opts
.init()
.map_err(|e| anyhow!("🚫 Initialising a new project failed: {}", e)),
.context(error_wrapper("Initialising a new project failed")),
Config(opts) => opts
.config()
.map_err(|e| anyhow!("🚫 Configuring new project failed: {}", e)),
.context(error_wrapper("Configuring new project failed")),
#[cfg(feature = "plugin")]
Plugin(opts) => opts
.plugin()
.await
.map_err(|e| anyhow!("🚫 Error with plugin: {}", e)),
.context(error_wrapper("Error with plugin")),
Autoformat(opts) => opts
.autoformat()
.await
.map_err(|e| anyhow!("🚫 Error autoformatting RSX: {}", e)),
.context(error_wrapper("Error autoformatting RSX")),
Check(opts) => opts
.check()
.await
.map_err(|e| anyhow!("🚫 Error checking RSX: {}", e)),
.context(error_wrapper("Error checking RSX")),
Version(opt) => {
let version = opt.version();
@@ -85,33 +87,33 @@ async fn main() -> anyhow::Result<()> {
action => {
let bin = get_bin(args.bin)?;
let _dioxus_config = DioxusConfig::load(Some(bin.clone()))
.map_err(|e| anyhow!("Failed to load Dioxus config because: {e}"))?
.unwrap_or_else(|| {
log::info!("You appear to be creating a Dioxus project from scratch; we will use the default config");
DioxusConfig::default()
});
.context("Failed to load Dioxus config because")?
.unwrap_or_else(|| {
log::info!("You appear to be creating a Dioxus project from scratch; we will use the default config");
DioxusConfig::default()
});
#[cfg(feature = "plugin")]
PluginManager::init(_dioxus_config.plugin)
.map_err(|e| anyhow!("🚫 Plugin system initialization failed: {e}"))?;
.context(error_wrapper("Plugin system initialization failed"))?;
match action {
Build(opts) => opts
.build(Some(bin.clone()), None)
.map_err(|e| anyhow!("🚫 Building project failed: {}", e)),
.context(error_wrapper("Building project failed")),
Clean(opts) => opts
.clean(Some(bin.clone()))
.map_err(|e| anyhow!("🚫 Cleaning project failed: {}", e)),
.context(error_wrapper("Cleaning project failed")),
Serve(opts) => opts
.serve(Some(bin.clone()))
.await
.map_err(|e| anyhow!("🚫 Serving project failed: {}", e)),
.context(error_wrapper("Serving project failed")),
Bundle(opts) => opts
.bundle(Some(bin.clone()))
.map_err(|e| anyhow!("🚫 Bundling project failed: {}", e)),
.context(error_wrapper("Bundling project failed")),
_ => unreachable!(),
}
+4 -4
View File
@@ -181,8 +181,8 @@ impl PluginManager {
let args = lua.create_table()?;
args.set("name", crate_config.dioxus_config.application.name.clone())?;
args.set("platform", platform)?;
args.set("out_dir", crate_config.out_dir.to_str().unwrap())?;
args.set("asset_dir", crate_config.asset_dir.to_str().unwrap())?;
args.set("out_dir", crate_config.out_dir().to_str().unwrap())?;
args.set("asset_dir", crate_config.asset_dir().to_str().unwrap())?;
for i in 1..(manager.len()? as i32 + 1) {
let info = manager.get::<i32, PluginInfo>(i)?;
@@ -205,8 +205,8 @@ impl PluginManager {
let args = lua.create_table()?;
args.set("name", crate_config.dioxus_config.application.name.clone())?;
args.set("platform", platform)?;
args.set("out_dir", crate_config.out_dir.to_str().unwrap())?;
args.set("asset_dir", crate_config.asset_dir.to_str().unwrap())?;
args.set("out_dir", crate_config.out_dir().to_str().unwrap())?;
args.set("asset_dir", crate_config.asset_dir().to_str().unwrap())?;
for i in 1..(manager.len()? as i32 + 1) {
let info = manager.get::<i32, PluginInfo>(i)?;
+1 -1
View File
@@ -221,7 +221,7 @@ fn start_desktop(config: &CrateConfig, skip_assets: bool) -> Result<(RAIIChild,
ExecutableType::Binary(name)
| ExecutableType::Lib(name)
| ExecutableType::Example(name) => {
let mut file = config.out_dir.join(name);
let mut file = config.out_dir().join(name);
if cfg!(windows) {
file.set_extension("exe");
}
+88 -78
View File
@@ -31,93 +31,103 @@ async fn setup_file_watcher<F: Fn() -> Result<BuildResult> + Send + 'static>(
let mut watcher = notify::recommended_watcher(move |info: notify::Result<notify::Event>| {
let config = watcher_config.clone();
if let Ok(e) = info {
if chrono::Local::now().timestamp() > last_update_time {
let mut needs_full_rebuild;
if let Some(hot_reload) = &hot_reload {
// find changes to the rsx in the file
let mut rsx_file_map = hot_reload.file_map.lock().unwrap();
let mut messages: Vec<Template> = Vec::new();
match e.kind {
notify::EventKind::Create(_)
| notify::EventKind::Remove(_)
| notify::EventKind::Modify(_) => {
if chrono::Local::now().timestamp() > last_update_time {
let mut needs_full_rebuild;
if let Some(hot_reload) = &hot_reload {
// find changes to the rsx in the file
let mut rsx_file_map = hot_reload.file_map.lock().unwrap();
let mut messages: Vec<Template> = Vec::new();
// In hot reload mode, we only need to rebuild if non-rsx code is changed
needs_full_rebuild = false;
// In hot reload mode, we only need to rebuild if non-rsx code is changed
needs_full_rebuild = false;
for path in &e.paths {
// if this is not a rust file, rebuild the whole project
if path.extension().and_then(|p| p.to_str()) != Some("rs") {
for path in &e.paths {
// if this is not a rust file, rebuild the whole project
if path.extension().and_then(|p| p.to_str()) != Some("rs") {
needs_full_rebuild = true;
break;
}
// Workaround for notify and vscode-like editor:
// when edit & save a file in vscode, there will be two notifications,
// the first one is a file with empty content.
// filter the empty file notification to avoid false rebuild during hot-reload
if let Ok(metadata) = fs::metadata(path) {
if metadata.len() == 0 {
continue;
}
}
match rsx_file_map.update_rsx(path, &config.crate_dir) {
Ok(UpdateResult::UpdatedRsx(msgs)) => {
messages.extend(msgs);
needs_full_rebuild = false;
}
Ok(UpdateResult::NeedsRebuild) => {
needs_full_rebuild = true;
}
Err(err) => {
log::error!("{}", err);
}
}
}
if needs_full_rebuild {
// Reset the file map to the new state of the project
let FileMapBuildResult {
map: new_file_map,
errors,
} = FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
for err in errors {
log::error!("{}", err);
}
*rsx_file_map = new_file_map;
} else {
for msg in messages {
let _ = hot_reload.messages.send(msg);
}
}
} else {
needs_full_rebuild = true;
break;
}
// Workaround for notify and vscode-like editor:
// when edit & save a file in vscode, there will be two notifications,
// the first one is a file with empty content.
// filter the empty file notification to avoid false rebuild during hot-reload
if let Ok(metadata) = fs::metadata(path) {
if metadata.len() == 0 {
continue;
if needs_full_rebuild {
match build_with() {
Ok(res) => {
last_update_time = chrono::Local::now().timestamp();
#[allow(clippy::redundant_clone)]
print_console_info(
&config,
PrettierOptions {
changed: e.paths.clone(),
warnings: res.warnings,
elapsed_time: res.elapsed_time,
},
web_info.clone(),
);
#[cfg(feature = "plugin")]
let _ = PluginManager::on_serve_rebuild(
chrono::Local::now().timestamp(),
e.paths,
);
}
Err(e) => {
last_update_time = chrono::Local::now().timestamp();
log::error!("{:?}", e);
}
}
}
match rsx_file_map.update_rsx(path, &config.crate_dir) {
Ok(UpdateResult::UpdatedRsx(msgs)) => {
messages.extend(msgs);
needs_full_rebuild = false;
}
Ok(UpdateResult::NeedsRebuild) => {
needs_full_rebuild = true;
}
Err(err) => {
log::error!("{}", err);
}
}
}
if needs_full_rebuild {
// Reset the file map to the new state of the project
let FileMapBuildResult {
map: new_file_map,
errors,
} = FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
for err in errors {
log::error!("{}", err);
}
*rsx_file_map = new_file_map;
} else {
for msg in messages {
let _ = hot_reload.messages.send(msg);
}
}
} else {
needs_full_rebuild = true;
}
if needs_full_rebuild {
match build_with() {
Ok(res) => {
last_update_time = chrono::Local::now().timestamp();
#[allow(clippy::redundant_clone)]
print_console_info(
&config,
PrettierOptions {
changed: e.paths.clone(),
warnings: res.warnings,
elapsed_time: res.elapsed_time,
},
web_info.clone(),
);
#[cfg(feature = "plugin")]
let _ = crate::plugin::PluginManager::on_serve_rebuild(
chrono::Local::now().timestamp(),
e.paths,
);
}
Err(e) => log::error!("{}", e),
}
}
_ => {}
}
}
})
+2 -2
View File
@@ -290,7 +290,7 @@ async fn setup_router(
std::fs::read_to_string(
file_service_config
.crate_dir
.join(file_service_config.out_dir)
.join(file_service_config.out_dir())
.join("index.html"),
)
.ok()
@@ -315,7 +315,7 @@ async fn setup_router(
Ok(response)
},
)
.service(ServeDir::new(config.crate_dir.join(&config.out_dir)));
.service(ServeDir::new(config.crate_dir.join(config.out_dir())));
// Setup websocket
let mut router = Router::new().route("/_dioxus/ws", get(ws_handler));
+1 -1
View File
@@ -7,7 +7,7 @@ use std::{
use anyhow::Context;
use flate2::read::GzDecoder;
use futures::StreamExt;
use futures_util::StreamExt;
use tar::Archive;
use tokio::io::AsyncWriteExt;
+120 -1
View File
@@ -1,6 +1,6 @@
use dioxus::dioxus_core::Mutation::*;
use dioxus::prelude::*;
use dioxus_core::{ElementId, NoOpMutations};
use dioxus_core::{AttributeValue, ElementId, NoOpMutations};
#[test]
fn text_diff() {
@@ -82,3 +82,122 @@ fn element_swap() {
]
);
}
#[test]
fn attribute_diff() {
fn app(cx: Scope) -> Element {
let gen = cx.generation();
// attributes have to be sorted by name
let attrs = match gen % 5 {
0 => cx.bump().alloc([Attribute::new(
"a",
AttributeValue::Text("hello"),
None,
false,
)]) as &[Attribute],
1 => cx.bump().alloc([
Attribute::new("a", AttributeValue::Text("hello"), None, false),
Attribute::new("b", AttributeValue::Text("hello"), None, false),
Attribute::new("c", AttributeValue::Text("hello"), None, false),
]) as &[Attribute],
2 => cx.bump().alloc([
Attribute::new("c", AttributeValue::Text("hello"), None, false),
Attribute::new("d", AttributeValue::Text("hello"), None, false),
Attribute::new("e", AttributeValue::Text("hello"), None, false),
]) as &[Attribute],
3 => cx.bump().alloc([Attribute::new(
"d",
AttributeValue::Text("world"),
None,
false,
)]) as &[Attribute],
_ => unreachable!(),
};
cx.render(rsx!(
div {
..*attrs,
"hello"
}
))
}
let mut vdom = VirtualDom::new(app);
_ = vdom.rebuild();
vdom.mark_dirty(ScopeId::ROOT);
assert_eq!(
vdom.render_immediate().santize().edits,
[
SetAttribute {
name: "b",
value: (&AttributeValue::Text("hello",)).into(),
id: ElementId(1,),
ns: None,
},
SetAttribute {
name: "c",
value: (&AttributeValue::Text("hello",)).into(),
id: ElementId(1,),
ns: None,
},
]
);
vdom.mark_dirty(ScopeId::ROOT);
assert_eq!(
vdom.render_immediate().santize().edits,
[
SetAttribute {
name: "a",
value: (&AttributeValue::None).into(),
id: ElementId(1,),
ns: None,
},
SetAttribute {
name: "b",
value: (&AttributeValue::None).into(),
id: ElementId(1,),
ns: None,
},
SetAttribute {
name: "d",
value: (&AttributeValue::Text("hello",)).into(),
id: ElementId(1,),
ns: None,
},
SetAttribute {
name: "e",
value: (&AttributeValue::Text("hello",)).into(),
id: ElementId(1,),
ns: None,
},
]
);
vdom.mark_dirty(ScopeId::ROOT);
assert_eq!(
vdom.render_immediate().santize().edits,
[
SetAttribute {
name: "c",
value: (&AttributeValue::None).into(),
id: ElementId(1,),
ns: None,
},
SetAttribute {
name: "d",
value: (&AttributeValue::Text("world",)).into(),
id: ElementId(1,),
ns: None,
},
SetAttribute {
name: "e",
value: (&AttributeValue::None).into(),
id: ElementId(1,),
ns: None,
},
]
);
}
+7 -4
View File
@@ -147,7 +147,7 @@ fn app() -> Element {
ctrlKey: false,
metaKey: false,
shiftKey: false,
isComposing: false,
isComposing: true,
which: 65,
bubbles: true,
})"#,
@@ -343,7 +343,8 @@ fn app() -> Element {
assert_eq!(event.data.code().to_string(), "KeyA");
assert_eq!(event.data.location(), Location::Standard);
assert!(event.data.is_auto_repeating());
received_events.with_mut(|x| *x + 1);
assert!(event.data.is_composing());
received_events.with_mut(|x| *x + 1)
}
}
input {
@@ -355,7 +356,8 @@ fn app() -> Element {
assert_eq!(event.data.code().to_string(), "KeyA");
assert_eq!(event.data.location(), Location::Standard);
assert!(!event.data.is_auto_repeating());
received_events.with_mut(|x| *x + 1);
assert!(!event.data.is_composing());
received_events.with_mut(|x| *x + 1)
}
}
input {
@@ -367,7 +369,8 @@ fn app() -> Element {
assert_eq!(event.data.code().to_string(), "KeyA");
assert_eq!(event.data.location(), Location::Standard);
assert!(!event.data.is_auto_repeating());
received_events.with_mut(|x| *x + 1);
assert!(!event.data.is_composing());
received_events.with_mut(|x| *x + 1)
}
}
input {
+1 -1
View File
@@ -64,7 +64,7 @@ mod impl_ {
])
.unwrap();
let edit_menu = Submenu::new("Window", true);
let edit_menu = Submenu::new("Edit", true);
edit_menu
.append_items(&[
&PredefinedMenuItem::undo(None),
+22 -1
View File
@@ -91,7 +91,28 @@ impl WebviewInstance {
}
};
let mut webview = WebViewBuilder::new(&window)
#[cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
))]
let mut webview = WebViewBuilder::new(&window);
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
)))]
let mut webview = {
use tao::platform::unix::WindowExtUnix;
use wry::WebViewBuilderExtUnix;
let vbox = window.default_vbox().unwrap();
WebViewBuilder::new_gtk(vbox)
};
webview = webview
.with_transparent(cfg.window.window.transparent)
.with_url("dioxus://index.html/")
.unwrap()
+195
View File
@@ -1744,4 +1744,199 @@ builder_constructors! {
r#use ["use", "http://www.w3.org/2000/svg"] {
href: String DEFAULT,
};
// MathML elements
/// Build a
/// [`<annotation>`](https://w3c.github.io/mathml-core/#dfn-annotation)
/// element.
annotation "http://www.w3.org/1998/Math/MathML" {
encoding: String DEFAULT,
};
/// Build a
/// [`<annotation-xml>`](https://w3c.github.io/mathml-core/#dfn-annotation-xml)
/// element.
annotationXml ["annotation-xml", "http://www.w3.org/1998/Math/MathML"] {
encoding: String DEFAULT,
};
/// Build a
/// [`<merror>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/merror)
/// element.
merror "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<math>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/math)
/// element.
math "http://www.w3.org/1998/Math/MathML" {
display: String DEFAULT,
};
/// Build a
/// [`<mfrac>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mfrac)
/// element.
mfrac "http://www.w3.org/1998/Math/MathML" {
linethickness: usize DEFAULT,
};
/// Build a
/// [`<mi>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mi)
/// element.
mi "http://www.w3.org/1998/Math/MathML" {
mathvariant: String DEFAULT,
};
/// Build a
/// [`<mmultiscripts>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mmultiscripts)
/// element.
mmultiscripts "http://www.w3.org/1998/math/mathml" {};
/// Build a
/// [`<mn>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mn)
/// element.
mn "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<mo>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mo)
/// element.
mo "http://www.w3.org/1998/Math/MathML" {
fence: Bool DEFAULT,
largeop: Bool DEFAULT,
lspace: usize DEFAULT,
maxsize: usize DEFAULT,
minsize: usize DEFAULT,
movablelimits: Bool DEFAULT,
rspace: usize DEFAULT,
separator: Bool DEFAULT,
stretchy: Bool DEFAULT,
symmetric: Bool DEFAULT,
};
/// Build a
/// [`<mover>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mover)
/// element.
mover "http://www.w3.org/1998/Math/MathML" {
accent: Bool DEFAULT,
};
/// Build a
/// [`<mpadded>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mpadded)
/// element.
mpadded "http://www.w3.org/1998/Math/MathML" {
depth: usize DEFAULT,
height: usize DEFAULT,
lspace: usize DEFAULT,
voffset: usize DEFAULT,
width: usize DEFAULT,
};
/// Build a
/// [`<mphantom>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mphantom)
/// element.
mphantom "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<mprescripts>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mprescripts)
/// element.
mprescripts "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<mroot>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mroot)
/// element.
mroot "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<mrow>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mrow)
/// element.
mrow "http://www.w3.org/1998/Math/MathML" {
};
/// Build a
/// [`<ms>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/ms)
/// element.
ms "http://www.w3.org/1998/Math/MathML" {
lquote: String DEFAULT,
rquote: String DEFAULT,
};
/// Build a
/// [`<mspace>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mspace)
/// element.
mspace "http://www.w3.org/1998/Math/MathML" {
depth: usize DEFAULT,
height: usize DEFAULT,
width: usize DEFAULT,
};
/// Build a
/// [`<msqrt>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msqrt)
/// element.
msqrt "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<mstyle>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mstyle)
/// element.
mstyle "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<msub>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msub)
/// element.
msub "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<msubsup>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msubsup)
/// element.
msubsup "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<msup>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msup)
/// element.
msup "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<mtable>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtable)
/// element.
mtable "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<mtd>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtd)
/// element.
mtd "http://www.w3.org/1998/Math/MathML" {
columnspan: usize DEFAULT,
rowspan: usize DEFAULT,
};
/// Build a
/// [`<mtext>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtext)
/// element.
mtext "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<mtr>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtr)
/// element.
mtr "http://www.w3.org/1998/Math/MathML" {};
/// Build a
/// [`<munder>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/munder)
/// element.
munder "http://www.w3.org/1998/Math/MathML" {
accentunder: Bool DEFAULT,
};
/// Build a
/// [`<munderover>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/munderover)
/// element.
munderover "http://www.w3.org/1998/Math/MathML" {
accent: Bool DEFAULT,
accentunder: Bool DEFAULT,
};
/// Build a
/// [`<semantics>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/semantics)
/// element.
semantics "http://www.w3.org/1998/Math/MathML" {
encoding: String DEFAULT,
};
}
+18
View File
@@ -33,6 +33,7 @@ impl std::fmt::Debug for KeyboardData {
.field("modifiers", &self.modifiers())
.field("location", &self.location())
.field("is_auto_repeating", &self.is_auto_repeating())
.field("is_composing", &self.is_composing())
.finish()
}
}
@@ -44,6 +45,7 @@ impl PartialEq for KeyboardData {
&& self.modifiers() == other.modifiers()
&& self.location() == other.location()
&& self.is_auto_repeating() == other.is_auto_repeating()
&& self.is_composing() == other.is_composing()
}
}
@@ -75,6 +77,11 @@ impl KeyboardData {
self.inner.is_auto_repeating()
}
/// Indicates whether the key is fired within a composition session.
pub fn is_composing(&self) -> bool {
self.inner.is_composing()
}
/// Downcast this KeyboardData to a concrete type.
pub fn downcast<T: 'static>(&self) -> Option<&T> {
self.inner.as_any().downcast_ref::<T>()
@@ -92,6 +99,7 @@ impl ModifiersInteraction for KeyboardData {
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
pub struct SerializedKeyboardData {
char_code: u32,
is_composing: bool,
key: String,
key_code: KeyCode,
#[serde(deserialize_with = "resilient_deserialize_code")]
@@ -114,9 +122,11 @@ impl SerializedKeyboardData {
location: Location,
is_auto_repeating: bool,
modifiers: Modifiers,
is_composing: bool,
) -> Self {
Self {
char_code: key.legacy_charcode(),
is_composing,
key: key.to_string(),
key_code: KeyCode::from_raw_code(
std::convert::TryInto::try_into(key.legacy_keycode())
@@ -144,6 +154,7 @@ impl From<&KeyboardData> for SerializedKeyboardData {
data.location(),
data.is_auto_repeating(),
data.modifiers(),
data.is_composing(),
)
}
}
@@ -166,6 +177,10 @@ impl HasKeyboardData for SerializedKeyboardData {
self.repeat
}
fn is_composing(&self) -> bool {
self.is_composing
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
@@ -236,6 +251,9 @@ pub trait HasKeyboardData: ModifiersInteraction + std::any::Any {
/// `true` iff the key is being held down such that it is automatically repeating.
fn is_auto_repeating(&self) -> bool;
/// Indicates whether the key is fired within a composition session.
fn is_composing(&self) -> bool;
/// return self as Any
fn as_any(&self) -> &dyn std::any::Any;
}
+4
View File
@@ -78,6 +78,10 @@ impl HasKeyboardData for KeyboardEvent {
self.repeat()
}
fn is_composing(&self) -> bool {
self.is_composing()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
+2
View File
@@ -351,6 +351,7 @@ async function serialize_event(event) {
case "keyup": {
let {
charCode,
isComposing,
key,
altKey,
ctrlKey,
@@ -364,6 +365,7 @@ async function serialize_event(event) {
} = event;
return {
char_code: charCode,
is_composing: isComposing,
key: key,
alt_key: altKey,
ctrl_key: ctrlKey,
+2
View File
@@ -179,6 +179,7 @@ impl InnerInputState {
k.location(),
is_repeating,
k.modifiers(),
k.is_composing(),
);
}
@@ -779,6 +780,7 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
Location::Standard,
false,
modifiers,
false,
)))
}
+2 -2
View File
@@ -84,7 +84,7 @@ mod segment;
///
/// # `#[route("path", component)]`
///
/// The `#[route]` attribute is used to define a route. It takes up to 3 parameters:
/// The `#[route]` attribute is used to define a route. It takes up to 2 parameters:
/// - `path`: The path to the enum variant (relative to the parent nest)
/// - (optional) `component`: The component to render when the route is matched. If not specified, the name of the variant is used
///
@@ -162,7 +162,7 @@ mod segment;
///
/// # `#[layout(component)]`
///
/// The `#[layout]` attribute is used to define a layout. It takes 2 parameters:
/// The `#[layout]` attribute is used to define a layout. It takes 1 parameters:
/// - `component`: The component to render when the route is matched. If not specified, the name of the variant is used
///
/// The layout component allows you to wrap all children of the layout in a component. The child routes are rendered in the Outlet of the layout component. The layout component must take all dynamic parameters of the nests it is nested in.
+20 -3
View File
@@ -9,7 +9,8 @@
/// ```
pub struct Config {
pub(crate) hydrate: bool,
pub(crate) rootname: String,
pub(crate) root: ConfigRoot,
pub(crate) cached_strings: Vec<String>,
pub(crate) default_panic_hook: bool,
}
@@ -17,7 +18,8 @@ impl Default for Config {
fn default() -> Self {
Self {
hydrate: false,
rootname: "main".to_string(),
root: ConfigRoot::RootName("main".to_string()),
cached_strings: Vec::new(),
default_panic_hook: true,
}
}
@@ -46,8 +48,18 @@ impl Config {
/// Set the name of the element that Dioxus will use as the root.
///
/// This is akin to calling React.render() on the element with the specified name.
/// Note that this only works on the current document, i.e. `window.document`.
/// To use a different document (popup, iframe, ...) use [Self::rootelement] instead.
pub fn rootname(mut self, name: impl Into<String>) -> Self {
self.rootname = name.into();
self.root = ConfigRoot::RootName(name.into());
self
}
/// Set the element that Dioxus will use as root.
///
/// This is akin to calling React.render() on the given element.
pub fn rootelement(mut self, elem: web_sys::Element) -> Self {
self.root = ConfigRoot::RootElement(elem);
self
}
@@ -59,3 +71,8 @@ impl Config {
self
}
}
pub(crate) enum ConfigRoot {
RootName(String),
RootElement(web_sys::Element),
}
+26 -14
View File
@@ -36,24 +36,36 @@ pub struct UiEvent {
pub data: PlatformEventData,
}
//fn get_document(elem: &web_sys::Element) ->
impl WebsysDom {
pub fn new(cfg: Config, event_channel: mpsc::UnboundedSender<UiEvent>) -> Self {
// eventually, we just want to let the interpreter do all the work of decoding events into our event type
// a match here in order to avoid some error during runtime browser test
let document = load_document();
let root = match document.get_element_by_id(&cfg.rootname) {
Some(root) => root,
None => {
web_sys::console::error_1(
&format!(
"element '#{}' not found. mounting to the body.",
cfg.rootname
)
.into(),
);
document.create_element("body").ok().unwrap()
let (document, root) = match cfg.root {
crate::cfg::ConfigRoot::RootName(rootname) => {
// eventually, we just want to let the interpreter do all the work of decoding events into our event type
// a match here in order to avoid some error during runtime browser test
let document = load_document();
let root = match document.get_element_by_id(&rootname) {
Some(root) => root,
None => {
web_sys::console::error_1(
&format!("element '#{}' not found. mounting to the body.", rootname)
.into(),
);
document.create_element("body").ok().unwrap()
}
};
(document, root)
}
crate::cfg::ConfigRoot::RootElement(root) => {
let document = match root.owner_document() {
Some(document) => document,
None => load_document(),
};
(document, root)
}
};
let interpreter = Channel::default();
let handler: Closure<dyn FnMut(&Event)> = Closure::wrap(Box::new({