more clippy fixes

This commit is contained in:
Evan Almloff committed 2023-07-20 09:06:12 -07:00
commit beb56b93a0
48 files changed
+1327 -1415

No files matched your search

+4
View File
@@ -3,6 +3,10 @@ name: docs stable
on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-deploy:
runs-on: ubuntu-latest
+4
View File
@@ -8,6 +8,10 @@ on:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-deploy:
runs-on: ubuntu-latest
-38
View File
@@ -1,38 +0,0 @@
name: macOS tests
on:
push:
branches:
- master
paths:
- packages/**
- examples/**
- src/**
- .github/**
- lib.rs
- Cargo.toml
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- master
paths:
- packages/**
- examples/**
- src/**
- .github/**
- lib.rs
- Cargo.toml
jobs:
test:
if: github.event.pull_request.draft == false
name: Test Suite
runs-on: macos-latest
steps:
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@v3
- run: |
cargo test --all --tests
cargo test --package fermi --release
+68
View File
@@ -27,6 +27,10 @@ on:
- lib.rs
- Cargo.toml
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
check:
if: github.event.pull_request.draft == false
@@ -79,6 +83,69 @@ jobs:
- uses: actions/checkout@v3
- run: cargo clippy --workspace --examples --tests -- -D warnings
matrix_test:
runs-on: ${{ matrix.platform.os }}
strategy:
matrix:
platform:
- {
target: x86_64-pc-windows-msvc,
os: windows-latest,
toolchain: '1.70.0',
cross: false,
command: 'test',
args: '--all --tests'
}
- {
target: x86_64-apple-darwin,
os: macos-latest,
toolchain: '1.70.0',
cross: false,
command: 'test',
args: '--all --tests'
}
- {
target: aarch64-apple-ios,
os: macos-latest,
toolchain: '1.70.0',
cross: false,
command: 'build',
args: '--package dioxus-mobile'
}
- {
target: aarch64-linux-android,
os: ubuntu-latest,
toolchain: '1.70.0',
cross: true,
command: 'build',
args: '--package dioxus-mobile'
}
steps:
- uses: actions/checkout@v2
- name: install stable
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.platform.toolchain }}
target: ${{ matrix.platform.target }}
override: true
default: true
- uses: Swatinem/rust-cache@v2
with:
workspaces: core -> ../target
save-if: ${{ matrix.features.key == 'all' }}
- name: test
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.platform.cross }}
command: ${{ matrix.platform.command }}
args: --target ${{ matrix.platform.target }} ${{ matrix.platform.args }}
# Coverage is disabled until we can fix it
# coverage:
# name: Coverage
@@ -99,3 +166,4 @@ jobs:
# uses: codecov/codecov-action@v2
# with:
# fail_ci_if_error: false
+11 -2
View File
@@ -6,6 +6,13 @@ on:
branches:
- 'auto'
- 'try'
paths:
- packages/**
- examples/**
- src/**
- .github/**
- lib.rs
- Cargo.toml
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
@@ -31,7 +38,9 @@ env:
# - tokio-stream/Cargo.toml
# rust_min: 1.49.0
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
test:
@@ -72,7 +81,7 @@ jobs:
# #[tokio::main] that calls epoll_create1 that Miri does not support.
# run: cargo miri test --features full --lib --no-fail-fast
run: |
cargo miri test --package dioxus-core --test miri_stress -- --exact --nocapture
cargo miri test --package dioxus-core -- --exact --nocapture
cargo miri test --package dioxus-native-core --test miri_native -- --exact --nocapture
# working-directory: tokio
+21 -11
View File
@@ -4,22 +4,25 @@ on:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
defaults:
run:
working-directory: ./playwright-tests
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
test:
if: github.event.pull_request.draft == false
timeout-minutes: 60
runs-on: ubuntu-20.04
steps:
# Do our best to cache the toolchain and node install steps
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npm install -D @playwright/test
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
@@ -29,11 +32,18 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Install WASM toolchain
run: rustup target add wasm32-unknown-unknown
- name: Install Dioxus CLI
uses: actions-rs/cargo@v1
with:
command: install
args: --path packages/cli
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npm install -D @playwright/test
- name: Install Playwright Browsers
run: npx playwright install --with-deps
# Cache the CLI by using cargo run internally
# - name: Install Dioxus CLI
# uses: actions-rs/cargo@v1
# with:
# command: install
# args: --path packages/cli
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v3
-89
View File
@@ -1,89 +0,0 @@
name: windows
on:
push:
branches:
- master
paths:
- packages/**
- examples/**
- src/**
- .github/**
- lib.rs
- Cargo.toml
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- master
paths:
- packages/**
- examples/**
- src/**
- .github/**
- lib.rs
- Cargo.toml
jobs:
test:
if: github.event.pull_request.draft == false
runs-on: windows-latest
name: (${{ matrix.target }}, ${{ matrix.cfg_release_channel }})
env:
CFG_RELEASE_CHANNEL: ${{ matrix.cfg_release_channel }}
strategy:
# https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits
# There's a limit of 60 concurrent jobs across all repos in the rust-lang organization.
# In order to prevent overusing too much of that 60 limit, we throttle the
# number of rustfmt jobs that will run concurrently.
# max-parallel:
# fail-fast: false
matrix:
target: [x86_64-pc-windows-gnu, x86_64-pc-windows-msvc]
cfg_release_channel: [stable]
steps:
# The Windows runners have autocrlf enabled by default
# which causes failures for some of rustfmt's line-ending sensitive tests
- name: disable git eol translation
run: git config --global core.autocrlf false
# Run build
- name: Install Rustup using win.rustup.rs
run: |
# Disable the download progress bar which can cause perf issues
$ProgressPreference = "SilentlyContinue"
Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe
.\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --default-toolchain=none
del rustup-init.exe
rustup target add ${{ matrix.target }}
shell: powershell
- name: Add mingw64 to path for x86_64-gnu
run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH
if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly'
shell: bash
# - name: checkout
# uses: actions/checkout@v3
# with:
# path: C:/dioxus.git
# fetch-depth: 1
# we need to use the C drive as the working directory
- name: Checkout
run: |
mkdir C:/dioxus.git
git clone https://github.com/dioxuslabs/dioxus.git C:/dioxus.git --depth 1
- name: test
working-directory: C:/dioxus.git
run: |
rustc -Vv
cargo -V
set RUST_BACKTRACE=1
cargo build --all --tests --examples
cargo test --all --tests
cargo test --package fermi --release
shell: cmd
+1 -1
View File
@@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react"]
dioxus-rsx = { workspace = true }
proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
quote = "1.0"
syn = { version = "2.0", features = ["full", "extra-traits"] }
syn = { version = "2.0", features = ["full", "extra-traits", "visit"] }
serde = { version = "1.0.136", features = ["derive"] }
prettyplease = { package = "prettier-please", version = "0.2", features = [
"verbatim",
+7 -164
View File
@@ -3,181 +3,24 @@
//! Returns all macros that match a pattern. You can use this information to autoformat them later
use proc_macro2::LineColumn;
use syn::{Block, Expr, File, Item, Macro, Stmt};
use syn::{visit::Visit, File, Macro};
type CollectedMacro<'a> = &'a Macro;
pub fn collect_from_file<'a>(file: &'a File, macros: &mut Vec<CollectedMacro<'a>>) {
for item in file.items.iter() {
collect_from_item(item, macros);
}
MacroCollector::visit_file(&mut MacroCollector { macros }, file);
}
pub fn collect_from_item<'a>(item: &'a Item, macros: &mut Vec<CollectedMacro<'a>>) {
match item {
Item::Fn(f) => collect_from_block(&f.block, macros),
// Ignore macros if they're not rsx or render
Item::Macro(macro_) => {
if macro_.mac.path.segments[0].ident == "rsx"
|| macro_.mac.path.segments[0].ident == "render"
{
macros.push(&macro_.mac);
}
}
// Currently disabled since we're not focused on autoformatting these
Item::Impl(_imp) => {}
Item::Trait(_) => {}
// Global-ish things
Item::Static(f) => collect_from_expr(&f.expr, macros),
Item::Const(f) => collect_from_expr(&f.expr, macros),
Item::Mod(s) => {
if let Some((_, block)) = &s.content {
for item in block {
collect_from_item(item, macros);
}
}
}
// None of these we can really do anything with at the item level
Item::Enum(_)
| Item::ExternCrate(_)
| Item::ForeignMod(_)
| Item::TraitAlias(_)
| Item::Type(_)
| Item::Struct(_)
| Item::Union(_)
| Item::Use(_)
| Item::Verbatim(_) => {}
_ => {}
}
struct MacroCollector<'a, 'b> {
macros: &'a mut Vec<CollectedMacro<'b>>,
}
pub fn collect_from_block<'a>(block: &'a Block, macros: &mut Vec<CollectedMacro<'a>>) {
for stmt in &block.stmts {
match stmt {
Stmt::Item(item) => collect_from_item(item, macros),
Stmt::Local(local) => {
if let Some(init) = &local.init {
collect_from_expr(&init.expr, macros);
}
}
Stmt::Expr(expr, _) => collect_from_expr(expr, macros),
Stmt::Macro(mac) => {
if mac.mac.path.segments[0].ident == "rsx"
|| mac.mac.path.segments[0].ident == "render"
{
macros.push(&mac.mac);
}
}
}
impl<'a, 'b> Visit<'b> for MacroCollector<'a, 'b> {
fn visit_macro(&mut self, i: &'b Macro) {
self.macros.push(i);
}
}
pub fn collect_from_expr<'a>(expr: &'a Expr, macros: &mut Vec<CollectedMacro<'a>>) {
// collect an expr from the exprs, descending into blocks
match expr {
Expr::Macro(macro_) => {
if macro_.mac.path.segments[0].ident == "rsx"
|| macro_.mac.path.segments[0].ident == "render"
{
macros.push(&macro_.mac);
}
}
Expr::MethodCall(e) => {
collect_from_expr(&e.receiver, macros);
for expr in e.args.iter() {
collect_from_expr(expr, macros);
}
}
Expr::Assign(exp) => {
collect_from_expr(&exp.left, macros);
collect_from_expr(&exp.right, macros);
}
Expr::Async(b) => collect_from_block(&b.block, macros),
Expr::Block(b) => collect_from_block(&b.block, macros),
Expr::Closure(c) => collect_from_expr(&c.body, macros),
Expr::Let(l) => collect_from_expr(&l.expr, macros),
Expr::Unsafe(u) => collect_from_block(&u.block, macros),
Expr::Loop(l) => collect_from_block(&l.body, macros),
Expr::Call(c) => {
collect_from_expr(&c.func, macros);
for expr in c.args.iter() {
collect_from_expr(expr, macros);
}
}
Expr::ForLoop(b) => {
collect_from_expr(&b.expr, macros);
collect_from_block(&b.body, macros);
}
Expr::If(f) => {
collect_from_expr(&f.cond, macros);
collect_from_block(&f.then_branch, macros);
if let Some((_, else_branch)) = &f.else_branch {
collect_from_expr(else_branch, macros);
}
}
Expr::Yield(y) => {
if let Some(expr) = &y.expr {
collect_from_expr(expr, macros);
}
}
Expr::Return(r) => {
if let Some(expr) = &r.expr {
collect_from_expr(expr, macros);
}
}
Expr::Match(l) => {
collect_from_expr(&l.expr, macros);
for arm in l.arms.iter() {
if let Some((_, expr)) = &arm.guard {
collect_from_expr(expr, macros);
}
collect_from_expr(&arm.body, macros);
}
}
Expr::While(w) => {
collect_from_expr(&w.cond, macros);
collect_from_block(&w.body, macros);
}
// don't both formatting these for now
Expr::Array(_)
| Expr::Await(_)
| Expr::Binary(_)
| Expr::Break(_)
| Expr::Cast(_)
| Expr::Continue(_)
| Expr::Field(_)
| Expr::Group(_)
| Expr::Index(_)
| Expr::Lit(_)
| Expr::Paren(_)
| Expr::Path(_)
| Expr::Range(_)
| Expr::Reference(_)
| Expr::Repeat(_)
| Expr::Struct(_)
| Expr::Try(_)
| Expr::TryBlock(_)
| Expr::Tuple(_)
| Expr::Unary(_)
| Expr::Verbatim(_) => {}
_ => {}
};
}
pub fn byte_offset(input: &str, location: LineColumn) -> usize {
let mut offset = 0;
for _ in 1..location.line {
+8 -1
View File
@@ -165,7 +165,14 @@ impl Writer<'_> {
match &field.content {
ContentField::ManExpr(exp) => {
let out = prettyplease::unparse_expr(exp);
write!(self.out, "{name}: {out}")?;
let mut lines = out.split('\n').peekable();
let first = lines.next().unwrap();
write!(self.out, "{name}: {first}")?;
for line in lines {
self.out.new_line()?;
self.out.indented_tab()?;
write!(self.out, "{line}")?;
}
}
ContentField::Formatted(s) => {
write!(
+20 -1
View File
@@ -216,7 +216,26 @@ impl Writer<'_> {
}
ElementAttr::AttrExpression { name, value } => {
let out = prettyplease::unparse_expr(value);
write!(self.out, "{name}: {out}")?;
let mut lines = out.split('\n').peekable();
let first = lines.next().unwrap();
// a one-liner for whatever reason
// Does not need a new line
if lines.peek().is_none() {
write!(self.out, "{name}: {first}")?;
} else {
writeln!(self.out, "{name}: {first}")?;
while let Some(line) = lines.next() {
self.out.indented_tab()?;
write!(self.out, "{line}")?;
if lines.peek().is_none() {
write!(self.out, "")?;
} else {
writeln!(self.out)?;
}
}
}
}
ElementAttr::CustomAttrText { name, value } => {
+16 -28
View File
@@ -27,41 +27,29 @@ impl Writer<'_> {
// If the expr is multiline, we want to collect all of its lines together and write them out properly
// This involves unshifting the first line if it's aligned
let first_line = &self.src[start.line - 1];
write!(
self.out,
"{}",
&first_line[start.column - 1..first_line.len()].trim()
)?;
write!(self.out, "{}", &first_line[start.column - 1..].trim_start())?;
let first_prefix = &self.src[start.line - 1][..start.column];
let offset = match first_prefix.trim() {
"" => 0,
_ => first_prefix
.chars()
.rev()
.take_while(|c| c.is_whitespace())
.count() as isize,
};
let prev_block_indent_level = crate::leading_whitespaces(first_line) / 4;
for (id, line) in self.src[start.line..end.line].iter().enumerate() {
writeln!(self.out)?;
// trim the leading whitespace
let line = match id {
x if x == (end.line - start.line) - 1 => &line[..end.column],
_ => line,
// check if this is the last line
let line = {
if id == (end.line - start.line) - 1 {
&line[..end.column]
} else {
line
}
};
if offset < 0 {
for _ in 0..-offset {
write!(self.out, " ")?;
}
// trim the leading whitespace
let previous_indent = crate::leading_whitespaces(line) / 4;
let offset = previous_indent.saturating_sub(prev_block_indent_level);
let required_indent = self.out.indent + offset;
self.out.write_tabs(required_indent)?;
write!(self.out, "{line}")?;
} else {
let offset = offset as usize;
let right = &line[offset..];
write!(self.out, "{right}")?;
}
let line = line.trim_start();
write!(self.out, "{line}")?;
}
Ok(())
+13 -7
View File
@@ -58,7 +58,7 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
let mut writer = Writer::new(contents);
// Dont parse nested macros
// Don't parse nested macros
let mut end_span = LineColumn { column: 0, line: 0 };
for item in macros {
let macro_path = &item.path.segments[0].ident;
@@ -68,16 +68,11 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
continue;
}
// item.parse_body::<CallBody>();
let body = item.parse_body::<CallBody>().unwrap();
let rsx_start = macro_path.span().start();
writer.out.indent = &writer.src[rsx_start.line - 1]
.chars()
.take_while(|c| *c == ' ')
.count()
/ 4;
writer.out.indent = leading_whitespaces(writer.src[rsx_start.line - 1]) / 4;
write_body(&mut writer, &body);
@@ -231,3 +226,14 @@ pub(crate) fn write_ifmt(input: &IfmtInput, writable: &mut impl Write) -> std::f
let display = DisplayIfmt(input);
write!(writable, "{}", display)
}
pub fn leading_whitespaces(input: &str) -> usize {
input
.chars()
.map_while(|c| match c {
' ' => Some(1),
'\t' => Some(4),
_ => None,
})
.sum()
}
+2
View File
@@ -81,6 +81,8 @@ rsx-rosetta = { workspace = true }
dioxus-rsx = { workspace = true }
dioxus-html = { workspace = true, features = ["hot-reload-context"] }
dioxus-core = { workspace = true, features = ["serialize"] }
dioxus-hot-reload = { workspace = true }
interprocess-docfix = { version = "1.2.2" }
[features]
default = []
+83 -87
View File
@@ -12,7 +12,6 @@ use std::{
io::Read,
panic,
path::PathBuf,
process::Command,
time::Duration,
};
use wasm_bindgen_cli_support::Bindgen;
@@ -244,128 +243,125 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
})
}
pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<()> {
pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResult> {
log::info!("🚅 Running build [Desktop] command...");
let t_start = std::time::Instant::now();
let ignore_files = build_assets(config)?;
let mut cmd = Command::new("cargo");
cmd.current_dir(&config.crate_dir)
let mut cmd = subprocess::Exec::cmd("cargo")
.cwd(&config.crate_dir)
.arg("build")
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit());
.arg("--message-format=json");
if config.release {
cmd.arg("--release");
cmd = cmd.arg("--release");
}
if config.verbose {
cmd.arg("--verbose");
cmd = cmd.arg("--verbose");
}
if config.custom_profile.is_some() {
let custom_profile = config.custom_profile.as_ref().unwrap();
cmd.arg("--profile");
cmd.arg(custom_profile);
cmd = cmd.arg("--profile").arg(custom_profile);
}
if config.features.is_some() {
let features_str = config.features.as_ref().unwrap().join(" ");
cmd.arg("--features");
cmd.arg(features_str);
cmd = cmd.arg("--features").arg(features_str);
}
match &config.executable {
let cmd = match &config.executable {
crate::ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
crate::ExecutableType::Lib(name) => cmd.arg("--lib").arg(name),
crate::ExecutableType::Example(name) => cmd.arg("--example").arg(name),
};
let output = cmd.output()?;
let warning_messages = prettier_build(cmd)?;
if !output.status.success() {
return Err(Error::BuildFailed("Program build failed.".into()));
}
let release_type = match config.release {
true => "release",
false => "debug",
};
if output.status.success() {
let release_type = match config.release {
true => "release",
false => "debug",
};
let file_name: String;
let mut res_path = match &config.executable {
crate::ExecutableType::Binary(name) | crate::ExecutableType::Lib(name) => {
file_name = name.clone();
config.target_dir.join(release_type).join(name)
}
crate::ExecutableType::Example(name) => {
file_name = name.clone();
config
.target_dir
.join(release_type)
.join("examples")
.join(name)
}
};
let target_file = if cfg!(windows) {
res_path.set_extension("exe");
format!("{}.exe", &file_name)
} else {
file_name
};
if !config.out_dir.is_dir() {
create_dir_all(&config.out_dir)?;
let file_name: String;
let mut res_path = match &config.executable {
crate::ExecutableType::Binary(name) | crate::ExecutableType::Lib(name) => {
file_name = name.clone();
config.target_dir.join(release_type).join(name)
}
copy(res_path, &config.out_dir.join(target_file))?;
crate::ExecutableType::Example(name) => {
file_name = name.clone();
config
.target_dir
.join(release_type)
.join("examples")
.join(name)
}
};
// this code will copy all public file to the output dir
if config.asset_dir.is_dir() {
let copy_options = fs_extra::dir::CopyOptions {
overwrite: true,
skip_exist: false,
buffer_size: 64000,
copy_inside: false,
content_only: false,
depth: 0,
};
let target_file = if cfg!(windows) {
res_path.set_extension("exe");
format!("{}.exe", &file_name)
} else {
file_name
};
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()))?;
} else {
match fs_extra::dir::copy(&path, &config.out_dir, &copy_options) {
Ok(_) => {}
Err(e) => {
log::warn!("Error copying dir: {}", e);
}
if !config.out_dir.is_dir() {
create_dir_all(&config.out_dir)?;
}
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() {
let copy_options = fs_extra::dir::CopyOptions {
overwrite: true,
skip_exist: false,
buffer_size: 64000,
copy_inside: false,
content_only: false,
depth: 0,
};
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()))?;
} else {
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);
if ignore.is_file() {
std::fs::remove_file(ignore)?;
}
}
for ignore in &ignore_files {
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)?;
}
}
}
}
log::info!(
"🚩 Build completed: [./{}]",
config
.dioxus_config
.application
.out_dir
.clone()
.unwrap_or_else(|| PathBuf::from("dist"))
.display()
);
}
Ok(())
log::info!(
"🚩 Build completed: [./{}]",
config
.dioxus_config
.application
.out_dir
.clone()
.unwrap_or_else(|| PathBuf::from("dist"))
.display()
);
println!("build desktop done");
Ok(BuildResult {
warnings: warning_messages,
elapsed_time: (t_start - std::time::Instant::now()).as_millis(),
})
}
fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result<Vec<Diagnostic>> {
+11 -5
View File
@@ -109,11 +109,17 @@ async fn autoformat_project(check: bool) -> Result<()> {
})
.await;
if res.is_err() {
eprintln!("error formatting file: {}", _path.display());
match res {
Err(err) => {
eprintln!("error formatting file: {}\n{err}", _path.display());
None
}
Ok(Err(err)) => {
eprintln!("error formatting file: {}\n{err}", _path.display());
None
}
Ok(Ok(res)) => Some(res),
}
res
})
.collect::<FuturesUnordered<_>>()
.collect::<Vec<_>>()
@@ -122,7 +128,7 @@ async fn autoformat_project(check: bool) -> Result<()> {
let files_formatted: usize = counts
.into_iter()
.map(|f| match f {
Ok(Ok(res)) => res,
Some(res) => res,
_ => 0,
})
.sum();
+8 -13
View File
@@ -1,3 +1,4 @@
use crate::cfg::Platform;
#[cfg(feature = "plugin")]
use crate::plugin::PluginManager;
@@ -31,27 +32,21 @@ impl Build {
crate_config.set_features(self.build.features.unwrap());
}
let platform = self.build.platform.unwrap_or_else(|| {
crate_config
.dioxus_config
.application
.default_platform
.clone()
});
let platform = self
.build
.platform
.unwrap_or(crate_config.dioxus_config.application.default_platform);
#[cfg(feature = "plugin")]
let _ = PluginManager::on_build_start(&crate_config, &platform);
match platform.as_str() {
"web" => {
match platform {
Platform::Web => {
crate::builder::build(&crate_config, false)?;
}
"desktop" => {
Platform::Desktop => {
crate::builder::build_desktop(&crate_config, false)?;
}
_ => {
return custom_error!("Unsupported platform target.");
}
}
let temp = gen_page(&crate_config.dioxus_config, false);
+17 -4
View File
@@ -1,3 +1,6 @@
use clap::ValueEnum;
use serde::Serialize;
use super::*;
/// Config options for the build system.
@@ -26,8 +29,8 @@ pub struct ConfigOptsBuild {
pub profile: Option<String>,
/// Build platform: support Web & Desktop [default: "default_platform"]
#[clap(long)]
pub platform: Option<String>,
#[clap(long, value_enum)]
pub platform: Option<Platform>,
/// Space separated list of features to activate
#[clap(long)]
@@ -69,8 +72,8 @@ pub struct ConfigOptsServe {
pub profile: Option<String>,
/// Build platform: support Web & Desktop [default: "default_platform"]
#[clap(long)]
pub platform: Option<String>,
#[clap(long, value_enum)]
pub platform: Option<Platform>,
/// Build with hot reloading rsx [default: false]
#[clap(long)]
@@ -88,6 +91,16 @@ pub struct ConfigOptsServe {
pub features: Option<Vec<String>>,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize, Debug)]
pub enum Platform {
#[clap(name = "web")]
#[serde(rename = "web")]
Web,
#[clap(name = "desktop")]
#[serde(rename = "desktop")]
Desktop,
}
/// Ensure the given value for `--public-url` is formatted correctly.
pub fn parse_public_url(val: &str) -> String {
let prefix = if !val.starts_with('/') { "/" } else { "" };
+15 -37
View File
@@ -1,10 +1,5 @@
use super::*;
use std::{
fs::create_dir_all,
io::Write,
path::PathBuf,
process::{Command, Stdio},
};
use std::{fs::create_dir_all, io::Write, path::PathBuf};
/// Run the WASM project on dev-server
#[derive(Clone, Debug, Parser)]
@@ -39,41 +34,24 @@ impl Serve {
// Subdirectories don't work with the server
crate_config.dioxus_config.web.app.base_path = None;
let platform = self.serve.platform.unwrap_or_else(|| {
crate_config
.dioxus_config
.application
.default_platform
.clone()
});
let platform = self
.serve
.platform
.unwrap_or(crate_config.dioxus_config.application.default_platform);
if platform.as_str() == "desktop" {
crate::builder::build_desktop(&crate_config, true)?;
match platform {
cfg::Platform::Web => {
// generate dev-index page
Serve::regen_dev_page(&crate_config)?;
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()?;
}
// start the develop server
server::web::startup(self.serve.port, crate_config.clone(), self.serve.open)
.await?;
}
cfg::Platform::Desktop => {
server::desktop::startup(crate_config.clone()).await?;
}
return Ok(());
} else if platform != "web" {
return custom_error!("Unsupported platform target.");
}
// generate dev-index page
Serve::regen_dev_page(&crate_config)?;
// start the develop server
server::startup(self.serve.port, crate_config.clone(), self.serve.open).await?;
Ok(())
}
+3 -3
View File
@@ -1,4 +1,4 @@
use crate::error::Result;
use crate::{cfg::Platform, error::Result};
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
@@ -73,7 +73,7 @@ impl Default for DioxusConfig {
Self {
application: ApplicationConfig {
name: "dioxus".into(),
default_platform: "web".to_string(),
default_platform: Platform::Web,
out_dir: Some(PathBuf::from("dist")),
asset_dir: Some(PathBuf::from("public")),
@@ -115,7 +115,7 @@ impl Default for DioxusConfig {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApplicationConfig {
pub name: String,
pub default_platform: String,
pub default_platform: Platform,
pub out_dir: Option<PathBuf>,
pub asset_dir: Option<PathBuf>,
+248
View File
@@ -0,0 +1,248 @@
use crate::{
server::{
output::{print_console_info, PrettierOptions},
setup_file_watcher, setup_file_watcher_hot_reload,
},
BuildResult, CrateConfig, Result,
};
use dioxus_hot_reload::HotReloadMsg;
use dioxus_html::HtmlCtx;
use dioxus_rsx::hot_reload::*;
use interprocess_docfix::local_socket::LocalSocketListener;
use std::{
process::{Child, Command},
sync::{Arc, Mutex, RwLock},
};
use tokio::sync::broadcast::{self};
#[cfg(feature = "plugin")]
use plugin::PluginManager;
pub async fn startup(config: CrateConfig) -> Result<()> {
// ctrl-c shutdown checker
let _crate_config = config.clone();
let _ = ctrlc::set_handler(move || {
#[cfg(feature = "plugin")]
let _ = PluginManager::on_serve_shutdown(&_crate_config);
std::process::exit(0);
});
match config.hot_reload {
true => serve_hot_reload(config).await?,
false => serve_default(config).await?,
}
Ok(())
}
/// Start the server without hot reload
pub async fn serve_default(config: CrateConfig) -> Result<()> {
let (child, first_build_result) = start_desktop(&config)?;
let currently_running_child: RwLock<Child> = RwLock::new(child);
log::info!("🚀 Starting development server...");
// We got to own watcher so that it exists for the duration of serve
// Otherwise full reload won't work.
let _watcher = setup_file_watcher(
{
let config = config.clone();
move || {
let mut current_child = currently_running_child.write().unwrap();
current_child.kill()?;
let (child, result) = start_desktop(&config)?;
*current_child = child;
Ok(result)
}
},
&config,
None,
)
.await?;
// Print serve info
print_console_info(
&config,
PrettierOptions {
changed: vec![],
warnings: first_build_result.warnings,
elapsed_time: first_build_result.elapsed_time,
},
None,
);
std::future::pending::<()>().await;
Ok(())
}
/// Start the server without hot reload
/// Start dx serve with hot reload
pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> {
let (_, first_build_result) = start_desktop(&config)?;
println!("🚀 Starting development server...");
// Setup hot reload
let FileMapBuildResult { map, errors } =
FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
println!("🚀 Starting development server...");
for err in errors {
log::error!("{}", err);
}
let file_map = Arc::new(Mutex::new(map));
let (hot_reload_tx, mut hot_reload_rx) = broadcast::channel(100);
// States
// The open interprocess sockets
let channels = Arc::new(Mutex::new(Vec::new()));
// Setup file watcher
// We got to own watcher so that it exists for the duration of serve
// Otherwise hot reload won't work.
let _watcher = setup_file_watcher_hot_reload(
&config,
hot_reload_tx,
file_map.clone(),
{
let config = config.clone();
let channels = channels.clone();
move || {
for channel in &mut *channels.lock().unwrap() {
send_msg(HotReloadMsg::Shutdown, channel);
}
Ok(start_desktop(&config)?.1)
}
},
None,
)
.await?;
// Print serve info
print_console_info(
&config,
PrettierOptions {
changed: vec![],
warnings: first_build_result.warnings,
elapsed_time: first_build_result.elapsed_time,
},
None,
);
clear_paths();
match LocalSocketListener::bind("@dioxusin") {
Ok(local_socket_stream) => {
let aborted = Arc::new(Mutex::new(false));
// listen for connections
std::thread::spawn({
let file_map = file_map.clone();
let channels = channels.clone();
let aborted = aborted.clone();
let _ = local_socket_stream.set_nonblocking(true);
move || {
loop {
if let Ok(mut connection) = local_socket_stream.accept() {
// send any templates than have changed before the socket connected
let templates: Vec<_> = {
file_map
.lock()
.unwrap()
.map
.values()
.filter_map(|(_, template_slot)| *template_slot)
.collect()
};
for template in templates {
if !send_msg(
HotReloadMsg::UpdateTemplate(template),
&mut connection,
) {
continue;
}
}
channels.lock().unwrap().push(connection);
println!("Connected to hot reloading 🚀");
}
if *aborted.lock().unwrap() {
break;
}
}
}
});
while let Ok(template) = hot_reload_rx.recv().await {
let channels = &mut *channels.lock().unwrap();
let mut i = 0;
while i < channels.len() {
let channel = &mut channels[i];
if send_msg(HotReloadMsg::UpdateTemplate(template), channel) {
i += 1;
} else {
channels.remove(i);
}
}
}
}
Err(error) => println!("failed to connect to hot reloading\n{error}"),
}
Ok(())
}
fn clear_paths() {
if cfg!(target_os = "macos") {
// On unix, if you force quit the application, it can leave the file socket open
// This will cause the local socket listener to fail to open
// We check if the file socket is already open from an old session and then delete it
let paths = ["./dioxusin", "./@dioxusin"];
for path in paths {
let path = std::path::PathBuf::from(path);
if path.exists() {
let _ = std::fs::remove_file(path);
}
}
}
}
fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool {
if let Ok(msg) = serde_json::to_string(&msg) {
if channel.write_all(msg.as_bytes()).is_err() {
return false;
}
if channel.write_all(&[b'\n']).is_err() {
return false;
}
true
} else {
false
}
}
pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> {
// Run the desktop application
let result = crate::builder::build_desktop(config, true)?;
match &config.executable {
crate::ExecutableType::Binary(name)
| crate::ExecutableType::Lib(name)
| crate::ExecutableType::Example(name) => {
let mut file = config.out_dir.join(name);
if cfg!(windows) {
file.set_extension("exe");
}
let child = Command::new(file.to_str().unwrap()).spawn()?;
Ok((child, result))
}
}
}
+17 -490
View File
@@ -1,449 +1,27 @@
use crate::{builder, serve::Serve, BuildResult, CrateConfig, Result};
use axum::{
body::{Full, HttpBody},
extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade},
http::{
header::{HeaderName, HeaderValue},
Method, Response, StatusCode,
},
response::IntoResponse,
routing::{get, get_service},
Router,
};
use axum_server::tls_rustls::RustlsConfig;
use crate::{BuildResult, CrateConfig, Result};
use cargo_metadata::diagnostic::Diagnostic;
use dioxus_core::Template;
use dioxus_html::HtmlCtx;
use dioxus_rsx::hot_reload::*;
use notify::{RecommendedWatcher, Watcher};
use std::{
net::UdpSocket,
path::PathBuf,
process::Command,
sync::{Arc, Mutex},
};
use tokio::sync::broadcast::{self, Sender};
use tower::ServiceBuilder;
use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody};
use tower_http::{
cors::{Any, CorsLayer},
ServiceBuilderExt,
};
#[cfg(feature = "plugin")]
use plugin::PluginManager;
mod proxy;
mod hot_reload;
use hot_reload::*;
use tokio::sync::broadcast::Sender;
mod output;
use output::*;
pub struct BuildManager {
config: CrateConfig,
reload_tx: broadcast::Sender<()>,
}
impl BuildManager {
fn rebuild(&self) -> Result<BuildResult> {
log::info!("🪁 Rebuild project");
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.send(());
Ok(result)
}
}
struct WsReloadState {
update: broadcast::Sender<()>,
}
pub async fn startup(port: u16, config: CrateConfig, start_browser: bool) -> Result<()> {
// ctrl-c shutdown checker
let _crate_config = config.clone();
let _ = ctrlc::set_handler(move || {
#[cfg(feature = "plugin")]
let _ = PluginManager::on_serve_shutdown(&_crate_config);
std::process::exit(0);
});
let ip = get_ip().unwrap_or(String::from("0.0.0.0"));
match config.hot_reload {
true => serve_hot_reload(ip, port, config, start_browser).await?,
false => serve_default(ip, port, config, start_browser).await?,
}
Ok(())
}
/// Start the server without hot reload
pub async fn serve_default(
ip: String,
port: u16,
config: CrateConfig,
start_browser: bool,
) -> Result<()> {
let first_build_result = crate::builder::build(&config, false)?;
log::info!("🚀 Starting development server...");
// WS Reload Watching
let (reload_tx, _) = broadcast::channel(100);
// We got to own watcher so that it exists for the duration of serve
// Otherwise full reload won't work.
let _watcher = setup_file_watcher(&config, port, ip.clone(), reload_tx.clone()).await?;
let ws_reload_state = Arc::new(WsReloadState {
update: reload_tx.clone(),
});
// HTTPS
// Before console info so it can stop if mkcert isn't installed or fails
let rustls_config = get_rustls(&config).await?;
// Print serve info
print_console_info(
&ip,
port,
&config,
PrettierOptions {
changed: vec![],
warnings: first_build_result.warnings,
elapsed_time: first_build_result.elapsed_time,
},
);
// Router
let router = setup_router(config, ws_reload_state, None).await?;
// Start server
start_server(port, router, start_browser, rustls_config).await?;
Ok(())
}
/// Start dx serve with hot reload
pub async fn serve_hot_reload(
ip: String,
port: u16,
config: CrateConfig,
start_browser: bool,
) -> Result<()> {
let first_build_result = crate::builder::build(&config, false)?;
log::info!("🚀 Starting development server...");
// Setup hot reload
let (reload_tx, _) = broadcast::channel(100);
let FileMapBuildResult { map, errors } =
FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
for err in errors {
log::error!("{}", err);
}
let file_map = Arc::new(Mutex::new(map));
let build_manager = Arc::new(BuildManager {
config: config.clone(),
reload_tx: reload_tx.clone(),
});
let hot_reload_tx = broadcast::channel(100).0;
// States
let hot_reload_state = Arc::new(HotReloadState {
messages: hot_reload_tx.clone(),
build_manager: build_manager.clone(),
file_map: file_map.clone(),
watcher_config: config.clone(),
});
let ws_reload_state = Arc::new(WsReloadState {
update: reload_tx.clone(),
});
// Setup file watcher
// We got to own watcher so that it exists for the duration of serve
// Otherwise hot reload won't work.
let _watcher = setup_file_watcher_hot_reload(
&config,
port,
ip.clone(),
hot_reload_tx,
file_map,
build_manager,
)
.await?;
// HTTPS
// Before console info so it can stop if mkcert isn't installed or fails
let rustls_config = get_rustls(&config).await?;
// Print serve info
print_console_info(
&ip,
port,
&config,
PrettierOptions {
changed: vec![],
warnings: first_build_result.warnings,
elapsed_time: first_build_result.elapsed_time,
},
);
// Router
let router = setup_router(config, ws_reload_state, Some(hot_reload_state)).await?;
// Start server
start_server(port, router, start_browser, rustls_config).await?;
Ok(())
}
const DEFAULT_KEY_PATH: &str = "ssl/key.pem";
const DEFAULT_CERT_PATH: &str = "ssl/cert.pem";
/// Returns an enum of rustls config and a bool if mkcert isn't installed
async fn get_rustls(config: &CrateConfig) -> Result<Option<RustlsConfig>> {
let web_config = &config.dioxus_config.web.https;
if web_config.enabled != Some(true) {
return Ok(None);
}
let (cert_path, key_path) = match web_config.mkcert {
// mkcert, use it
Some(true) => {
// Get paths to store certs, otherwise use ssl/item.pem
let key_path = web_config
.key_path
.clone()
.unwrap_or(DEFAULT_KEY_PATH.to_string());
let cert_path = web_config
.cert_path
.clone()
.unwrap_or(DEFAULT_CERT_PATH.to_string());
// Create ssl directory if using defaults
if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH {
_ = fs::create_dir("ssl");
}
let cmd = Command::new("mkcert")
.args([
"-install",
"-key-file",
&key_path,
"-cert-file",
&cert_path,
"localhost",
"::1",
"127.0.0.1",
])
.spawn();
match cmd {
Err(e) => {
match e.kind() {
io::ErrorKind::NotFound => log::error!("mkcert is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions."),
e => log::error!("an error occured while generating mkcert certificates: {}", e.to_string()),
};
return Err("failed to generate mkcert certificates".into());
}
Ok(mut cmd) => {
cmd.wait()?;
}
}
(cert_path, key_path)
}
// not mkcert
Some(false) => {
// get paths to cert & key
if let (Some(key), Some(cert)) =
(web_config.key_path.clone(), web_config.cert_path.clone())
{
(cert, key)
} else {
// missing cert or key
return Err("https is enabled but cert or key path is missing".into());
}
}
// other
_ => return Ok(None),
};
Ok(Some(
RustlsConfig::from_pem_file(cert_path, key_path).await?,
))
}
/// Sets up and returns a router
async fn setup_router(
config: CrateConfig,
ws_reload: Arc<WsReloadState>,
hot_reload: Option<Arc<HotReloadState>>,
) -> Result<Router> {
// Setup cors
let cors = CorsLayer::new()
// allow `GET` and `POST` when accessing the resource
.allow_methods([Method::GET, Method::POST])
// allow requests from any origin
.allow_origin(Any)
.allow_headers(Any);
let (coep, coop) = if config.cross_origin_policy {
(
HeaderValue::from_static("require-corp"),
HeaderValue::from_static("same-origin"),
)
} else {
(
HeaderValue::from_static("unsafe-none"),
HeaderValue::from_static("unsafe-none"),
)
};
// Create file service
let file_service_config = config.clone();
let file_service = ServiceBuilder::new()
.override_response_header(
HeaderName::from_static("cross-origin-embedder-policy"),
coep,
)
.override_response_header(HeaderName::from_static("cross-origin-opener-policy"), coop)
.and_then(
move |response: Response<ServeFileSystemResponseBody>| async move {
let response = if file_service_config
.dioxus_config
.web
.watcher
.index_on_404
.unwrap_or(false)
&& response.status() == StatusCode::NOT_FOUND
{
let body = Full::from(
// TODO: Cache/memoize this.
std::fs::read_to_string(
file_service_config
.crate_dir
.join(file_service_config.out_dir)
.join("index.html"),
)
.ok()
.unwrap(),
)
.map_err(|err| match err {})
.boxed();
Response::builder()
.status(StatusCode::OK)
.body(body)
.unwrap()
} else {
response.map(|body| body.boxed())
};
Ok(response)
},
)
.service(ServeDir::new(config.crate_dir.join(&config.out_dir)));
// Setup websocket
let mut router = Router::new().route("/_dioxus/ws", get(ws_handler));
// Setup proxy
for proxy_config in config.dioxus_config.web.proxy.unwrap_or_default() {
router = proxy::add_proxy(router, &proxy_config)?;
}
// Route file service
router = router.fallback(get_service(file_service).handle_error(
|error: std::io::Error| async move {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled internal error: {}", error),
)
},
));
// Setup routes
router = router
.route("/_dioxus/hot_reload", get(hot_reload_handler))
.layer(cors)
.layer(Extension(ws_reload));
if let Some(hot_reload) = hot_reload {
router = router.layer(Extension(hot_reload))
}
Ok(router)
}
/// Starts dx serve with no hot reload
async fn start_server(
port: u16,
router: Router,
start_browser: bool,
rustls: Option<RustlsConfig>,
) -> Result<()> {
// If plugins, call on_serve_start event
#[cfg(feature = "plugin")]
PluginManager::on_serve_start(&config)?;
// Parse address
let addr = format!("0.0.0.0:{}", port).parse().unwrap();
// Open the browser
if start_browser {
match rustls {
Some(_) => _ = open::that(format!("https://{}", addr)),
None => _ = open::that(format!("http://{}", addr)),
}
}
// Start the server with or without rustls
match rustls {
Some(rustls) => {
axum_server::bind_rustls(addr, rustls)
.serve(router.into_make_service())
.await?
}
None => {
axum::Server::bind(&addr)
.serve(router.into_make_service())
.await?
}
}
Ok(())
}
pub mod desktop;
pub mod web;
/// Sets up a file watcher
async fn setup_file_watcher(
async fn setup_file_watcher<F: Fn() -> Result<BuildResult> + Send + 'static>(
build_with: F,
config: &CrateConfig,
port: u16,
watcher_ip: String,
reload_tx: Sender<()>,
web_info: Option<WebServerInfo>,
) -> Result<RecommendedWatcher> {
let build_manager = BuildManager {
config: config.clone(),
reload_tx,
};
let mut last_update_time = chrono::Local::now().timestamp();
// file watcher: check file change
@@ -460,20 +38,19 @@ async fn setup_file_watcher(
let config = watcher_config.clone();
if let Ok(e) = info {
if chrono::Local::now().timestamp() > last_update_time {
match build_manager.rebuild() {
match build_with() {
Ok(res) => {
last_update_time = chrono::Local::now().timestamp();
#[allow(clippy::redundant_clone)]
print_console_info(
&watcher_ip,
port,
&config,
PrettierOptions {
changed: e.paths.clone(),
warnings: res.warnings,
elapsed_time: res.elapsed_time,
},
web_info.clone(),
);
#[cfg(feature = "plugin")]
@@ -502,13 +79,12 @@ async fn setup_file_watcher(
// Todo: reduce duplication and merge with setup_file_watcher()
/// 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,
port: u16,
watcher_ip: String,
hot_reload_tx: Sender<Template<'static>>,
file_map: Arc<Mutex<FileMap<HtmlCtx>>>,
build_manager: Arc<BuildManager>,
build_with: F,
web_info: Option<WebServerInfo>,
) -> Result<RecommendedWatcher> {
// file watcher: check file change
let allow_watch_path = config
@@ -533,17 +109,16 @@ async fn setup_file_watcher_hot_reload(
for path in evt.paths.clone() {
// if this is not a rust file, rebuild the whole project
if path.extension().and_then(|p| p.to_str()) != Some("rs") {
match build_manager.rebuild() {
match build_with() {
Ok(res) => {
print_console_info(
&watcher_ip,
port,
&config,
PrettierOptions {
changed: evt.paths,
warnings: res.warnings,
elapsed_time: res.elapsed_time,
},
web_info.clone(),
);
}
Err(err) => {
@@ -560,17 +135,16 @@ async fn setup_file_watcher_hot_reload(
messages.extend(msgs);
}
Ok(UpdateResult::NeedsRebuild) => {
match build_manager.rebuild() {
match build_with() {
Ok(res) => {
print_console_info(
&watcher_ip,
port,
&config,
PrettierOptions {
changed: evt.paths,
warnings: res.warnings,
elapsed_time: res.elapsed_time,
},
web_info.clone(),
);
}
Err(err) => {
@@ -606,50 +180,3 @@ async fn setup_file_watcher_hot_reload(
Ok(watcher)
}
/// Get the network ip
fn get_ip() -> Option<String> {
let socket = match UdpSocket::bind("0.0.0.0:0") {
Ok(s) => s,
Err(_) => return None,
};
match socket.connect("8.8.8.8:80") {
Ok(()) => (),
Err(_) => return None,
};
match socket.local_addr() {
Ok(addr) => Some(addr.ip().to_string()),
Err(_) => None,
}
}
/// Handle websockets
async fn ws_handler(
ws: WebSocketUpgrade,
_: Option<TypedHeader<headers::UserAgent>>,
Extension(state): Extension<Arc<WsReloadState>>,
) -> impl IntoResponse {
ws.on_upgrade(|mut socket| async move {
let mut rx = state.update.subscribe();
let reload_watcher = tokio::spawn(async move {
loop {
rx.recv().await.unwrap();
// ignore the error
if socket
.send(Message::Text(String::from("reload")))
.await
.is_err()
{
break;
}
// flush the errors after recompling
rx = rx.resubscribe();
}
});
reload_watcher.await.unwrap();
})
}
+33 -21
View File
@@ -11,7 +11,17 @@ pub struct PrettierOptions {
pub elapsed_time: u128,
}
pub fn print_console_info(ip: &String, port: u16, config: &CrateConfig, options: PrettierOptions) {
#[derive(Debug, Clone)]
pub struct WebServerInfo {
pub ip: String,
pub port: u16,
}
pub fn print_console_info(
config: &CrateConfig,
options: PrettierOptions,
web_info: Option<WebServerInfo>,
) {
if let Ok(native_clearseq) = Command::new(if cfg!(target_os = "windows") {
"cls"
} else {
@@ -70,26 +80,28 @@ pub fn print_console_info(ip: &String, port: u16, config: &CrateConfig, options:
);
}
if config.dioxus_config.web.https.enabled == Some(true) {
println!(
"\t> Local : {}",
format!("https://localhost:{}/", port).blue()
);
println!(
"\t> Network : {}",
format!("https://{}:{}/", ip, port).blue()
);
println!("\t> HTTPS : {}", "Enabled".to_string().green());
} else {
println!(
"\t> Local : {}",
format!("http://localhost:{}/", port).blue()
);
println!(
"\t> Network : {}",
format!("http://{}:{}/", ip, port).blue()
);
println!("\t> HTTPS : {}", "Disabled".to_string().red());
if let Some(WebServerInfo { ip, port }) = web_info {
if config.dioxus_config.web.https.enabled == Some(true) {
println!(
"\t> Local : {}",
format!("https://localhost:{}/", port).blue()
);
println!(
"\t> Network : {}",
format!("https://{}:{}/", ip, port).blue()
);
println!("\t> HTTPS : {}", "Enabled".to_string().green());
} else {
println!(
"\t> Local : {}",
format!("http://localhost:{}/", port).blue()
);
println!(
"\t> Network : {}",
format!("http://{}:{}/", ip, port).blue()
);
println!("\t> HTTPS : {}", "Disabled".to_string().red());
}
}
println!();
println!("\t> Profile : {}", profile.green());
@@ -10,12 +10,10 @@ use dioxus_html::HtmlCtx;
use dioxus_rsx::hot_reload::FileMap;
use tokio::sync::broadcast;
use super::BuildManager;
use crate::CrateConfig;
pub struct HotReloadState {
pub messages: broadcast::Sender<Template<'static>>,
pub build_manager: Arc<BuildManager>,
pub file_map: Arc<Mutex<FileMap<HtmlCtx>>>,
pub watcher_config: CrateConfig,
}
+490
View File
@@ -0,0 +1,490 @@
use crate::{
builder,
serve::Serve,
server::{
output::{print_console_info, PrettierOptions, WebServerInfo},
setup_file_watcher, setup_file_watcher_hot_reload,
},
BuildResult, CrateConfig, Result,
};
use axum::{
body::{Full, HttpBody},
extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade},
http::{
header::{HeaderName, HeaderValue},
Method, Response, StatusCode,
},
response::IntoResponse,
routing::{get, get_service},
Router,
};
use axum_server::tls_rustls::RustlsConfig;
use dioxus_html::HtmlCtx;
use dioxus_rsx::hot_reload::*;
use std::{
net::UdpSocket,
process::Command,
sync::{Arc, Mutex},
};
use tokio::sync::broadcast::{self, Sender};
use tower::ServiceBuilder;
use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody};
use tower_http::{
cors::{Any, CorsLayer},
ServiceBuilderExt,
};
#[cfg(feature = "plugin")]
use plugin::PluginManager;
mod proxy;
mod hot_reload;
use hot_reload::*;
struct WsReloadState {
update: broadcast::Sender<()>,
}
pub async fn startup(port: u16, config: CrateConfig, start_browser: bool) -> Result<()> {
// ctrl-c shutdown checker
let _crate_config = config.clone();
let _ = ctrlc::set_handler(move || {
#[cfg(feature = "plugin")]
let _ = PluginManager::on_serve_shutdown(&_crate_config);
std::process::exit(0);
});
let ip = get_ip().unwrap_or(String::from("0.0.0.0"));
match config.hot_reload {
true => serve_hot_reload(ip, port, config, start_browser).await?,
false => serve_default(ip, port, config, start_browser).await?,
}
Ok(())
}
/// Start the server without hot reload
pub async fn serve_default(
ip: String,
port: u16,
config: CrateConfig,
start_browser: bool,
) -> Result<()> {
let first_build_result = crate::builder::build(&config, false)?;
log::info!("🚀 Starting development server...");
// WS Reload Watching
let (reload_tx, _) = broadcast::channel(100);
// We got to own watcher so that it exists for the duration of serve
// Otherwise full reload won't work.
let _watcher = setup_file_watcher(
{
let config = config.clone();
let reload_tx = reload_tx.clone();
move || build(&config, &reload_tx)
},
&config,
Some(WebServerInfo {
ip: ip.clone(),
port,
}),
)
.await?;
let ws_reload_state = Arc::new(WsReloadState {
update: reload_tx.clone(),
});
// HTTPS
// Before console info so it can stop if mkcert isn't installed or fails
let rustls_config = get_rustls(&config).await?;
// Print serve info
print_console_info(
&config,
PrettierOptions {
changed: vec![],
warnings: first_build_result.warnings,
elapsed_time: first_build_result.elapsed_time,
},
Some(crate::server::output::WebServerInfo {
ip: ip.clone(),
port,
}),
);
// Router
let router = setup_router(config, ws_reload_state, None).await?;
// Start server
start_server(port, router, start_browser, rustls_config).await?;
Ok(())
}
/// Start dx serve with hot reload
pub async fn serve_hot_reload(
ip: String,
port: u16,
config: CrateConfig,
start_browser: bool,
) -> Result<()> {
let first_build_result = crate::builder::build(&config, false)?;
log::info!("🚀 Starting development server...");
// Setup hot reload
let (reload_tx, _) = broadcast::channel(100);
let FileMapBuildResult { map, errors } =
FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
for err in errors {
log::error!("{}", err);
}
let file_map = Arc::new(Mutex::new(map));
let hot_reload_tx = broadcast::channel(100).0;
// States
let hot_reload_state = Arc::new(HotReloadState {
messages: hot_reload_tx.clone(),
file_map: file_map.clone(),
watcher_config: config.clone(),
});
let ws_reload_state = Arc::new(WsReloadState {
update: reload_tx.clone(),
});
// Setup file watcher
// We got to own watcher so that it exists for the duration of serve
// Otherwise hot reload won't work.
let _watcher = setup_file_watcher_hot_reload(
&config,
hot_reload_tx,
file_map,
{
let config = config.clone();
let reload_tx = reload_tx.clone();
move || build(&config, &reload_tx)
},
Some(WebServerInfo {
ip: ip.clone(),
port,
}),
)
.await?;
// HTTPS
// Before console info so it can stop if mkcert isn't installed or fails
let rustls_config = get_rustls(&config).await?;
// Print serve info
print_console_info(
&config,
PrettierOptions {
changed: vec![],
warnings: first_build_result.warnings,
elapsed_time: first_build_result.elapsed_time,
},
Some(WebServerInfo {
ip: ip.clone(),
port,
}),
);
// Router
let router = setup_router(config, ws_reload_state, Some(hot_reload_state)).await?;
// Start server
start_server(port, router, start_browser, rustls_config).await?;
Ok(())
}
const DEFAULT_KEY_PATH: &str = "ssl/key.pem";
const DEFAULT_CERT_PATH: &str = "ssl/cert.pem";
/// Returns an enum of rustls config and a bool if mkcert isn't installed
async fn get_rustls(config: &CrateConfig) -> Result<Option<RustlsConfig>> {
let web_config = &config.dioxus_config.web.https;
if web_config.enabled != Some(true) {
return Ok(None);
}
let (cert_path, key_path) = match web_config.mkcert {
// mkcert, use it
Some(true) => {
// Get paths to store certs, otherwise use ssl/item.pem
let key_path = web_config
.key_path
.clone()
.unwrap_or(DEFAULT_KEY_PATH.to_string());
let cert_path = web_config
.cert_path
.clone()
.unwrap_or(DEFAULT_CERT_PATH.to_string());
// Create ssl directory if using defaults
if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH {
_ = fs::create_dir("ssl");
}
let cmd = Command::new("mkcert")
.args([
"-install",
"-key-file",
&key_path,
"-cert-file",
&cert_path,
"localhost",
"::1",
"127.0.0.1",
])
.spawn();
match cmd {
Err(e) => {
match e.kind() {
io::ErrorKind::NotFound => log::error!("mkcert is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions."),
e => log::error!("an error occured while generating mkcert certificates: {}", e.to_string()),
};
return Err("failed to generate mkcert certificates".into());
}
Ok(mut cmd) => {
cmd.wait()?;
}
}
(cert_path, key_path)
}
// not mkcert
Some(false) => {
// get paths to cert & key
if let (Some(key), Some(cert)) =
(web_config.key_path.clone(), web_config.cert_path.clone())
{
(cert, key)
} else {
// missing cert or key
return Err("https is enabled but cert or key path is missing".into());
}
}
// other
_ => return Ok(None),
};
Ok(Some(
RustlsConfig::from_pem_file(cert_path, key_path).await?,
))
}
/// Sets up and returns a router
async fn setup_router(
config: CrateConfig,
ws_reload: Arc<WsReloadState>,
hot_reload: Option<Arc<HotReloadState>>,
) -> Result<Router> {
// Setup cors
let cors = CorsLayer::new()
// allow `GET` and `POST` when accessing the resource
.allow_methods([Method::GET, Method::POST])
// allow requests from any origin
.allow_origin(Any)
.allow_headers(Any);
let (coep, coop) = if config.cross_origin_policy {
(
HeaderValue::from_static("require-corp"),
HeaderValue::from_static("same-origin"),
)
} else {
(
HeaderValue::from_static("unsafe-none"),
HeaderValue::from_static("unsafe-none"),
)
};
// Create file service
let file_service_config = config.clone();
let file_service = ServiceBuilder::new()
.override_response_header(
HeaderName::from_static("cross-origin-embedder-policy"),
coep,
)
.override_response_header(HeaderName::from_static("cross-origin-opener-policy"), coop)
.and_then(
move |response: Response<ServeFileSystemResponseBody>| async move {
let response = if file_service_config
.dioxus_config
.web
.watcher
.index_on_404
.unwrap_or(false)
&& response.status() == StatusCode::NOT_FOUND
{
let body = Full::from(
// TODO: Cache/memoize this.
std::fs::read_to_string(
file_service_config
.crate_dir
.join(file_service_config.out_dir)
.join("index.html"),
)
.ok()
.unwrap(),
)
.map_err(|err| match err {})
.boxed();
Response::builder()
.status(StatusCode::OK)
.body(body)
.unwrap()
} else {
response.map(|body| body.boxed())
};
Ok(response)
},
)
.service(ServeDir::new(config.crate_dir.join(&config.out_dir)));
// Setup websocket
let mut router = Router::new().route("/_dioxus/ws", get(ws_handler));
// Setup proxy
for proxy_config in config.dioxus_config.web.proxy.unwrap_or_default() {
router = proxy::add_proxy(router, &proxy_config)?;
}
// Route file service
router = router.fallback(get_service(file_service).handle_error(
|error: std::io::Error| async move {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled internal error: {}", error),
)
},
));
// Setup routes
router = router
.route("/_dioxus/hot_reload", get(hot_reload_handler))
.layer(cors)
.layer(Extension(ws_reload));
if let Some(hot_reload) = hot_reload {
router = router.layer(Extension(hot_reload))
}
Ok(router)
}
/// Starts dx serve with no hot reload
async fn start_server(
port: u16,
router: Router,
start_browser: bool,
rustls: Option<RustlsConfig>,
) -> Result<()> {
// If plugins, call on_serve_start event
#[cfg(feature = "plugin")]
PluginManager::on_serve_start(&config)?;
// Parse address
let addr = format!("0.0.0.0:{}", port).parse().unwrap();
// Open the browser
if start_browser {
match rustls {
Some(_) => _ = open::that(format!("https://{}", addr)),
None => _ = open::that(format!("http://{}", addr)),
}
}
// Start the server with or without rustls
match rustls {
Some(rustls) => {
axum_server::bind_rustls(addr, rustls)
.serve(router.into_make_service())
.await?
}
None => {
axum::Server::bind(&addr)
.serve(router.into_make_service())
.await?
}
}
Ok(())
}
/// Get the network ip
fn get_ip() -> Option<String> {
let socket = match UdpSocket::bind("0.0.0.0:0") {
Ok(s) => s,
Err(_) => return None,
};
match socket.connect("8.8.8.8:80") {
Ok(()) => (),
Err(_) => return None,
};
match socket.local_addr() {
Ok(addr) => Some(addr.ip().to_string()),
Err(_) => None,
}
}
/// Handle websockets
async fn ws_handler(
ws: WebSocketUpgrade,
_: Option<TypedHeader<headers::UserAgent>>,
Extension(state): Extension<Arc<WsReloadState>>,
) -> impl IntoResponse {
ws.on_upgrade(|mut socket| async move {
let mut rx = state.update.subscribe();
let reload_watcher = tokio::spawn(async move {
loop {
rx.recv().await.unwrap();
// ignore the error
if socket
.send(Message::Text(String::from("reload")))
.await
.is_err()
{
break;
}
// flush the errors after recompling
rx = rx.resubscribe();
}
});
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)
}
File renamed without changes.
-2
View File
@@ -33,8 +33,6 @@ log = { workspace = true }
# Serialize the Edits for use in Webview/Liveview instances
serde = { version = "1", features = ["derive"], optional = true }
bumpslab = { version = "0.2.0" }
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
dioxus = { workspace = true }
+5 -5
View File
@@ -91,21 +91,21 @@ impl VirtualDom {
// Note: This will not remove any ids from the arena
pub(crate) fn drop_scope(&mut self, id: ScopeId, recursive: bool) {
self.dirty_scopes.remove(&DirtyScope {
height: self.scopes[id].height,
height: self.scopes[id.0].height,
id,
});
self.ensure_drop_safety(id);
if recursive {
if let Some(root) = self.scopes[id].try_root_node() {
if let Some(root) = self.scopes[id.0].try_root_node() {
if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } {
self.drop_scope_inner(node)
}
}
}
let scope = &mut self.scopes[id];
let scope = &mut self.scopes[id.0];
// Drop all the hooks once the children are dropped
// this means we'll drop hooks bottom-up
@@ -116,7 +116,7 @@ impl VirtualDom {
scope.tasks.remove(task_id);
}
self.scopes.remove(id);
self.scopes.remove(id.0);
}
fn drop_scope_inner(&mut self, node: &VNode) {
@@ -137,7 +137,7 @@ impl VirtualDom {
/// Descend through the tree, removing any borrowed props and listeners
pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
let scope = &self.scopes[scope_id];
let scope = &self.scopes[scope_id.0];
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
// run the hooks (which hold an &mut Reference)
+2 -3
View File
@@ -88,8 +88,7 @@ impl<'b> VirtualDom {
}
// Intialize the root nodes slice
node.root_ids
.intialize(vec![ElementId(0); node.template.get().roots.len()].into_boxed_slice());
*node.root_ids.borrow_mut() = vec![ElementId(0); node.template.get().roots.len()];
// The best renderers will have templates prehydrated and registered
// Just in case, let's create the template using instructions anyways
@@ -328,7 +327,7 @@ impl<'b> VirtualDom {
fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId {
// Get an ID for this root since it's a real root
let this_id = self.next_root(template, root_idx);
template.root_ids.set(root_idx, this_id);
template.root_ids.borrow_mut()[root_idx] = this_id;
self.mutations.push(LoadTemplate {
name: template.template.get().name,
+19 -17
View File
@@ -15,7 +15,7 @@ use DynamicNode::*;
impl<'b> VirtualDom {
pub(super) fn diff_scope(&mut self, scope: ScopeId) {
let scope_state = &mut self.scopes[scope];
let scope_state = &mut self.scopes[scope.0];
self.scope_stack.push(scope);
unsafe {
@@ -129,12 +129,14 @@ impl<'b> VirtualDom {
});
// Make sure the roots get transferred over while we're here
right_template.root_ids.transfer(&left_template.root_ids);
*right_template.root_ids.borrow_mut() = left_template.root_ids.borrow().clone();
let root_ids = right_template.root_ids.borrow();
// Update the node refs
for i in 0..right_template.root_ids.len() {
if let Some(root_id) = right_template.root_ids.get(i) {
self.update_template(root_id, right_template);
for i in 0..root_ids.len() {
if let Some(root_id) = root_ids.get(i) {
self.update_template(*root_id, right_template);
}
}
}
@@ -189,7 +191,7 @@ impl<'b> VirtualDom {
right.scope.set(Some(scope_id));
// copy out the box for both
let old = self.scopes[scope_id].props.as_ref();
let old = self.scopes[scope_id.0].props.as_ref();
let new: Box<dyn AnyProps> = right.props.take().unwrap();
let new: Box<dyn AnyProps> = unsafe { std::mem::transmute(new) };
@@ -201,14 +203,14 @@ impl<'b> VirtualDom {
}
// First, move over the props from the old to the new, dropping old props in the process
self.scopes[scope_id].props = Some(new);
self.scopes[scope_id.0].props = Some(new);
// Now run the component and diff it
self.run_scope(scope_id);
self.diff_scope(scope_id);
self.dirty_scopes.remove(&DirtyScope {
height: self.scopes[scope_id].height,
height: self.scopes[scope_id.0].height,
id: scope_id,
});
}
@@ -686,7 +688,7 @@ impl<'b> VirtualDom {
Some(node) => node,
None => {
self.mutations.push(Mutation::PushRoot {
id: node.root_ids.get(idx).unwrap(),
id: node.root_ids.borrow()[idx],
});
return 1;
}
@@ -712,7 +714,7 @@ impl<'b> VirtualDom {
Component(comp) => {
let scope = comp.scope.get().unwrap();
match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } {
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
RenderReturn::Ready(node) => self.push_all_real_nodes(node),
RenderReturn::Aborted(_node) => todo!(),
}
@@ -821,7 +823,7 @@ impl<'b> VirtualDom {
if let Some(dy) = node.dynamic_root(idx) {
self.remove_dynamic_node(dy, gen_muts);
} else {
let id = node.root_ids.get(idx).unwrap();
let id = node.root_ids.borrow()[idx];
if gen_muts {
self.mutations.push(Mutation::Remove { id });
}
@@ -913,13 +915,13 @@ impl<'b> VirtualDom {
.expect("VComponents to always have a scope");
// Remove the component from the dom
match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } {
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
RenderReturn::Ready(t) => self.remove_node(t, gen_muts),
RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts),
};
// Restore the props back to the vcomponent in case it gets rendered again
let props = self.scopes[scope].props.take();
let props = self.scopes[scope.0].props.take();
*comp.props.borrow_mut() = unsafe { std::mem::transmute(props) };
// Now drop all the resouces
@@ -928,13 +930,13 @@ impl<'b> VirtualDom {
fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
match node.dynamic_root(0) {
None => node.root_ids.get(0).unwrap(),
None => node.root_ids.borrow()[0],
Some(Text(t)) => t.id.get().unwrap(),
Some(Fragment(t)) => self.find_first_element(&t[0]),
Some(Placeholder(t)) => t.id.get().unwrap(),
Some(Component(comp)) => {
let scope = comp.scope.get().unwrap();
match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } {
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
RenderReturn::Ready(t) => self.find_first_element(t),
_ => todo!("cannot handle nonstandard nodes"),
}
@@ -944,13 +946,13 @@ impl<'b> VirtualDom {
fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId {
match node.dynamic_root(node.template.get().roots.len() - 1) {
None => node.root_ids.last().unwrap(),
None => *node.root_ids.borrow().last().unwrap(),
Some(Text(t)) => t.id.get().unwrap(),
Some(Fragment(t)) => self.find_last_element(t.last().unwrap()),
Some(Placeholder(t)) => t.id.get().unwrap(),
Some(Component(comp)) => {
let scope = comp.scope.get().unwrap();
match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } {
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
RenderReturn::Ready(t) => self.find_last_element(t),
_ => todo!("cannot handle nonstandard nodes"),
}
+3 -102
View File
@@ -5,7 +5,7 @@ use bumpalo::boxed::Box as BumpBox;
use bumpalo::Bump;
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell, UnsafeCell},
cell::{Cell, RefCell},
fmt::{Arguments, Debug},
};
@@ -54,7 +54,7 @@ pub struct VNode<'a> {
/// The IDs for the roots of this template - to be used when moving the template around and removing it from
/// the actual Dom
pub root_ids: BoxedCellSlice,
pub root_ids: RefCell<Vec<ElementId>>,
/// The dynamic parts of the template
pub dynamic_nodes: &'a [DynamicNode<'a>],
@@ -63,112 +63,13 @@ pub struct VNode<'a> {
pub dynamic_attrs: &'a [Attribute<'a>],
}
// Saftey: There is no way to get references to the internal data of this struct so no refrences will be invalidated by mutating the data with a immutable reference (The same principle behind Cell)
#[derive(Debug, Default)]
pub struct BoxedCellSlice(UnsafeCell<Option<Box<[ElementId]>>>);
impl Clone for BoxedCellSlice {
fn clone(&self) -> Self {
Self(UnsafeCell::new(unsafe { (*self.0.get()).clone() }))
}
}
impl BoxedCellSlice {
pub fn last(&self) -> Option<ElementId> {
unsafe {
(*self.0.get())
.as_ref()
.and_then(|inner| inner.as_ref().last().copied())
}
}
pub fn get(&self, idx: usize) -> Option<ElementId> {
unsafe {
(*self.0.get())
.as_ref()
.and_then(|inner| inner.as_ref().get(idx).copied())
}
}
pub unsafe fn get_unchecked(&self, idx: usize) -> Option<ElementId> {
(*self.0.get())
.as_ref()
.and_then(|inner| inner.as_ref().get(idx).copied())
}
pub fn set(&self, idx: usize, new: ElementId) {
unsafe {
if let Some(inner) = &mut *self.0.get() {
inner[idx] = new;
}
}
}
pub fn intialize(&self, contents: Box<[ElementId]>) {
unsafe {
*self.0.get() = Some(contents);
}
}
pub fn transfer(&self, other: &Self) {
unsafe {
*self.0.get() = (*other.0.get()).clone();
}
}
pub fn take_from(&self, other: &Self) {
unsafe {
*self.0.get() = (*other.0.get()).take();
}
}
pub fn len(&self) -> usize {
unsafe {
(*self.0.get())
.as_ref()
.map(|inner| inner.len())
.unwrap_or(0)
}
}
}
impl<'a> IntoIterator for &'a BoxedCellSlice {
type Item = ElementId;
type IntoIter = BoxedCellSliceIter<'a>;
fn into_iter(self) -> Self::IntoIter {
BoxedCellSliceIter {
index: 0,
borrow: self,
}
}
}
pub struct BoxedCellSliceIter<'a> {
index: usize,
borrow: &'a BoxedCellSlice,
}
impl Iterator for BoxedCellSliceIter<'_> {
type Item = ElementId;
fn next(&mut self) -> Option<Self::Item> {
let result = self.borrow.get(self.index);
if result.is_some() {
self.index += 1;
}
result
}
}
impl<'a> VNode<'a> {
/// Create a template with no nodes that will be skipped over during diffing
pub fn empty() -> Element<'a> {
Some(VNode {
key: None,
parent: None,
root_ids: BoxedCellSlice::default(),
root_ids: Default::default(),
dynamic_nodes: &[],
dynamic_attrs: &[],
template: Cell::new(Template {
+1 -1
View File
@@ -20,7 +20,7 @@ impl VirtualDom {
// If the task completes...
if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
// Remove it from the scope so we dont try to double drop it when the scope dropes
let scope = &self.scopes[task.scope];
let scope = &self.scopes[task.scope.0];
scope.spawned_tasks.borrow_mut().remove(&id);
// Remove it from the scheduler
+12 -12
View File
@@ -16,9 +16,9 @@ impl VirtualDom {
let parent = self.acquire_current_scope_raw();
let entry = self.scopes.vacant_entry();
let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) };
let id = entry.key();
let id = ScopeId(entry.key());
entry.insert(ScopeState {
entry.insert(Box::new(ScopeState {
parent,
id,
height,
@@ -35,13 +35,13 @@ impl VirtualDom {
shared_contexts: Default::default(),
borrowed_props: Default::default(),
attributes_to_drop: Default::default(),
})
}))
}
fn acquire_current_scope_raw(&self) -> Option<*const ScopeState> {
let id = self.scope_stack.last().copied()?;
let scope = self.scopes.get(id)?;
Some(scope)
let scope = self.scopes.get(id.0)?;
Some(scope.as_ref())
}
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
@@ -51,9 +51,9 @@ impl VirtualDom {
self.ensure_drop_safety(scope_id);
let new_nodes = unsafe {
self.scopes[scope_id].previous_frame().bump_mut().reset();
self.scopes[scope_id.0].previous_frame().bump_mut().reset();
let scope = &self.scopes[scope_id];
let scope = &self.scopes[scope_id.0];
scope.suspended.set(false);
scope.hook_idx.set(0);
@@ -65,7 +65,7 @@ impl VirtualDom {
props.render(scope).extend_lifetime()
};
let scope = &self.scopes[scope_id];
let scope = &self.scopes[scope_id.0];
// We write on top of the previous frame and then make it the current by pushing the generation forward
let frame = scope.previous_frame();
@@ -83,12 +83,12 @@ impl VirtualDom {
id: scope.id,
});
if matches!(allocated, RenderReturn::Aborted(_)) {
if scope.suspended.get() {
if scope.suspended.get() {
if matches!(allocated, RenderReturn::Aborted(_)) {
self.suspended_scopes.insert(scope.id);
} else if !self.suspended_scopes.is_empty() {
_ = self.suspended_scopes.remove(&scope.id);
}
} else if !self.suspended_scopes.is_empty() {
_ = self.suspended_scopes.remove(&scope.id);
}
// rebind the lifetime now that its stored internally
-92
View File
@@ -9,15 +9,12 @@ use crate::{
AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId,
};
use bumpalo::{boxed::Box as BumpBox, Bump};
use bumpslab::{BumpSlab, Slot};
use rustc_hash::FxHashSet;
use slab::{Slab, VacantEntry};
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell, UnsafeCell},
fmt::{Arguments, Debug},
future::Future,
ops::{Index, IndexMut},
rc::Rc,
sync::Arc,
};
@@ -65,95 +62,6 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
pub struct ScopeId(pub usize);
/// A thin wrapper around a BumpSlab that uses ids to index into the slab.
pub(crate) struct ScopeSlab {
slab: BumpSlab<ScopeState>,
// a slab of slots of stable pointers to the ScopeState in the bump slab
entries: Slab<Slot<'static, ScopeState>>,
}
impl Drop for ScopeSlab {
fn drop(&mut self) {
// Bump slab doesn't drop its contents, so we need to do it manually
for slot in self.entries.drain() {
self.slab.remove(slot);
}
}
}
impl Default for ScopeSlab {
fn default() -> Self {
Self {
slab: BumpSlab::new(),
entries: Slab::new(),
}
}
}
impl ScopeSlab {
pub(crate) fn get(&self, id: ScopeId) -> Option<&ScopeState> {
self.entries.get(id.0).map(|slot| unsafe { &*slot.ptr() })
}
pub(crate) fn get_mut(&mut self, id: ScopeId) -> Option<&mut ScopeState> {
self.entries
.get(id.0)
.map(|slot| unsafe { &mut *slot.ptr_mut() })
}
pub(crate) fn vacant_entry(&mut self) -> ScopeSlabEntry {
let entry = self.entries.vacant_entry();
ScopeSlabEntry {
slab: &mut self.slab,
entry,
}
}
pub(crate) fn remove(&mut self, id: ScopeId) {
self.slab.remove(self.entries.remove(id.0));
}
pub(crate) fn contains(&self, id: ScopeId) -> bool {
self.entries.contains(id.0)
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &ScopeState> {
self.entries.iter().map(|(_, slot)| unsafe { &*slot.ptr() })
}
}
pub(crate) struct ScopeSlabEntry<'a> {
slab: &'a mut BumpSlab<ScopeState>,
entry: VacantEntry<'a, Slot<'static, ScopeState>>,
}
impl<'a> ScopeSlabEntry<'a> {
pub(crate) fn key(&self) -> ScopeId {
ScopeId(self.entry.key())
}
pub(crate) fn insert(self, scope: ScopeState) -> &'a ScopeState {
let slot = self.slab.push(scope);
// this is safe because the slot is only ever accessed with the lifetime of the borrow of the slab
let slot = unsafe { std::mem::transmute(slot) };
let entry = self.entry.insert(slot);
unsafe { &*entry.ptr() }
}
}
impl Index<ScopeId> for ScopeSlab {
type Output = ScopeState;
fn index(&self, id: ScopeId) -> &Self::Output {
self.get(id).unwrap()
}
}
impl IndexMut<ScopeId> for ScopeSlab {
fn index_mut(&mut self, id: ScopeId) -> &mut Self::Output {
self.get_mut(id).unwrap()
}
}
/// A component's state separate from its props.
///
/// This struct exists to provide a common interface for all scopes without relying on generics.
+10 -7
View File
@@ -5,7 +5,7 @@
use crate::{
any_props::VProps,
arena::{ElementId, ElementRef},
innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg, ScopeSlab},
innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
mutations::Mutation,
nodes::RenderReturn,
nodes::{Template, TemplateId},
@@ -176,7 +176,7 @@ use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
pub struct VirtualDom {
// Maps a template path to a map of byteindexes to templates
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
pub(crate) scopes: ScopeSlab,
pub(crate) scopes: Slab<Box<ScopeState>>,
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
pub(crate) scheduler: Rc<Scheduler>,
@@ -281,14 +281,14 @@ impl VirtualDom {
///
/// This is useful for inserting or removing contexts from a scope, or rendering out its root node
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
self.scopes.get(id)
self.scopes.get(id.0).map(|f| f.as_ref())
}
/// Get the single scope at the top of the VirtualDom tree that will always be around
///
/// This scope has a ScopeId of 0 and is the root of the tree
pub fn base_scope(&self) -> &ScopeState {
self.scopes.get(ScopeId(0)).unwrap()
self.get_scope(ScopeId(0)).unwrap()
}
/// Build the virtualdom with a global context inserted into the base scope
@@ -303,7 +303,7 @@ impl VirtualDom {
///
/// Whenever the VirtualDom "works", it will re-render this scope
pub fn mark_dirty(&mut self, id: ScopeId) {
if let Some(scope) = self.scopes.get(id) {
if let Some(scope) = self.get_scope(id) {
let height = scope.height;
self.dirty_scopes.insert(DirtyScope { height, id });
}
@@ -496,7 +496,7 @@ impl VirtualDom {
pub fn replace_template(&mut self, template: Template<'static>) {
self.register_template_first_byte_index(template);
// iterating a slab is very inefficient, but this is a rare operation that will only happen during development so it's fine
for scope in self.scopes.iter() {
for (_, scope) in self.scopes.iter() {
if let Some(RenderReturn::Ready(sync)) = scope.try_root_node() {
if sync.template.get().name.rsplit_once(':').unwrap().0
== template.name.rsplit_once(':').unwrap().0
@@ -571,12 +571,15 @@ impl VirtualDom {
/// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
pub async fn wait_for_suspense(&mut self) {
loop {
// println!("waiting for suspense {:?}", self.suspended_scopes);
if self.suspended_scopes.is_empty() {
return;
}
// println!("waiting for suspense");
self.wait_for_work().await;
// println!("Rendered immediately");
_ = self.render_immediate();
}
}
@@ -598,7 +601,7 @@ impl VirtualDom {
self.dirty_scopes.remove(&dirty);
// If the scope doesn't exist for whatever reason, then we should skip it
if !self.scopes.contains(dirty.id) {
if !self.scopes.contains(dirty.id.0) {
continue;
}
+4
View File
@@ -1,3 +1,5 @@
#![cfg(not(miri))]
use dioxus::prelude::Props;
use dioxus_core::*;
use std::{cell::Cell, collections::HashSet};
@@ -314,6 +316,7 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
}
// test for panics when creating random nodes and templates
#[cfg(not(miri))]
#[test]
fn create() {
for _ in 0..1000 {
@@ -325,6 +328,7 @@ fn create() {
// test for panics when diffing random nodes
// This test will change the template every render which is not very realistic, but it helps stress the system
#[cfg(not(miri))]
#[test]
fn diff() {
for _ in 0..100000 {
+13 -8
View File
@@ -1,17 +1,22 @@
use dioxus::prelude::*;
#[tokio::test]
async fn it_works() {
#[test]
fn it_works() {
// wait just a moment, not enough time for the boundary to resolve
let mut dom = VirtualDom::new(app);
_ = dom.rebuild();
dom.wait_for_suspense().await;
let out = dioxus_ssr::pre_render(&dom);
tokio::runtime::Builder::new_current_thread()
.build()
.unwrap()
.block_on(async {
let mut dom = VirtualDom::new(app);
_ = dom.rebuild();
dom.wait_for_suspense().await;
let out = dioxus_ssr::pre_render(&dom);
assert_eq!(out, "<div>Waiting for... child</div>");
assert_eq!(out, "<div>Waiting for... child</div>");
dbg!(out);
dbg!(out);
});
}
fn app(cx: Scope) -> Element {
+9 -5
View File
@@ -1,10 +1,11 @@
//! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely
use dioxus::prelude::*;
use std::time::Duration;
use std::{sync::atomic::AtomicUsize, time::Duration};
static mut POLL_COUNT: usize = 0;
static POLL_COUNT: AtomicUsize = AtomicUsize::new(0);
#[cfg(not(miri))]
#[tokio::test]
async fn it_works() {
let mut dom = VirtualDom::new(app);
@@ -18,7 +19,10 @@ async fn it_works() {
// By the time the tasks are finished, we should've accumulated ticks from two tasks
// Be warned that by setting the delay to too short, tokio might not schedule in the tasks
assert_eq!(unsafe { POLL_COUNT }, 135);
assert_eq!(
POLL_COUNT.fetch_add(0, std::sync::atomic::Ordering::Relaxed),
135
);
}
fn app(cx: Scope) -> Element {
@@ -26,14 +30,14 @@ fn app(cx: Scope) -> Element {
cx.spawn(async {
for x in 0..10 {
tokio::time::sleep(Duration::from_micros(50)).await;
unsafe { POLL_COUNT += x }
POLL_COUNT.fetch_add(x, std::sync::atomic::Ordering::Relaxed);
}
});
cx.spawn(async {
for x in 0..10 {
tokio::time::sleep(Duration::from_micros(25)).await;
unsafe { POLL_COUNT += x * 2 }
POLL_COUNT.fetch_add(x * 2, std::sync::atomic::Ordering::Relaxed);
}
});
});
+39 -36
View File
@@ -14,42 +14,6 @@ pub(crate) struct FileDialogRequest {
pub bubbles: bool,
}
fn get_file_event_for_folder(request: &FileDialogRequest, dialog: rfd::FileDialog) -> Vec<PathBuf> {
if request.multiple {
dialog.pick_folders().into_iter().flatten().collect()
} else {
dialog.pick_folder().into_iter().collect()
}
}
fn get_file_event_for_file(
request: &FileDialogRequest,
mut dialog: rfd::FileDialog,
) -> Vec<PathBuf> {
let filters: Vec<_> = request
.accept
.as_deref()
.unwrap_or_default()
.split(',')
.filter_map(|s| Filters::from_str(s).ok())
.collect();
let file_extensions: Vec<_> = filters
.iter()
.flat_map(|f| f.as_extensions().into_iter())
.collect();
dialog = dialog.add_filter("name", file_extensions.as_slice());
let files: Vec<_> = if request.multiple {
dialog.pick_files().into_iter().flatten().collect()
} else {
dialog.pick_file().into_iter().collect()
};
files
}
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
@@ -73,6 +37,45 @@ pub(crate) fn get_file_event(_request: &FileDialogRequest) -> Vec<PathBuf> {
target_os = "openbsd"
))]
pub(crate) fn get_file_event(request: &FileDialogRequest) -> Vec<PathBuf> {
fn get_file_event_for_folder(
request: &FileDialogRequest,
dialog: rfd::FileDialog,
) -> Vec<PathBuf> {
if request.multiple {
dialog.pick_folders().into_iter().flatten().collect()
} else {
dialog.pick_folder().into_iter().collect()
}
}
fn get_file_event_for_file(
request: &FileDialogRequest,
mut dialog: rfd::FileDialog,
) -> Vec<PathBuf> {
let filters: Vec<_> = request
.accept
.as_deref()
.unwrap_or_default()
.split(',')
.filter_map(|s| Filters::from_str(s).ok())
.collect();
let file_extensions: Vec<_> = filters
.iter()
.flat_map(|f| f.as_extensions().into_iter())
.collect();
dialog = dialog.add_filter("name", file_extensions.as_slice());
let files: Vec<_> = if request.multiple {
dialog.pick_files().into_iter().flatten().collect()
} else {
dialog.pick_file().into_iter().collect()
};
files
}
let dialog = rfd::FileDialog::new();
if request.directory {
+1 -1
View File
@@ -20,7 +20,7 @@ fn app(cx: Scope) -> Element {
background_color: "hsl({hue}, 70%, {brightness}%)",
onmousemove: move |evt| {
if let RenderReturn::Ready(node) = cx.root_node() {
if let Some(id) = node.root_ids.get(0){
if let Some(id) = node.root_ids.borrow().get(0).cloned() {
let node = tui_query.get(mapping.get_node_id(id).unwrap());
let Size{width, height} = node.size().unwrap();
let pos = evt.inner().element_coordinates();
+8 -8
View File
@@ -1,12 +1,12 @@
{
"name": "dioxus",
"version": "0.0.1",
"version": "0.0.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "dioxus",
"version": "0.0.1",
"version": "0.0.2",
"license": "MIT",
"dependencies": {
"dioxus-ext": "./pkg",
@@ -3720,9 +3720,9 @@
"dev": true
},
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@@ -6444,9 +6444,9 @@
"dev": true
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true
},
"wrappy": {
+21 -94
View File
@@ -1,12 +1,8 @@
#![allow(missing_docs)]
use dioxus_core::{ScopeState, TaskId};
use std::{
any::Any,
cell::{Cell, RefCell},
future::{Future, IntoFuture},
rc::Rc,
sync::Arc,
};
use std::{any::Any, cell::Cell, future::Future, rc::Rc, sync::Arc};
use crate::{use_state, UseState};
/// A future that resolves to a value.
///
@@ -31,43 +27,30 @@ where
F: Future<Output = T> + 'static,
D: UseFutureDep,
{
let val = use_state(cx, || None);
let state = cx.use_hook(move || UseFuture {
update: cx.schedule_update(),
needs_regen: Cell::new(true),
values: Default::default(),
task: Cell::new(None),
state: val.clone(),
task: Default::default(),
dependencies: Vec::new(),
waker: Default::default(),
});
*state.waker.borrow_mut() = None;
if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() {
// We don't need regen anymore
state.needs_regen.set(false);
// kill the old one, if it exists
if let Some(task) = state.task.take() {
cx.remove_future(task);
}
// Create the new future
let fut = future(dependencies.out());
// Clone in our cells
let values = state.values.clone();
let schedule_update = state.update.clone();
let waker = state.waker.clone();
// Cancel the current future
if let Some(current) = state.task.take() {
cx.remove_future(current);
}
let val = val.clone();
let task = state.task.clone();
state.task.set(Some(cx.push_future(async move {
let res = fut.await;
values.borrow_mut().push(Box::leak(Box::new(res)));
// if there's a waker, we dont re-render the component. Instead we just progress that future
match waker.borrow().as_ref() {
Some(waker) => waker.wake_by_ref(),
None => schedule_update(),
}
val.set(Some(fut.await));
task.take();
})));
}
@@ -80,21 +63,12 @@ pub enum FutureState<'a, T> {
Regenerating(&'a T), // the old value
}
pub struct UseFuture<T> {
pub struct UseFuture<T: 'static> {
update: Arc<dyn Fn()>,
needs_regen: Cell<bool>,
task: Cell<Option<TaskId>>,
task: Rc<Cell<Option<TaskId>>>,
dependencies: Vec<Box<dyn Any>>,
waker: Rc<RefCell<Option<std::task::Waker>>>,
values: Rc<RefCell<Vec<*mut T>>>,
}
impl<T> Drop for UseFuture<T> {
fn drop(&mut self) {
for value in self.values.take().into_iter() {
drop(unsafe { Box::from_raw(value) })
}
}
state: UseState<Option<T>>,
}
pub enum UseFutureState<'a, T> {
@@ -120,30 +94,16 @@ impl<T> UseFuture<T> {
}
}
// clears the value in the future slot without starting the future over
pub fn clear(&self) -> Option<T> {
todo!()
// (self.update)();
// self.slot.replace(None)
}
// Manually set the value in the future slot without starting the future over
pub fn set(&self, _new_value: T) {
// self.slot.set(Some(new_value));
// self.needs_regen.set(true);
// (self.update)();
todo!()
pub fn set(&self, new_value: T) {
self.state.set(Some(new_value));
}
/// Return any value, even old values if the future has not yet resolved.
///
/// If the future has never completed, the returned value will be `None`.
pub fn value(&self) -> Option<&T> {
self.values
.borrow_mut()
.last()
.cloned()
.map(|x| unsafe { &*x })
self.state.current_val.as_ref().as_ref()
}
/// Get the ID of the future in Dioxus' internal scheduler
@@ -169,35 +129,6 @@ impl<T> UseFuture<T> {
}
}
impl<'a, T> IntoFuture for &'a UseFuture<T> {
type Output = &'a T;
type IntoFuture = UseFutureAwait<'a, T>;
fn into_future(self) -> Self::IntoFuture {
UseFutureAwait { hook: self }
}
}
pub struct UseFutureAwait<'a, T> {
hook: &'a UseFuture<T>,
}
impl<'a, T> Future for UseFutureAwait<'a, T> {
type Output = &'a T;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
match self.hook.values.borrow_mut().last().cloned() {
Some(value) => std::task::Poll::Ready(unsafe { &*value }),
None => {
self.hook.waker.replace(Some(cx.waker().clone()));
std::task::Poll::Pending
}
}
}
}
pub trait UseFutureDep: Sized + Clone {
type Out;
fn out(&self) -> Self::Out;
@@ -343,10 +274,6 @@ mod tests {
let blah = "asd";
});
let g2 = a.await;
let g = fut.await;
todo!()
}
}
+1 -1
View File
@@ -236,7 +236,7 @@ pub(crate) fn create_error_type(
match segment {
RouteSegment::Static(index) => {
error_variants.push(quote! { #error_name(String) });
display_match.push(quote! { Self::#error_name(found) => write!(f, "Static segment '{}' did not match instead found '{found}'", #index)? });
display_match.push(quote! { Self::#error_name(found) => write!(f, "Static segment '{}' did not match instead found '{}'", #index, found)? });
}
RouteSegment::Dynamic(ident, ty) => {
let missing_error = segment.missing_error_name().unwrap();
+1 -1
View File
@@ -90,7 +90,7 @@ impl WebsysDom {
// make sure we set the root node ids even if the node is not dynamic
set_node(
hydrated,
vnode.root_ids.get(i).ok_or(VNodeNotInitialized)?,
*vnode.root_ids.borrow().get(i).ok_or(VNodeNotInitialized)?,
current_child.clone()?,
);
+10 -10
View File
@@ -9,23 +9,23 @@
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.34.3"
"@playwright/test": "^1.36.1"
}
},
"node_modules/@playwright/test": {
"version": "1.34.3",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.34.3.tgz",
"integrity": "sha512-zPLef6w9P6T/iT6XDYG3mvGOqOyb6eHaV9XtkunYs0+OzxBtrPAAaHotc0X+PJ00WPPnLfFBTl7mf45Mn8DBmw==",
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.1.tgz",
"integrity": "sha512-YK7yGWK0N3C2QInPU6iaf/L3N95dlGdbsezLya4n0ZCh3IL7VgPGxC6Gnznh9ApWdOmkJeleT2kMTcWPRZvzqg==",
"dev": true,
"dependencies": {
"@types/node": "*",
"playwright-core": "1.34.3"
"playwright-core": "1.36.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=14"
"node": ">=16"
},
"optionalDependencies": {
"fsevents": "2.3.2"
@@ -52,15 +52,15 @@
}
},
"node_modules/playwright-core": {
"version": "1.34.3",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.34.3.tgz",
"integrity": "sha512-2pWd6G7OHKemc5x1r1rp8aQcpvDh7goMBZlJv6Co5vCNLVcQJdhxRL09SGaY6HcyHH9aT4tiynZabMofVasBYw==",
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.1.tgz",
"integrity": "sha512-7+tmPuMcEW4xeCL9cp9KxmYpQYHKkyjwoXRnoeTowaeNat8PoBMk/HwCYhqkH2fRkshfKEOiVus/IhID2Pg8kg==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=14"
"node": ">=16"
}
}
}
@@ -12,6 +12,6 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.34.3"
"@playwright/test": "^1.36.1"
}
}
@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<meta name='color-scheme' content='dark light'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Playwright Test Report</title>
<script type="module">var s0=Object.defineProperty;var i0=(e,t,n)=>t in e?s0(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var xt=(e,t,n)=>(i0(e,typeof t!="symbol"?t+"":t,n),n);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const i of s.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&r(i)}).observe(document,{childList:!0,subtree:!0});function n(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(o){if(o.ep)return;o.ep=!0;const s=n(o);fetch(o.href,s)}})();var qn=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function l0(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Iu={exports:{}},ls={},Du={exports:{}},q={};/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var qr=Symbol.for("react.element"),c0=Symbol.for("react.portal"),a0=Symbol.for("react.fragment"),u0=Symbol.for("react.strict_mode"),f0=Symbol.for("react.profiler"),d0=Symbol.for("react.provider"),p0=Symbol.for("react.context"),h0=Symbol.for("react.forward_ref"),g0=Symbol.for("react.suspense"),m0=Symbol.for("react.memo"),v0=Symbol.for("react.lazy"),hc=Symbol.iterator;function w0(e){return e===null||typeof e!="object"?null:(e=hc&&e[hc]||e["@@iterator"],typeof e=="function"?e:null)}var Ru={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},bu=Object.assign,Tu={};function Xn(e,t,n){this.props=e,this.context=t,this.refs=Tu,this.updater=n||Ru}Xn.prototype.isReactComponent={};Xn.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};Xn.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function Nu(){}Nu.prototype=Xn.prototype;function al(e,t,n){this.props=e,this.context=t,this.refs=Tu,this.updater=n||Ru}var ul=al.prototype=new Nu;ul.constructor=al;bu(ul,Xn.prototype);ul.isPureReactComponent=!0;var gc=Array.isArray,Lu=Object.prototype.hasOwnProperty,fl={current:null},Pu={key:!0,ref:!0,__self:!0,__source:!0};function Ou(e,t,n){var r,o={},s=null,i=null;if(t!=null)for(r in t.ref!==void 0&&(i=t.ref),t.key!==void 0&&(s=""+t.key),t)Lu.call(t,r)&&!Pu.hasOwnProperty(r)&&(o[r]=t[r]);var l=arguments.length-2;if(l===1)o.children=n;else if(1<l){for(var c=Array(l),u=0;u<l;u++)c[u]=arguments[u+2];o.children=c}if(e&&e.defaultProps)for(r in l=e.defaultProps,l)o[r]===void 0&&(o[r]=l[r]);return{$$typeof:qr,type:e,key:s,ref:i,props:o,_owner:fl.current}}function y0(e,t){return{$$typeof:qr,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}function dl(e){return typeof e=="object"&&e!==null&&e.$$typeof===qr}function A0(e){var t={"=":"=0",":":"=2"};return"$"+e.replace(/[=:]/g,function(n){return t[n]})}var mc=/\/+/g;function Ds(e,t){return typeof e=="object"&&e!==null&&e.key!=null?A0(""+e.key):t.toString(36)}function wo(e,t,n,r,o){var s=typeof e;(s==="undefined"||s==="boolean")&&(e=null);var i=!1;if(e===null)i=!0;else switch(s){case"string":case"number":i=!0;break;case"object":switch(e.$$typeof){case qr:case c0:i=!0}}if(i)return i=e,o=o(i),e=r===""?"."+Ds(i,0):r,gc(o)?(n="",e!=null&&(n=e.replace(mc,"$&/")+"/"),wo(o,t,n,"",function(u){return u})):o!=null&&(dl(o)&&(o=y0(o,n+(!o.key||i&&i.key===o.key?"":(""+o.key).replace(mc,"$&/")+"/")+e)),t.push(o)),1;if(i=0,r=r===""?".":r+":",gc(e))for(var l=0;l<e.length;l++){s=e[l];var c=r+Ds(s,l);i+=wo(s,t,n,c,o)}else if(c=w0(e),typeof c=="function")for(e=c.call(e),l=0;!(s=e.next()).done;)s=s.value,c=r+Ds(s,l++),i+=wo(s,t,n,c,o);else if(s==="object")throw t=String(e),Error("Objects are not valid as a React child (found: "+(t==="[object Object]"?"object with keys {"+Object.keys(e).join(", ")+"}":t)+"). If you meant to render a collection of children, use an array instead.");return i}function Xr(e,t,n){if(e==null)return e;var r=[],o=0;return wo(e,r,"","",function(s){return t.call(n,s,o++)}),r}function E0(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(n){(e._status===0||e._status===-1)&&(e._status=1,e._result=n)},function(n){(e._status===0||e._status===-1)&&(e._status=2,e._result=n)}),e._status===-1&&(e._status=0,e._result=t)}if(e._status===1)return e._result.default;throw e._result}var Ee={current:null},yo={transition:null},x0={ReactCurrentDispatcher:Ee,ReactCurrentBatchConfig:yo,ReactCurrentOwner:fl};q.Children={map:Xr,forEach:function(e,t,n){Xr(e,function(){t.apply(this,arguments)},n)},count:function(e){var t=0;return Xr(e,function(){t++}),t},toArray:function(e){return Xr(e,function(t){return t})||[]},only:function(e){if(!dl(e))throw Error("React.Children.only expected to receive a single React element child.");return e}};q.Component=Xn;q.Fragment=a0;q.Profiler=f0;q.PureComponent=al;q.StrictMode=u0;q.Suspense=g0;q.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=x0;q.cloneElement=function(e,t,n){if(e==null)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+e+".");var r=bu({},e.props),o=e.key,s=e.ref,i=e._owner;if(t!=null){if(t.ref!==void 0&&(s=t.ref,i=fl.current),t.key!==void 0&&(o=""+t.key),e.type&&e.type.defaultProps)var l=e.type.defaultProps;for(c in t)Lu.call(t,c)&&!Pu.hasOwnProperty(c)&&(r[c]=t[c]===void 0&&l!==void 0?l[c]:t[c])}var c=arguments.length-2;if(c===1)r.children=n;else if(1<c){l=Array(c);for(var u=0;u<c;u++)l[u]=arguments[u+2];r.children=l}return{$$typeof:qr,type:e.type,key:o,ref:s,props:r,_owner:i}};q.createContext=function(e){return e={$$typeof:p0,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null,_defaultValue:null,_globalName:null},e.Provider={$$typeof:d0,_context:e},e.Line truncated
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var k0=Q,S0=Symbol.for("react.element"),C0=Symbol.for("react.fragment"),I0=Object.prototype.hasOwnProperty,D0=k0.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,R0={key:!0,ref:!0,__self:!0,__source:!0};function Bu(e,t,n){var r,o={},s=null,i=null;n!==void 0&&(s=""+n),t.key!==void 0&&(s=""+t.key),t.ref!==void 0&&(i=t.ref);for(r in t)I0.call(t,r)&&!R0.hasOwnProperty(r)&&(o[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps,t)o[r]===void 0&&(o[r]=t[r]);return{$$typeof:S0,type:e,key:s,ref:i,props:o,_owner:D0.current}}ls.Fragment=C0;ls.jsx=Bu;ls.jsxs=Bu;Iu.exports=ls;var pl=Iu.exports;const dn=pl.Fragment,A=pl.jsx,O=pl.jsxs,b0=15,j=0,ht=1,T0=2,Se=-2,Z=-3,vc=-4,gt=-5,Le=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],Hu=1440,N0=0,L0=4,P0=9,O0=5,B0=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],H0=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],M0=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27Line truncated
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/(function(e){function t(b,M){var N=b.length;b.push(M);e:for(;0<N;){var X=N-1>>>1,F=b[X];if(0<o(F,M))b[X]=M,b[N]=F,N=X;else break e}}function n(b){return b.length===0?null:b[0]}function r(b){if(b.length===0)return null;var M=b[0],N=b.pop();if(N!==M){b[0]=N;e:for(var X=0,F=b.length,Y=F>>>1;X<Y;){var zt=2*(X+1)-1,Is=b[zt],Kt=zt+1,Yr=b[Kt];if(0>o(Is,N))Kt<F&&0>o(Yr,Is)?(b[X]=Yr,b[Kt]=N,X=Kt):(b[X]=Is,b[zt]=N,X=zt);else if(Kt<F&&0>o(Yr,N))b[X]=Yr,b[Kt]=N,X=Kt;else break e}}return M}function o(b,M){var N=b.sortIndex-M.sortIndex;return N!==0?N:b.id-M.id}if(typeof performance=="object"&&typeof performance.now=="function"){var s=performance;e.unstable_now=function(){return s.now()}}else{var i=Date,l=i.now();e.unstable_now=function(){return i.now()-l}}var c=[],u=[],p=1,v=null,h=3,C=!1,E=!1,g=!1,m=typeof setTimeout=="function"?setTimeout:null,a=typeof clearTimeout=="function"?clearTimeout:null,f=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function d(b){for(var M=n(u);M!==null;){if(M.callback===null)r(u);else if(M.startTime<=b)r(u),M.sortIndex=M.expirationTime,t(c,M);else break;M=n(u)}}function y(b){if(g=!1,d(b),!E)if(n(c)!==null)E=!0,H(k);else{var M=n(u);M!==null&&re(y,M.startTime-b)}}function k(b,M){E=!1,g&&(g=!1,a(S),S=-1),C=!0;var N=h;try{for(d(M),v=n(c);v!==null&&(!(v.expirationTime>M)||b&&!T());){var X=v.callback;if(typeof X=="function"){v.callback=null,h=v.priorityLevel;var F=X(v.expirationTime<=M);M=e.unstable_now(),typeof F=="function"?v.callback=F:v===n(c)&&r(c),d(M)}else r(c);v=n(c)}if(v!==null)var Y=!0;else{var zt=n(u);zt!==null&&re(y,zt.startTime-M),Y=!1}return Y}finally{v=null,h=N,C=!1}}var w=!1,x=null,S=-1,D=5,I=-1;function T(){return!(e.unstable_now()-I<D)}function B(){if(x!==null){var b=e.unstable_now();I=b;var M=!0;try{M=x(!0,b)}finally{M?P():(w=!1,x=null)}}else w=!1}var P;if(typeof f=="function")P=function(){f(B)};else if(typeof MessageChannel<"u"){var G=new MessageChannel,U=G.port2;G.port1.onmessage=B,P=function(){U.postMessage(null)}}else P=function(){m(B,0)};function H(b){x=b,w||(w=!0,P())}function re(b,M){S=m(function(){b(e.unstable_now())},M)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(b){b.callback=null},e.unstable_continueExecution=function(){E||C||(E=!0,H(k))},e.unstable_forceFrameRate=function(b){0>b||125<b?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):D=0<b?Math.floor(1e3/b):5},e.unstable_getCurrentPriorityLevel=function(){return h},e.unstable_getFirstCallbackNode=function(){return n(c)},e.unstable_next=function(b){switch(h){case 1:case 2:case 3:var M=3;break;default:M=h}var N=h;h=M;try{return b()}finally{h=N}},e.unstable_pauseExecution=function(){},e.unstable_requestPaint=function(){},e.unstable_runWithPriority=function(b,M){switch(b){case 1:case 2:case 3:case 4:case 5:break;default:b=3}var N=h;h=b;try{return M()}finally{h=N}},e.unstable_scheduleCallback=function(b,M,N){var X=e.unstable_now();switch(typeof N=="object"&&N!==null?(N=N.delay,N=typeof N=="number"&&0<N?X+N:X):N=X,b){case 1:var F=-1;break;case 2:F=250;break;case 5:F=1073741823;break;case 4:F=1e4;break;default:F=5e3}return F=N+F,b={id:p++,callback:M,priorityLevel:b,startTime:N,expirationTime:F,sortIndex:-1},N>X?(b.sortIndex=N,t(u,b),n(c)===null&&b===n(u)&&(g?(a(S),S=-1):g=!0,re(y,N-X))):(b.sortIndex=F,t(c,b),E||C||(E=!0,H(k))),b},e.unstable_shouldYield=T,e.unstable_wrapCallback=function(b){var M=h;return function(){var N=h;h=M;try{return b.apply(this,arguments)}finally{h=N}}}})(Sf);kf.exports=Sf;var dg=kf.exports;/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Cf=Q,Fe=dg;function R(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var If=new Set,xr={};function pn(e,t){Qn(e,t),Qn(e+"Capture",t)}function Qn(e,t){for(xr[e]=t,e=0;e<t.length;e++)If.add(t[e])}var wt=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),mi=Object.prototype.hasOwnProperty,pg=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,la={},ca={};function hg(e){return mi.call(ca,e)?!0:mi.call(la,e)?!1:pg.test(e)?ca[e]=!0:(la[e]=!0,!1)}function gg(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function mg(e,t,n,r){if(t===null||typeof t>"u"||gg(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function xe(e,t,n,r,o,s,i){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=o,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=s,this.removeEmptyString=i}var pe={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){pe[e]=new xe(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];pe[t]=new xe(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){pe[e]=new xe(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){pe[e]=new xe(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){pe[e]=new xe(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){pe[e]=new xe(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){pe[e]=new xe(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){pe[e]=new xe(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){pe[e]=new xe(e,5,!1,e.toLowerCase(),null,!1,!1)});var El=/[\-:]([a-z])/g;function xl(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(El,xl);pe[t]=new xe(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(El,xl);pe[t]=new xe(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(El,xl);pe[t]=new xe(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){pe[e]=new xe(e,1,!1,e.toLowerCase(),null,!1,!1)});pe.xlinkHref=new xe("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){pe[e]=new xe(e,1,!1,e.toLowerCase(),null,!0,!0)});function kl(e,t,n,r){var o=pe.hasOwnProperty(t)?pe[t]:null;(o!==null?o.type!==0:r||!(2<t.length)||t[0]!=="o"&&t[0]!=="Line truncated
`+Fs+e}var Us=!1;function qs(e,t){if(!e||Us)return"";Us=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(t)if(t=function(){throw Error()},Object.defineProperty(t.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(t,[])}catch(u){var r=u}Reflect.construct(e,[],t)}else{try{t.call()}catch(u){r=u}e.call(t.prototype)}else{try{throw Error()}catch(u){r=u}e()}}catch(u){if(u&&r&&typeof u.stack=="string"){for(var o=u.stack.split(`
`),s=r.stack.split(`
`),i=o.length-1,l=s.length-1;1<=i&&0<=l&&o[i]!==s[l];)l--;for(;1<=i&&0<=l;i--,l--)if(o[i]!==s[l]){if(i!==1||l!==1)do if(i--,l--,0>l||o[i]!==s[l]){var c=`
`+o[i].replace(" at new "," at ");return e.displayName&&c.includes("<anonymous>")&&(c=c.replace("<anonymous>",e.displayName)),c}while(1<=i&&0<=l);break}}}finally{Us=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?lr(e):""}function vg(e){switch(e.tag){case 5:return lr(e.type);case 16:return lr("Lazy");case 13:return lr("Suspense");case 19:return lr("SuspenseList");case 0:case 2:case 15:return e=qs(e.type,!1),e;case 11:return e=qs(e.type.render,!1),e;case 1:return e=qs(e.type,!0),e;default:return""}}function Ai(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case En:return"Fragment";case An:return"Portal";case vi:return"Profiler";case Sl:return"StrictMode";case wi:return"Suspense";case yi:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Rf:return(e.displayName||"Context")+".Consumer";case Df:return(e._context.displayName||"Context")+".Provider";case Cl:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Il:return t=e.displayName||null,t!==null?t:Ai(e.type)||"Memo";case Dt:t=e._payload,e=e._init;try{return Ai(e(t))}catch{}}return null}function wg(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Ai(t);case 8:return t===Sl?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Qt(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Tf(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function yg(e){var t=Tf(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var o=n.get,s=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(i){r=""+i,s.call(this,i)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(i){r=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function $r(e){e._valueTracker||(e._valueTracker=yg(e))}function Nf(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Tf(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Bo(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Ei(e,t){var n=t.checked;return te({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function ua(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=Qt(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Lf(e,t){t=t.checked,t!=null&&kl(e,"checked",t,!1)}function xi(e,t){Lf(e,t);var n=Qt(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?ki(e,t.type,n):t.hasOwnProperty("defaultValue")&&ki(e,t.type,Qt(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function fa(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function ki(e,t,n){(t!=="number"||Bo(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var cr=Array.isArray;function On(e,t,n,r){if(e=e.options,t){t={};for(var o=0;o<n.length;o++)t["$"+n[o]]=!0;for(n=0;n<e.length;n++)o=t.hasOwnProperty("$"+e[n].value),e[n].selected!==o&&(e[n].selected=o),o&&r&&(e[n].defaultSelected=!0)}else{for(n=""+Qt(n),t=null,o=0;o<e.length;o++){if(e[o].value===n){e[o].selected=!0,r&&(e[o].defaultSelected=!0);return}t!==null||e[o].disabled||(t=e[o])}t!==null&&(t.selected=!0)}}function Si(e,t){if(t.danLine truncated
`).replace(b1,"")}function lo(e,t,n){if(t=Oa(t),Oa(e)!==t&&n)throw Error(R(425))}function Vo(){}var Mi=null,Fi=null;function Ui(e,t){return e==="textarea"||e==="noscript"||typeof t.children=="string"||typeof t.children=="number"||typeof t.dangerouslySetInnerHTML=="object"&&t.dangerouslySetInnerHTML!==null&&t.dangerouslySetInnerHTML.__html!=null}var qi=typeof setTimeout=="function"?setTimeout:void 0,T1=typeof clearTimeout=="function"?clearTimeout:void 0,Ba=typeof Promise=="function"?Promise:void 0,N1=typeof queueMicrotask=="function"?queueMicrotask:typeof Ba<"u"?function(e){return Ba.resolve(null).then(e).catch(L1)}:qi;function L1(e){setTimeout(function(){throw e})}function $s(e,t){var n=t,r=0;do{var o=n.nextSibling;if(e.removeChild(n),o&&o.nodeType===8)if(n=o.data,n==="/$"){if(r===0){e.removeChild(o),Dr(t);return}r--}else n!=="$"&&n!=="$?"&&n!=="$!"||r++;n=o}while(n);Dr(t)}function ft(e){for(;e!=null;e=e.nextSibling){var t=e.nodeType;if(t===1||t===3)break;if(t===8){if(t=e.data,t==="$"||t==="$!"||t==="$?")break;if(t==="/$")return null}}return e}function Ha(e){e=e.previousSibling;for(var t=0;e;){if(e.nodeType===8){var n=e.data;if(n==="$"||n==="$!"||n==="$?"){if(t===0)return e;t--}else n==="/$"&&t++}e=e.previousSibling}return null}var Kn=Math.random().toString(36).slice(2),st="__reactFiber$"+Kn,Lr="__reactProps$"+Kn,yt="__reactContainer$"+Kn,Qi="__reactEvents$"+Kn,P1="__reactListeners$"+Kn,O1="__reactHandles$"+Kn;function tn(e){var t=e[st];if(t)return t;for(var n=e.parentNode;n;){if(t=n[yt]||n[st]){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=Ha(e);e!==null;){if(n=e[st])return n;e=Ha(e)}return t}e=n,n=e.parentNode}return null}function Gr(e){return e=e[st]||e[yt],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function Cn(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(R(33))}function ds(e){return e[Lr]||null}var ji=[],In=-1;function Yt(e){return{current:e}}function J(e){0>In||(e.current=ji[In],ji[In]=null,In--)}function z(e,t){In++,ji[In]=e.current,e.current=t}var jt={},we=Yt(jt),be=Yt(!1),ln=jt;function jn(e,t){var n=e.type.contextTypes;if(!n)return jt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var o={},s;for(s in n)o[s]=t[s];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=o),o}function Te(e){return e=e.childContextTypes,e!=null}function Go(){J(be),J(we)}function Ma(e,t,n){if(we.current!==jt)throw Error(R(168));z(we,t),z(be,n)}function Ad(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var o in r)if(!(o in t))throw Error(R(108,wg(e)||"Unknown",o));return te({},n,r)}function Wo(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||jt,ln=we.current,z(we,e),z(be,be.current),!0}function Fa(e,t,n){var r=e.stateNode;if(!r)throw Error(R(169));n?(e=Ad(e,t,ln),r.__reactInternalMemoizedMergedChildContext=e,J(be),J(we),z(we,e)):J(be),z(be,n)}var ut=null,ps=!1,ei=!1;function Ed(e){ut===null?ut=[e]:ut.push(e)}function B1(e){ps=!0,Ed(e)}function Xt(){if(!ei&&ut!==null){ei=!0;var e=0,t=W;try{var n=ut;for(W=1;e<n.length;e++){var r=n[e];do r=r(!0);while(r!==null)}ut=null,ps=!1}catch(o){throw ut!==null&&(ut=ut.slice(e+1)),Yf(Rl,Xt),o}finally{W=t,ei=!1}}return null}var H1=Et.ReactCurrentBatchConfig;function Ke(e,t){if(e&&e.defaultProps){t=te({},t),e=e.defaultProps;for(var n in e)t[n]===void 0&&(t[n]=e[n]);return t}return t}var Yo=Yt(null),Xo=null,Dn=null,Ml=null;function Fl(){Ml=Dn=Xo=null}function Ul(e){var t=Yo.current;J(Yo),e._currentValue=t}function Vi(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Fn(e,t){Xo=e,Ml=Dn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(Re=!0),e.firstContext=null)}function Ye(e){var t=e._currentValue;if(Ml!==e)if(e={context:e,memoizedValue:t,next:null},Dn===null){if(Xo===null)throw Error(R(308));Dn=e,Xo.dependencies={lanes:0,firstContext:e}}else Dn=Dn.next=e;return t}var $e=null,Rt=!1;function ql(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function xd(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function mt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function Ft(e,t){var n=e.updateQueue;n!==null&&(n=n.shared,fp(e)?(e=n.interleaved,e===null?(t.next=t,$e===null?$e=[n]:$e.push(n)):(t.next=e.next,e.next=t),n.interleaved=t):(e=n.pending,e===null?t.next=t:(t.next=e.next,e.next=t),n.pending=t))}function Co(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanLine truncated
Error generating stack: `+s.message+`
`+s.stack}return{value:e,source:t,stack:o}}function zi(e,t){try{console.error(t.value)}catch(n){setTimeout(function(){throw n})}}var G1=typeof WeakMap=="function"?WeakMap:Map;function Kd(e,t,n){n=mt(-1,n),n.tag=3,n.payload={element:null};var r=t.value;return n.callback=function(){ts||(ts=!0,rl=r),zi(e,t)},n}function Zd(e,t,n){n=mt(-1,n),n.tag=3;var r=e.type.getDerivedStateFromError;if(typeof r=="function"){var o=t.value;n.payload=function(){return r(o)},n.callback=function(){zi(e,t)}}var s=e.stateNode;return s!==null&&typeof s.componentDidCatch=="function"&&(n.callback=function(){zi(e,t),typeof r!="function"&&(Ut===null?Ut=new Set([this]):Ut.add(this));var i=t.stack;this.componentDidCatch(t.value,{componentStack:i!==null?i:""})}),n}function za(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new G1;var o=new Set;r.set(t,o)}else o=r.get(t),o===void 0&&(o=new Set,r.set(t,o));o.has(n)||(o.add(n),e=rm.bind(null,e,t,n),t.then(e,e))}function Ka(e){do{var t;if((t=e.tag===13)&&(t=e.memoizedState,t=t!==null?t.dehydrated!==null:!0),t)return e;e=e.return}while(e!==null);return null}function Za(e,t,n,r,o){return e.mode&1?(e.flags|=65536,e.lanes=o,e):(e===t?e.flags|=65536:(e.flags|=128,n.flags|=131072,n.flags&=-52805,n.tag===1&&(n.alternate===null?n.tag=17:(t=mt(-1,1),t.tag=2,Ft(n,t))),n.lanes|=1),e)}var Jd,Ki,_d,$d;Jd=function(e,t){for(var n=t.child;n!==null;){if(n.tag===5||n.tag===6)e.appendChild(n.stateNode);else if(n.tag!==4&&n.child!==null){n.child.return=n,n=n.child;continue}if(n===t)break;for(;n.sibling===null;){if(n.return===null||n.return===t)return;n=n.return}n.sibling.return=n.return,n=n.sibling}};Ki=function(){};_d=function(e,t,n,r){var o=e.memoizedProps;if(o!==r){e=t.stateNode,nn(ct.current);var s=null;switch(n){case"input":o=Ei(e,o),r=Ei(e,r),s=[];break;case"select":o=te({},o,{value:void 0}),r=te({},r,{value:void 0}),s=[];break;case"textarea":o=Si(e,o),r=Si(e,r),s=[];break;default:typeof o.onClick!="function"&&typeof r.onClick=="function"&&(e.onclick=Vo)}Ii(n,r);var i;n=null;for(u in o)if(!r.hasOwnProperty(u)&&o.hasOwnProperty(u)&&o[u]!=null)if(u==="style"){var l=o[u];for(i in l)l.hasOwnProperty(i)&&(n||(n={}),n[i]="")}else u!=="dangerouslySetInnerHTML"&&u!=="children"&&u!=="suppressContentEditableWarning"&&u!=="suppressHydrationWarning"&&u!=="autoFocus"&&(xr.hasOwnProperty(u)?s||(s=[]):(s=s||[]).push(u,null));for(u in r){var c=r[u];if(l=o!=null?o[u]:void 0,r.hasOwnProperty(u)&&c!==l&&(c!=null||l!=null))if(u==="style")if(l){for(i in l)!l.hasOwnProperty(i)||c&&c.hasOwnProperty(i)||(n||(n={}),n[i]="");for(i in c)c.hasOwnProperty(i)&&l[i]!==c[i]&&(n||(n={}),n[i]=c[i])}else n||(s||(s=[]),s.push(u,n)),n=c;else u==="dangerouslySetInnerHTML"?(c=c?c.__html:void 0,l=l?l.__html:void 0,c!=null&&l!==c&&(s=s||[]).push(u,c)):u==="children"?typeof c!="string"&&typeof c!="number"||(s=s||[]).push(u,""+c):u!=="suppressContentEditableWarning"&&u!=="suppressHydrationWarning"&&(xr.hasOwnProperty(u)?(c!=null&&u==="onScroll"&&K("scroll",e),s||l===c||(s=[])):(s=s||[]).push(u,c))}n&&(s=s||[]).push("style",n);var u=s;(t.updateQueue=u)&&(t.flags|=4)}};$d=function(e,t,n,r){n!==r&&(t.flags|=4)};function sr(e,t){if(!_)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;t!==null;)t.alternate!==null&&(n=t),t=t.sibling;n===null?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;n!==null;)n.alternate!==null&&(r=n),n=n.sibling;r===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:r.sibling=null}}function ge(e){var t=e.alternate!==null&&e.alternate.child===e.child,n=0,r=0;if(t)for(var o=e.child;o!==null;)n|=o.lanes|o.childLanes,r|=o.subtreeFlags&14680064,r|=o.flags&14680064,o.return=e,o=o.sibling;else for(o=e.child;o!==null;)n|=o.lanes|o.childLanes,r|=o.subtreeFlags,r|=o.flags,o.return=e,o=o.sibling;return e.subtreeFlags|=r,e.childLanes=n,t}function W1(e,t,n){var r=t.pendingProps;switch(jl(t),t.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return ge(t),null;case 1:return Te(t.type)&&Go(),ge(t),null;case 3:return r=t.stateNode,Wn(),J(be),J(we),Yl(),r.pendingContext&&(r.context=r.pendingContext,r.pendingContext=null),(e===null||e.child===null)&&(rr(t)?t.flags|=4:e===null||e.memoizedState.isDehydrated&&!(t.flags&256)||(t.flags|=1024,Je!==null&&(il(Je),Je=null))),Ki(e,t),ge(t),null;case 5:Wl(t);var o=nn(Or.current);if(n=t.type,e!==null&&t.stateNode!=null)_d(e,t,n,r,o),e.ref!==t.ref&&(t.flags|=512,t.flags|=2097152);else{if(!r){if(t.stateNode===null)throw Error(R(166));return ge(t),null}if(e=nn(ct.current),rr(t)){r=t.stateNode,n=t.type;var s=t.memoizedProps;switch(r[st]=t,r[Lr]=s,e=(t.mode&1)!==0,n){case"dialog":K("cancel",r),K("close",r);break;case"iframe":case"object":case"embed":K("load",r);break;case"video":case"audio":for(o=0;o<ur.length;o++)K(ur[o],r);break;case"source":K("error",r);break;case"img":case"image":case"link":K("error",r),K("load",r);break;case"details":K("toggle",r);break;case"input":ua(r,s),K("invalid",r);break;case"select":r._wrapperState={wasMultiple:!!s.multLine truncated
`,rT="∄",oT="∄",sT="𝔑",iT="𝔫",lT="≧̸",cT="≱",aT="≱",uT="≧̸",fT="⩾̸",dT="⩾̸",pT="⋙̸",hT="≵",gT="≫⃒",mT="≯",vT="≯",wT="≫̸",yT="↮",AT="⇎",ET="⫲",xT="∋",kT="⋼",ST="⋺",CT="∋",IT="Њ",DT="њ",RT="↚",bT="⇍",TT="‥",NT="≦̸",LT="≰",PT="↚",OT="⇍",BT="↮",HT="⇎",MT="≰",FT="≦̸",UT="⩽̸",qT="⩽̸",QT="≮",jT="⋘̸",VT="≴",GT="≪⃒",WT="≮",YT="⋪",XT="⋬",zT="≪̸",KT="∤",ZT="⁠",JT=" ",_T="𝕟",$T="ℕ",eN="⫬",tN="¬",nN="≢",rN="≭",oN="∦",sN="∉",iN="≠",lN="≂̸",cN="∄",aN="≯",uN="≱",fN="≧̸",dN="≫̸",pN="≹",hN="⩾̸",gN="≵",mN="≎̸",vN="≏̸",wN="∉",yN="⋵̸",AN="⋹̸",EN="∉",xN="⋷",kN="⋶",SN="⧏̸",CN="⋪",IN="⋬",DN="≮",RN="≰",bN="≸",TN="≪̸",NN="⩽̸",LN="≴",PN="⪢̸",ON="⪡̸",BN="∌",HN="∌",MN="⋾",FN="⋽",UN="⊀",qN="⪯̸",QN="⋠",jN="∌",VN="⧐̸",GN="⋫",WN="⋭",YN="⊏̸",XN="⋢",zN="⊐̸",KN="⋣",ZN="⊂⃒",JN="⊈",_N="⊁",$N="⪰̸",e9="⋡",t9="≿̸",n9="⊃⃒",r9="⊉",o9="≁",s9="≄",i9="≇",l9="≉",c9="∤",a9="∦",u9="∦",f9="⫽⃥",d9="∂̸",p9="⨔",h9="⊀",g9="⋠",m9="⊀",v9="⪯̸",w9="⪯̸",y9="⤳̸",A9="↛",E9="⇏",x9="↝̸",k9="↛",S9="⇏",C9="⋫",I9="⋭",D9="⊁",R9="⋡",b9="⪰̸",T9="𝒩",N9="𝓃",L9="∤",P9="∦",O9="≁",B9="≄",H9="≄",M9="∤",F9="∦",U9="⋢",q9="⋣",Q9="⊄",j9="⫅̸",V9="⊈",G9="⊂⃒",W9="⊈",Y9="⫅̸",X9="⊁",z9="⪰̸",K9="⊅",Z9="⫆̸",J9="⊉",_9="⊃⃒",$9="⊉",eL="⫆̸",tL="≹",nL="Ñ",rL="ñ",oL="≸",sL="⋪",iL="⋬",lL="⋫",cL="⋭",aL="Ν",uL="ν",fL="#",dL="№",pL=" ",hL="≍⃒",gL="⊬",mL="⊭",vL="⊮",wL="⊯",yL="≥⃒",AL=">⃒",EL="⤄",xL="⧞",kL="⤂",SL="≤⃒",CL="<⃒",IL="⊴⃒",DL="⤃",RL="⊵⃒",bL="∼⃒",TL="⤣",NL="↖",LL="⇖",PL="↖",OL="⤧",BL="Ó",HL="ó",ML="⊛",FL="Ô",UL="ô",qL="⊚",QL="О",jL="о",VL="⊝",GL="Ő",WL="ő",YL="⨸",XL="⊙",zL="⦼",KL="Œ",ZL="œ",JL="⦿",_L="𝔒",$L="𝔬",eP="˛",tP="Ò",nP="ò",rP="⧁",oP="⦵",sP="Ω",iP="∮",lP="↺",cP="⦾",aP="⦻",uP="‾",fP="⧀",dP="Ō",pP="ō",hP="Ω",gP="ω",mP="Ο",vP="ο",wP="⦶",yP="⊖",AP="𝕆",EP="𝕠",xP="⦷",kP="“",SP="‘",CP="⦹",IP="⊕",DP="↻",RP="⩔",bP="∨",TP="⩝",NP="ℴ",LP="ℴ",PP="ª",OP="º",BP="⊶",HP="⩖",MP="⩗",FP="⩛",UP="Ⓢ",qP="𝒪",QP="ℴ",jP="Ø",VP="ø",GP="⊘",WP="Õ",YP="õ",XP="⨶",zP="⨷",KP="⊗",ZP="Ö",JP="ö",_P="⌽",$P="‾",eO="⏞",tO="⎴",nO="⏜",rO="¶",oO="∥",sO="∥",iO="⫳",lO="⫽",cO="∂",aO="∂",uO="П",fO="п",dO="%",pO=".",hO="‰",gO="⊥",mO="‱",vO="𝔓",wO="𝔭",yO="Φ",AO="φ",EO="ϕ",xO="ℳ",kO="☎",SO="Π",CO="π",IO="⋔",DO="ϖ",RO="ℏ",bO="ℎ",TO="ℏ",NO="⨣",LO="⊞",PO="⨢",OO="+",BO="∔",HO="⨥",MO="⩲",FO="±",UO="±",qO="⨦",QO="⨧",jO="±",VO="ℌ",GO="⨕",WO="𝕡",YO="ℙ",XO="£",zO="⪷",KO="⪻",ZO="≺",JO="≼",_O="⪷",$O="≺",eB="≼",tB="≺",nB="⪯",rB="≼",oB="≾",sB="⪯",iB="⪹",lB="⪵",cB="⋨",aB="⪯",uB="⪳",fB="≾",dB="′",pB="″",hB="ℙ",gB="⪹",mB="⪵",vB="⋨",wB="∏",yB="∏",AB="⌮",EB="⌒",xB="⌓",kB="∝",SB="∝",CB="∷",IB="∝",DB="≾",RB="⊰",bB="𝒫",TB="𝓅",NB="Ψ",LB="ψ",PB=" ",OB="𝔔",BB="𝔮",HB="⨌",MB="𝕢",FB="ℚ",UB="⁗",qB="𝒬",QB="𝓆",jB="ℍ",VB="⨖",GB="?",WB="≟",YB='"',XB='"',zB="⇛",KB="∽̱",ZB="Ŕ",JB="ŕ",_B="√",$B="⦳",eH="⟩",tH="⟫",nH="⦒",rH="⦥",oH="⟩",sH="»",iH="⥵",lH="⇥",cH="⤠",aH="⤳",uH="→",fH="↠",dH="⇒",pH="⤞",hH="↪",gH="↬",mH="⥅",vH="⥴",wH="⤖",yH="↣",AH="↝",EH="⤚",xH="⤜",kH="∶",SH="ℚ",CH="⤍",IH="⤏",DH="⤐",RH="❳",bH="}",TH="]",NH="⦌",LH="⦎",PH="⦐",OH="Ř",BH="ř",HH="Ŗ",MH="ŗ",FH="⌉",UH="}",qH="Р",QH="р",jH="⤷",VH="⥩",GH="”",WH="”",YH="↳",XH="ℜ",zH="ℛ",KH="ℜ",ZH="ℝ",JH="ℜ",_H="▭",$H="®",eM="®",tM="∋",nM="⇋",rM="⥯",oM="⥽",sM="⌋",iM="𝔯",lM="ℜ",cM="⥤",aM="⇁",uM="⇀",fM="⥬",dM="Ρ",pM="ρ",hM="ϱ",gM="⟩",mM="⇥",vM="→",wM="→",yM="⇒",AM="⇄",EM="↣",xM="⌉",kM="⟧",SM="⥝",CM="⥕",IM="⇂",DM="⌋",RM="⇁",bM="⇀",TM="⇄",NM="⇌",LM="⇉",PM="↝",OM="↦",BM="⊢",HM="⥛",MM="⋌",FM="⧐",UM="⊳",qM="⊵",QM="⥏",jM="⥜",VM="⥔",GM="↾",WM="⥓",YM="⇀",XM="˚",zM="≓",KM="⇄",ZM="⇌",JM="‏",_M="⎱",$M="⎱",eF="⫮",tF="⟭",nF="⇾",rF="⟧",oF="⦆",sF="𝕣",iF="ℝ",lF="⨮",cF="⨵",aF="⥰",uF=")",fF="⦔",dF="⨒",pF="⇉",hF="⇛",gF="›",mF="𝓇",vF="ℛ",wF="↱",yF="↱",AF="]",EF="’",xF="’",kF="⋌",SF="⋊",CF="▹",IF="⊵",DF="▸",RF="⧎",bF="⧴",TF="⥨",NF="℞",LF="Ś",PF="ś",OF="‚",BF="⪸",HF="Š",MF="š",FF="⪼",UF="≻",qF="≽",QF="⪰",jF="⪴",VF="Ş",GF="ş",WF="Ŝ",YF="ŝ",XF="⪺",zF="⪶",KF="⋩",ZF="⨓",JF="≿",_F="С",$F="с",eU="⊡",tU="⋅",nU="⩦",rU="⤥",oU="↘",sU="⇘",iU="↘",lU="§",cU=";",aU="⤩",uU="∖",fU="∖",dU="✶",Line truncated
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}var s=!0,i=!1,l;return{s:function(){n=n.call(e)},n:function(){var u=n.next();return s=u.done,u},e:function(u){i=!0,l=u},f:function(){try{!s&&n.return!=null&&n.return()}finally{if(i)throw l}}}}function dZ(e,t){if(e){if(typeof e=="string")return Au(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if(n==="Object"&&e.constructor&&(n=e.constructor.name),n==="Map"||n==="Set")return Array.from(e);if(n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Au(e,t)}}function Au(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}var pZ=Qp,Eu={fg:"#FFF",bg:"#000",newline:!1,escapeXML:!1,stream:!1,colors:hZ()};function hZ(){var e={0:"#000",1:"#A00",2:"#0A0",3:"#A50",4:"#00A",5:"#A0A",6:"#0AA",7:"#AAA",8:"#555",9:"#F55",10:"#5F5",11:"#FF5",12:"#55F",13:"#F5F",14:"#5FF",15:"#FFF"};return mo(0,5).forEach(function(t){mo(0,5).forEach(function(n){mo(0,5).forEach(function(r){return gZ(t,n,r,e)})})}),mo(0,23).forEach(function(t){var n=t+232,r=t0(t*10+8);e[n]="#"+r+r+r}),e}function gZ(e,t,n,r){var o=16+e*36+t*6+n,s=e>0?e*40+55:0,i=t>0?t*40+55:0,l=n>0?n*40+55:0;r[o]=mZ([s,i,l])}function t0(e){for(var t=e.toString(16);t.length<2;)t="0"+t;return t}function mZ(e){var t=[],n=e0(e),r;try{for(n.s();!(r=n.n()).done;){var o=r.value;t.push(t0(o))}}catch(s){n.e(s)}finally{n.f()}return"#"+t.join("")}function xu(e,t,n,r){var o;return t==="text"?o=AZ(n,r):t==="display"?o=wZ(e,n,r):t==="xterm256Foreground"?o=Lo(e,r.colors[n]):t==="xterm256Background"?o=Po(e,r.colors[n]):t==="rgb"&&(o=vZ(e,n)),o}function vZ(e,t){t=t.substring(2).slice(0,-1);var n=+t.substr(0,2),r=t.substring(5).split(";"),o=r.map(function(s){return("0"+Number(s).toString(16)).substr(-2)}).join("");return No(e,(n===38?"color:#":"background-color:#")+o)}function wZ(e,t,n){t=parseInt(t,10);var r={"-1":function(){return"<br/>"},0:function(){return e.length&&n0(e)},1:function(){return Lt(e,"b")},3:function(){return Lt(e,"i")},4:function(){return Lt(e,"u")},8:function(){return No(e,"display:none")},9:function(){return Lt(e,"strike")},22:function(){return No(e,"font-weight:normal;text-decoration:none;font-style:normal")},23:function(){return Su(e,"i")},24:function(){return Su(e,"u")},39:function(){return Lo(e,n.fg)},49:function(){return Po(e,n.bg)},53:function(){return No(e,"text-decoration:overline")}},o;return r[t]?o=r[t]():4<t&&t<7?o=Lt(e,"blink"):29<t&&t<38?o=Lo(e,n.colors[t-30]):39<t&&t<48?o=Po(e,n.colors[t-40]):89<t&&t<98?o=Lo(e,n.colors[8+(t-90)]):99<t&&t<108&&(o=Po(e,n.colors[8+(t-100)])),o}function n0(e){var t=e.slice(0);return e.length=0,t.reverse().map(function(n){return"</"+n+">"}).join("")}function mo(e,t){for(var n=[],r=e;r<=t;r++)n.push(r);return n}function yZ(e){return function(t){return(e===null||t.category!==e)&&e!=="all"}}function ku(e){e=parseInt(e,10);var t=null;return e===0?t="all":e===1?t="bold":2<e&&e<5?t="underline":4<e&&e<7?t="blink":e===8?t="hide":e===9?t="strike":29<e&&e<38||e===39||89<e&&e<98?t="foreground-color":(39<e&&e<48||e===49||99<e&&e<108)&&(t="background-color"),t}function AZ(e,t){return t.escapeXML?pZ.encodeXML(e):e}function Lt(e,t,n){return n||(n=""),e.push(t),"<".concat(t).concat(n?' style="'.concat(n,'"'):"",">")}function No(e,t){return Lt(e,"span",t)}function Lo(e,t){return Lt(e,"span","color:"+t)}function Po(e,t){return Lt(e,"span","background-color:"+t)}function Su(e,t){var n;if(e.slice(-1)[0]===t&&(n=e.pop()),n)return"</"+t+">"}function EZ(e,t,n){var r=!1,o=3;function s(){return""}function i(k,w){return n("xterm256Foreground",w),""}function l(k,w){return n("xterm256Background",w),""}function c(k){return t.newline?n("display",-1):n("text",k),""}function u(k,w){r=!0,w.trim().length===0&&(w="0"),w=w.trimRight(";").split(";");var x=e0(w),S;try{for(x.s();!(S=x.n()).done;){var D=S.value;n("display",D)}}catch(I){x.e(I)}finally{x.f()}return""}function p(k){return n("text",k),""}function v(k){return n("rgb",k),""}var h=[{pattern:/^\x08+/,sub:s},{pattern:/^\x1b\[[012]?K/,sub:s},{pattern:/^\x1b\[\(B/,sub:s},{pattern:/^\x1b\[[34]8;2;\d+;\d+;\d+m/,sub:v},{pattern:/^\x1b\[38;5;(\d+)m/,sub:i},{pattern:/^\x1b\[48;5;(\d+)m/,sub:l},{pattern:/^\n/,sub:c},{pattern:/^\r+\n/,sub:c},{pattern:/^\r/,sub:c},{pattern:/^\x1b\[((?:\d{1,3};?)+|)m/,sub:u},{pattern:/^\x1b\[\d?J/,sub:s},{pattern:/^\x1b\[\d{0,3};\d{0,3}f/,sub:s},{pattern:/^\x1b\[?[\d;]{0,3}/,sub:s},{pattern:/^(([^\x1b\x08\r\n])+)/,sub:p}];function C(k,w){w>o&&r||(r=!1,e=e.replace(k.pattern,k.sub))}var E=[],g=e,m=g.length;e:for(;m>0;){for(var a=0,f=0,d=h.length;f<d;a=++f){var y=h[a];if(C(y,a),e.length!==m){m=e.length;continue e}}if(e.length===m)break;E.push(0),m=e.length}return E}function xZ(e,t,n){return t!=="text"&&(e=e.filter(yZ(ku(n))),e.push({token:t,data:n,category:ku(n)})),e}var kZ=function(){function e(t){uZ(this,e),t=t||{},t.colors&&(t.colors=Object.assign({},Eu.colors,t.colors)),this.options=Object.assign({},Eu,t),this.stack=[],this.stickyStackLine truncated
</script>
<style type='text/css'>:root{--color-canvas-default-transparent: rgba(255,255,255,0);--color-marketing-icon-primary: #218bff;--color-marketing-icon-secondary: #54aeff;--color-diff-blob-addition-num-text: #24292f;--color-diff-blob-addition-fg: #24292f;--color-diff-blob-addition-num-bg: #CCFFD8;--color-diff-blob-addition-line-bg: #E6FFEC;--color-diff-blob-addition-word-bg: #ABF2BC;--color-diff-blob-deletion-num-text: #24292f;--color-diff-blob-deletion-fg: #24292f;--color-diff-blob-deletion-num-bg: #FFD7D5;--color-diff-blob-deletion-line-bg: #FFEBE9;--color-diff-blob-deletion-word-bg: rgba(255,129,130,.4);--color-diff-blob-hunk-num-bg: rgba(84,174,255,.4);--color-diff-blob-expander-icon: #57606a;--color-diff-blob-selected-line-highlight-mix-blend-mode: multiply;--color-diffstat-deletion-border: rgba(27,31,36,.15);--color-diffstat-addition-border: rgba(27,31,36,.15);--color-diffstat-addition-bg: #2da44e;--color-search-keyword-hl: #fff8c5;--color-prettylights-syntax-comment: #6e7781;--color-prettylights-syntax-constant: #0550ae;--color-prettylights-syntax-entity: #8250df;--color-prettylights-syntax-storage-modifier-import: #24292f;--color-prettylights-syntax-entity-tag: #116329;--color-prettylights-syntax-keyword: #cf222e;--color-prettylights-syntax-string: #0a3069;--color-prettylights-syntax-variable: #953800;--color-prettylights-syntax-brackethighlighter-unmatched: #82071e;--color-prettylights-syntax-invalid-illegal-text: #f6f8fa;--color-prettylights-syntax-invalid-illegal-bg: #82071e;--color-prettylights-syntax-carriage-return-text: #f6f8fa;--color-prettylights-syntax-carriage-return-bg: #cf222e;--color-prettylights-syntax-string-regexp: #116329;--color-prettylights-syntax-markup-list: #3b2300;--color-prettylights-syntax-markup-heading: #0550ae;--color-prettylights-syntax-markup-italic: #24292f;--color-prettylights-syntax-markup-bold: #24292f;--color-prettylights-syntax-markup-deleted-text: #82071e;--color-prettylights-syntax-markup-deleted-bg: #FFEBE9;--color-prettylights-syntax-markup-inserted-text: #116329;--color-prettylights-syntax-markup-inserted-bg: #dafbe1;--color-prettylights-syntax-markup-changed-text: #953800;--color-prettylights-syntax-markup-changed-bg: #ffd8b5;--color-prettylights-syntax-markup-ignored-text: #eaeef2;--color-prettylights-syntax-markup-ignored-bg: #0550ae;--color-prettylights-syntax-meta-diff-range: #8250df;--color-prettylights-syntax-brackethighlighter-angle: #57606a;--color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;--color-prettylights-syntax-constant-other-reference-link: #0a3069;--color-codemirror-text: #24292f;--color-codemirror-bg: #ffffff;--color-codemirror-gutters-bg: #ffffff;--color-codemirror-guttermarker-text: #ffffff;--color-codemirror-guttermarker-subtle-text: #6e7781;--color-codemirror-linenumber-text: #57606a;--color-codemirror-cursor: #24292f;--color-codemirror-selection-bg: rgba(84,174,255,.4);--color-codemirror-activeline-bg: rgba(234,238,242,.5);--color-codemirror-matchingbracket-text: #24292f;--color-codemirror-lines-bg: #ffffff;--color-codemirror-syntax-comment: #24292f;--color-codemirror-syntax-constant: #0550ae;--color-codemirror-syntax-entity: #8250df;--color-codemirror-syntax-keyword: #cf222e;--color-codemirror-syntax-storage: #cf222e;--color-codemirror-syntax-string: #0a3069;--color-codemirror-syntax-support: #0550ae;--color-codemirror-syntax-variable: #953800;--color-checks-bg: #24292f;--color-checks-run-border-width: 0px;--color-checks-container-border-width: 0px;--color-checks-text-primary: #f6f8fa;--color-checks-text-secondary: #8c959f;--color-checks-text-link: #54aeff;--color-checks-btn-icon: #afb8c1;--color-checks-btn-hover-icon: #f6f8fa;--color-checks-btn-hover-bg: rgba(255,255,255,.125);--color-checks-input-text: #eaeef2;--color-checks-input-placeholder-text: #8c959f;--color-checks-input-focus-text: #8c959f;--color-checks-input-bg: #32383f;--color-checks-input-shadow: none;--color-checks-donut-error: #fa4549;--color-checks-donut-pending: #bf8700;--color-checks-donut-success: #2da44e;--color-checks-donut-neutral: #afb8c1;--color-checks-dropdown-text: #afb8c1;--color-checks-dropdown-bg: #32383f;--color-checks-dropdown-border: #424a53;--color-checks-dropdown-shadow: rgba(27,31,36,.3);--color-checks-dropdown-hover-text: #f6f8fa;--color-checks-dropdown-hover-bg: #424a53;--color-checks-dropdown-btn-hover-text: #f6f8fa;--color-checks-dropdown-btn-hover-bg: #32383f;--color-checks-scrollbar-thumb-bg: #57606a;--color-checks-header-label-text: #d0d7de;--color-checks-header-label-open-text: #f6f8fa;--color-checks-header-border: #32383f;--color-checks-header-icon: #8c959f;--color-checks-line-text: #d0d7de;--color-checks-line-num-text: rgba(140,149,159,.75);--color-checks-line-timestamp-text: #8c959f;--color-checks-line-hover-bg: #32383f;--color-checks-line-selected-bg: rgba(33,139,255,.15);--color-checks-line-selected-num-text: #54aeff;--color-checks-line-dt-fm-text: #24292f;--color-checks-line-dt-fm-bg: #9a6700;--color-checks-gate-bg: rgba(1Line truncated
</style>
</head>
<body>
<div id='root'></div>
</body>
</html>
<script>
window.playwrightReportBase64 = "data:application/zip;base64,UEsDBBQAAAgIAAhS81Y0S/t9WwAAAHwAAAALAAAAcmVwb3J0Lmpzb25VikEKgCAUBe/y1i5ae4guEC0++YVSU/QLhXj3ImrRbmaYhsBChoSgW1ewq+cCPc0KKceNFxkpfKUIyY0NEoU89KDAR7ofNo/U/afWkzsfKm5N6a3RQUuu3PsFUEsBAj8DFAAACAgACFLzVjRL+31bAAAAfAAAAAsAAAAAAAAAAAAAALSBAAAAAHJlcG9ydC5qc29uUEsFBgAAAAABAAEAOQAAAIQAAAAAAA==";</script>
@@ -12,7 +12,7 @@ const path = require("path");
* @see https://playwright.dev/docs/test-configuration
*/
module.exports = defineConfig({
testDir: "./playwright-tests",
testDir: ".",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
@@ -81,16 +81,16 @@ module.exports = defineConfig({
stdout: "pipe",
},
{
cwd: path.join(process.cwd(), "playwright-tests", "web"),
command: "dx serve",
cwd: path.join(process.cwd(), "web"),
command: "cargo run --package dioxus-cli -- serve",
port: 8080,
timeout: 10 * 60 * 1000,
reuseExistingServer: !process.env.CI,
stdout: "pipe",
},
{
cwd: path.join(process.cwd(), 'playwrite-tests', 'fullstack'),
command: 'dx build --features web --release\ncargo run --release --features ssr',
cwd: path.join(process.cwd(), 'fullstack'),
command: 'cargo run --package dioxus-cli -- build --features web --release\ncargo run --release --features ssr',
port: 3333,
timeout: 10 * 60 * 1000,
reuseExistingServer: !process.env.CI,